All of us know that we can use multiple Find methods to get information from the database via models and we also know that this is possible because Active Record::Base provides us the support for the same. Some of the normal ones which are most used by developers are find(id), find_all and find_first. All these three are the derivatives of ActiveRecord::Base::find(*args) and most of the information are handled by these. This method takes many parameters which can be used to create easy operations. The parameters are:
- :conditions (for where clause)
-
rder (determines the order)
- :group (grouping data)
- :limit(specify maximum number of rows to be retrieved)
-
ffset(number of records to be omitted)
- :joins(join multiple tables)
- :include(for associations with left outer join)
- :select(to specify the attributes of the output data)
- :readonly(to execute as reader)
In this post, we shall not look into more details of each of this segment, possibly we can write another post to provide information on these. So most of the queries that are used by Rails developers are any of the above ones. However it becomes an issue to resolve. I shall explain in this post 3 methods which makes developers’ life easy. These finders are:
- Query finder Method (Generalized Finder) : Query finder is a methodology where the user finds information from the database by use of SQL queries. The developer uses standard SQL Queries in multiple form and executes them using the find_by_sql method.
- Dynamic finder Method (Specialized Finder) : This method uses the method_missing method of the ActiveRecord::Base class to identify and create finder methods which can be declared and used dynamically. However these have to follow some rules.They can be applied only in conjunction with table fields. The four methods used are find_by_xxx, find_all_by_xxx, find_or_create_by_xxx and find_or_initialize_by_xxx methods. Dynamic finders also use the _and_ keyword to use multiple table fields in the single request.
- Custom finder Method (Customized Finder): This method uses writing custom find* methods which could be either un-conventional method (not as used in above methods) or override the methods declared by the ActiveRecord::Base. Custom finders are declared in the Model objects and are generally tagged by self keyword.
METHOD #1: Query Finders Method (Generalized Find)
Many a times the developers are comfortable to use standard SQL queries to write their applications database requests rather than using any of the above find methods or parameters and they get lost identifying these parameters. Also for complex queries it becomes difficult to use.
However ActiveRecord::Base again comes to the rescue and gives one another method find_by_sql . Using this method the developers can write SQL queries and execute them. The advantage of this system would also be that it could be used for executing stored procedures also. This is advantageous because MySQL 5.0 comes in with Stored Procedures.
The following are few examples of using find_by_sql methods
Retrieving complete information of selected records:
Developers can just key in the sql query and get the information. Example:
@loggedinusers = User.find_by_sql(“select * from users where loggedin=true;”)
The @loggedinusers element shall contain all the logged in users information.
However if there are inputs required which are unknown then generic find by sql could be written as similar to :conditions of Find method. An example could be as follows:
@loggedinusers =User.find_by_sql(["select * from users where loggedin=? and isActive=?",@isloggedin,@isactive]
Retrieving partial information as required:
In the above example, we identified that the inputs could be varied and outputs has to be the complete database record. When these operations are done, the advantage is that the objects can be directly mapped to a particular database record. However in this world there are requirements of all types. There are requirements where only particular information has to be populated for these types of inputs we could use.
@loggedinusers = User.find_by_sql(["SELECT username, lastloggedindate, isActive FROM users"])
The @loggedinusers contains only the information about the username, lastloggedindate and isActive. The following code snippet can be used to get the information:
loggedinuser = @loggedinusers [0]
puts loggedinuser.username
puts loggedinuser.lastloggedindate
puts loggedinuser.isActive
METHOD #2: Dynamic Finders Method (Specialized Find )
Dynamic finders are a method of finding information based on particular database field of a table. If find_by_sql method provides a generalized method to query information from the database, by using dynamic finders, you can construct methods to find records based on specific requirements which are input as parameters.
There are 4 types of dynamic finders which can be used:
- find_by_XXX methods: Finds the first row for a given database field name(s).
- find_all_by_XXX methods: Finds all the rows that satisfies a given database field name(s).
- find_or_initialize_by_XXX: Finds a row with a given parameter(s) and if not available, creates a new row and returns back to the user the empty object to be filled.
- find_or_create_by_XXX methods: Finds a row with particular parameters and if not available, then will be created in the database with the input parameters and then returned.
find_by_XXX methods:
This method is used to find any record by a given input parameter. This makes developers’ life easier to find records by any database field. One example would be is to find the record of the person whose username is ‘heurionconsulting’.
@user= User.find_by_username(“heurionconsulting”)
The example writes to :
@user = User.find_by_sql(["SELECT Top(1) * FROM users WHERE username=?","heurionconsulting"])
The advantage of this is the find_by_xxx can also be added in conjunction with _and_ option if we have to add multiple database fields to create a query. We could modify our earlier example to find the first user who has logged in last and is a purchaser.
@firstloggedinuser= User.find_by_loggedindate_and_ispurchaser( “05/08/2007″ , “true” )
The above example explains to return the first user information who has loggedindate and is a purchaser. This is equivalent to
@firstloggedinuser = User.find_by_sql(“SELECT Top(1) * FROM users WHERE loggedindate=’05/08/2007′ AND ispurchaser=’true’; “)
Use of keyword _and_ helps to identify all the database fields which is required to achieve the output.
This method is used only to get the first record of the result only.
find_all_by_XXX methods:
This method is similar to the find_by_XXX method, but this is used when you want all the records that satisfy the criteria. The _and_ criteria could be used for this object as well.
An example of find_all_by_XXX method could be as follows:
@loggedinusers= User.find_all_by_loggedindate_and_ispurchaser( “05/08/2007″ , “true”)
This example results in:
@loggedinusers = User.find_by_sql(“SELECT * FROM users WHERE loggedindate= ’05/08/2007′ AND ispurchaser=’true’; “)
find_or_initialize_by_XXX methods:
@product= Product.find_or_initialize_by_product_name(“MUG”)
if@product.product_name.nil?
redirect_to :action=>‘viewproduct’, :locals=>{:product => @product}
else
redirect_to :action=>‘newproduct’,:locals=>{:product => @product}
end
In the above example, the method shall first find if “MUG” is available in the database. If the product is not available, then the user could create a new product with a new product id and the product name shall be “MUG”. So in the newproduct page the user sets only those information which is missing and saves the information to the database.
find_or_create_by_XXX methods:
This method is a complement of the find_by_intialize_by_XXX where in the initialize case, the user just initializes the object with the input information and gets the object. Whereas in case of find_or_create_by_XXX method, the user can provide complete information required to save an object. Modifying the above example we could re-write it as:
@product = Product.find_or_create_by_product_name(“MUG”,:create_args => {:product_name => ‘Mug’, :description=>“New Product”, :availability=>true})
This method uses a :create_args parameter which has to be filled if the object is not found. So if there is no product by name mug, then create a product and set its information as in the :create_args section and return the object.
P.S: All these 4 dynamic finder methods are actually calculated in the method_missing method declared in the ActiveRecord::Base.
METHOD #3: Custom Finders Method (Customized Find)
In the earlier 2 types we have queried the database by: a) Providing the complete sql syntax i.e. find_by_sql method and b) By modifying the find method to read the required syntax i.e. find_by/all_ or find_or_initialize/create_by methods. Both of these methods are good and have advantages on their own. But there are many other scenarios where any of these methods cannot be used.
One classic example would be where you would like to evaluate a scenario before you call the Active record’s find method. For such scenarios the best approach is to use custom finders.
Custom finders are nothing but writing find methods within the Model and call the ActiveRecord::Base::Find* methods from these methods. Custom finders generally are self methods declared in the Model object.
#Product.rb — Model file
def self.find_product_by_status(status)
if status != utofshelf
Product.find_by_sql ["SELECT * FROM product WHERE status=?",status]
else
Product.find_by_sql ["SELECT * FROM product WHERE status= 'outofshelf' AND availability=false"]
end
end
The example above identifies a custom finder where it checks if the status is not out of shelf, then get all the products based on the status and if it is out of shelf, then check the status and also is not available. Also if you see the name of the find method it is an un-conventional name find _product_by_status instead of find_by_status.
Developers can utilize custom finder to name methods by thier own way or even custom finders can also be used to over write the method names which exists in the ActiveRecord::Base class. An example can be found as follows:
#Product.rb — Model file
def self.find_by_status(status)
if status != utofshelf
Product.find_by_sql ["SELECT * FROM product WHERE status=? AND availability=false"]
else
super
end
end
In this example, you see that the custom finder has the same name as a find_by_status method which is a standard ActiveRecord::Base method and has been overwritten by the find method in the Model file. In the example, we have also dealt in re-directing the custom finder method back to its parent method by using the super keyword.
To Summarize
In this post we have explained various types of find methods.
- We have explained in brief about Find method and its parameters. However going in depth of this method is out of the scope of this article.
- We have dealt with how to use find_by_sql method to query complete and partial record information (with and with out multiple inputs).
- We have come across how to use dynamic finders by just the names of the database fields with _and_ combination to find single, all, find & initialize and find & create methods by using find_by/all_ and find_or_create/initialize_by methods.
- We have also explained how to use custom finders which can be used either as a wrapper on general find methods or override the ActiveRecord::Base‘s find methods.
We hope these information is useful for the reader and are open to any comments.
– Heurion Consulting Information Release
