|
Dynamic Model Selection within a Controller
By: Bruce Bahlmann - Contributing Author (your
feedback
is important to us!)
In rails development, where you want to loop through various columns that
reference specific models. Rather than create some large nested if or case
statement to define each model and perform some method on that model, you
can dynamically create a reference (or selection) which then can be used to
access the data associated with the model.
Creating a model selection requires the following steps: create a common
method across the models you want to select, create the loop that selects
the models. In our case, we will show two tables: inventory and
manufacturer. Manufacturer table is a lookup table for manufacturers that
are associated with records within the inventory table.
[apps/models/manufacturer.rb]
class Manufacturer < ActiveRecord::Base
establish_connection :apps
set_table_name "global_manufacturer"
def lname
self.name
end
has_many :inventory
end
The model above creates a simple method called lname (lookup name). Since
you are using dynamic selection, you probably have a number of these tables
(like the one below), that reference a column in the main table.

For completeness sake, the following is the relevant aspects of the
inventory model which points to the manufacturer table for a list of
possible manufacturers available to its inventory records. IMPORTANT NOTE:
you really need to use the RIGHT NAMING CONVENTION here for this to work. If
you have a model named [Manufacturer] the foreign key used to access that
table belonging to the model MUST BE, [manufacturer_id]. If you do not use
this type of naming convention, you will find yourself writing a lot of
extra code in vein - as the result will not only be ugly, but it won't work.
[apps/models/inventory.rb]
class Inventory < ActiveRecord::Base
...
belongs_to :manufacturer, :foreign_key => :manufacturer_id
validate :custom_validation
def custom_validation
## validate
errors.add(:manufacturer_id, " - left blank - it is required to save ...") if manufacturer_id.nil?
if errors.count > 0
## Any post processing of errors prior to returning back to edit/new page
else
## Proceed with any finished processing prior to saving
end
end
end
The point of showing the above is the linking to the static lookup table,
as well as how to manage error handling for the static lookup table. The
inventory table would look something like the following:

Moving now to the controller, we'll perform the task of the dynamic
selection.
[apps/controllers/inventories_controller.rb]
...
@inventory = Inventory.find(params[:id])
## Array of lookup tables used for logging changes
ltArray = ["manufacturer_id","classification_id","location_id","disposition_id","vendor_id","warranty_id"]
@inventory.attributes.each do |k,v|
next if k == 'notes'
next if k == 'lmodified'
if @inventory.send(k).to_s != params[:inventory][k].to_s
## Mark lhash with key (indicating the instance array has been loaded)
if ltArray.include?(k)
comparisonText += ", #{k} changed from ["+
"#{@inventory.send(k) == '' ? '' : k[0..-4].classify.constantize.find(@inventory.send(k)).lname}]"+
" to ["+
"#{params[:inventory][k] == '' ? '' : k[0..-4].classify.constantize.find(params[:inventory][k]).lname}]"
else
comparisonText += ", #{k} changed from [#{@inventory.send(k)}] to [#{params[:inventory][k]}]"
end
end
end
comparisonText.gsub!(/^\,\s/,'')
...
In the controller, we loop through the inventory table columns, and
having defined which columns we want to treat special (see ltArray), we can
then process these entries and use the column names to
access the models associated with them. This can be done by first converting
the text of the column name into a model (e.g. manufacturer_id). The
conversion process begins with removing the [_id] part of the name - this is
done using [0..-4] which is removed from the key [k] assigned in the loop [@inventory.attributes.each
do |k,v|]. Once we have just the [manufacturer] key name, we can use
classify and constantize to turn the string into the model name
[Manufacturer] which can then be used with find, etc.
Note, we need to use [send] to access the
object [@inventory] values which are used as arguments for the find. The
above code can be used to translate lookup table indexes to common names so
that the log shows what [real-world] value actually changed rather than
simply show a certain column changed from index x to index y (which is
meaningless without the table to make sense of the indexes).
Can Birds-Eye.Net help you or your Company?
Receive your Birds-Eye.Net articles and white
papers hot off
the presses by adding our RSS feed to your reader.
|
|