Sign In
Start Page | Recent Changes | All Pages | Orphan Pages | Junebug Help

Extension - Model Tags

Simple examples

Query all records of a given model


<r:model name="Product">
  <r:find:each>
    <r:value name="id" />, 
    <r:value name="price" />, 
    <r:value name="name" /> <br>
  </r:find:each>
</r:model>

Simple search with conditions


<r:model name="Product">
  <r:find conditions="price>1">
    <r:each>
      <r:value name="id" />, 
      <r:value name="price" />, 
      <r:value name="name" /> <br>
    </r:each>
    Number of records returned: <r:count />
  </r:find>
</r:model>

Summary of the tags shown

model tag

Attribute:
  • name: (required) specify model name here

model:find tag

There are two main operation modes for model:find:

  • if id attribute was given that it returns the record with the given id
  • if no id was specified than a group of records will be returned

Either modes support the same tags.

  • limit, order, conditions attributes will be passed to Model.find(...)

If distinct attribute was given than the following query will run: SELECT DISTINCT(distinct) FROM model WHERE conditions etc…

Furthermore, there is a special page type called Filter that may give further filtering conditions. More on that later.

Functions that may be called on the returned data (or on the Model itself)

What described above is very basic, but real life may require more complex calculations. To support that model_tags gives you several possibilities:

  • You may create class funcions (methods) that may be called using a special tag function
  • You may create instance functions (methods) that may be called on the result records

This leads to potential secutity issues, that will be addressed in a very basic fashion. Now lets see this in action.

Class functions

First you need to prepare your model like this:


class Product < ActiveRecord::Base

  def self.myfunction(params=nil,conditions=nil)
    "returned value" 
  end

  def self.otherfunction(params=nil,conditions=nil)
    ["value1","value2"]
  end

  def self.thirdfunction(params=nil,conditions=nil)
    { 'key' => 'value', 'key2' => 'value2' } 
  end

  def self.fourthfunction(params=nil,conditions=nil)
    Product.find(...whatever...)
  end

  def self.function_is_allowed_to?(fun,user)
    ['myfunction','otherfunction','thirdfunction', 'fourthfunction'].include?(fun)
  end

end
  • myfunction returns a string that maybe displayed this way

<r:model name="Product">
  <r:function name="myfunction">
    <r:each:value />
    <r:if_returned>Great! Returned a real value...</r:if_returned>
  </r:function>
</r:model>
  • otherfunction returns an aray that may be displayed this way

<r:model name="Product">
  <r:function name="otherfunction">
    <r:each><r:value />, </r:each>
    Number of returned items: <r:count />
  </r:function>
</r:model>

please note the difference bw each:value and each,value

  • thirdfunction returns a hash, that may be displayed this way:

<r:model name="Product">
  <r:function name="thirdfunction">
    <r:each:value name="key" />
    <r:each:value name="key2" />
  </r:function>
</r:model>
  • fourthfunction returns an array of Products:

<r:model name="Product">
  <r:function name="fourthfunction">
    <r:each>
       <r:value name="id"/> <r:value name="price"/>, <br /> 
    </r:each>
    Number of returned products: <r:count />
  </r:function>
</r:model>

Instance functions

Instance functions may be created like class functions. They may return a single string, an array of values, a hash, an array of hashes or model instances. Same game. Prepare your model almost like above:


class Product < ActiveRecord::Base

  def myfunction(params=nil,conditions=nil)
    ["value1","value2"]
  end

  def function_is_allowed_to?(fun,user)
    ['myfunction'].include?(fun)
  end

end
  • myfunction returns an aray that may be displayed this way

<r:model name="Product">
  <r:find>
    <r:each>
      <r:function name="otherfunction">
        <r:each><r:value />, </r:each>
        Number of returned items: <r:count />
      </r:function>
    </r:each>
  </r:find>
</r:model>

The important point here is that function tag is placed inside model:find:each instead of model. In the latter case the function is called on the Model class, in the former case on the returned Model instance. params=nil,conditions=nil parameters may be used to pass parameters and conditions to the function.

  • params is an array that is the value of the attribute params split on commas like this: params=”1,2,3” => [“1”,”2”,”3”]
  • conditions is the combined conditions given to the find tag and represented by the Filter url.

Filter page type and tags

This feature allows you to create a specific page that processes the children url path as an SQL filter condition. To make this happen the following actions must be taken:

  • create a page with the type: ‘Filter’ (here I assume its url is /filter)
  • the page content would be something like:

<r:filters>
  <r:conditions>
    <r:model name="Product">
      <r:find>
        <r:each>....</r:each>
      </r:find>
    </r:model>
  </r:conditions>
</r:filters>

If someone displays the following url /filter/price:e:100 than the code above is equivalent to this:


<r:model name="Product">
  <r:find conditions="price=100">
    <r:each>....</r:each>
  </r:find>
</r:model>

Thus the url part price:e:100 is equivalent to price=100 condition. Even better, different path components are combined to a single SQL condition. As shown the conditions are encoded like Field:Operation:Argument. The list of possible Operation values are:

  • e : Equals
  • g : Greater than
  • G : Greater or equal
  • l : Less than
  • L : Less or equal
  • a : String starts with
  • z : String ends with
  • c : Contains
  • n : Is null
  • N : Is not null

Null and not null conditions are treated in a special way, becuase Rails often stores empty strings rather than real null values. Because of that they means both empty string and null value. Also note that the Argument value is ignored, but must not be empty for null conditions.

Filter conditions may be enumerated, counted, listed and relative url may be displayed. (More on this here…)

Note: the result of /filter/price:e:100 call will be cached just like any other urls. This is on purpose. One might create a product catalog based on this feature and the distinct tag. It is desired not to query the database for each specific product category/filtered list.

Container to store Model entities

The SiteController that handles static pages disables session support for performance reasons. It also provides caching features. I like both features, so I didn’t want to change that. Still, sometimes part of the page should be created dynamically, while most of them may be cached. One such example is a shopping basket. To solve this I created special tags and a controller.

The idea is to render part of the page using AJAX javascript and handle the basket with a different controller while the container page is still rendered by the SiteController.

The ContainerController handles a collection of ids of a specific Model (type). The collection contains id/quantity pairs. The add_item_to tag below supports quantity attribute too. There is a remove_item_from attribute as well.

To use this feature you need to follow these steps:

  • create a layout with content-type: text/html and only a <r:content /> tag inside (assume this will be called SameAsContent)
  • create a page with the SameAsContent layout (assume its url is /display/item). This page will be rendered in a loop inside the div tag specified below.
  • create a page like this for the static content:

<r:model name="Product">
 <r:find>
  <r:each>
   <r:add_item_to url="/display/item"> <r:value name="name" /> </r:add_item_to>,
  </r:each>
 </r:find>
</r:model>

<div id="Product_container></div>

Now each product name will be rendered in a comma separated list, each of them are displayed as links. If you click on such a link than the page part “Product_container” will be updated with a collection of ’/display/item’ pages. Now lets see what is inside ’/display/item’:


<r:container_item>
   <r:quantity />
   <r:remove_item_from url="/display/item">
    <r:value name="id" /><r:value name="name" />, 
    <r:each>
    <r:function name="image_urls" >
     <r:each><img src="<r:value />" /></r:each>
    </r:function>
    </r:each>
   </r:remove_item_from>
  <r:subtotal_for field="price" />
</r:container_item>

The above example shows that function may be called on the contained Model instance just like above with the model:find:each:function tag. So model tags are available inside container_item as shown above. These tags are also present:

  • container_item:value
  • container_item:quantity: shows the stored quantity of the Model instance
  • container_item:subtotal_for

(More on this here…)


Version 17 | Other versions: « older newer » current versions
Page last edited by dbeck on September 12, 2007 08:18 AM (diff)