Say I wanted to allow an administrative user to add a field to an ActiveRecord Model via an interface in the Rails app. I believe the normal ActiveRecord::Migration code would be adequate for modifying the AR Model's table structure (something that would not be wise for many applications - I know). Of course, only certain types of fields could be added...in theory.
Obviously, the forms that add (or edit) records to this newly modified ActiveRecord Model would need to be build dynamically at run-time. A common form_for approach won't do. This discussion suggests this can only be accomplished with JavaScript.
http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/fc0b55fd4b2438a5
I've used Ruby in the past to query an object for it's available methods. I seem to remember it was insanely slow. I'm too green with Ruby and Rails to know an elegant way to approach this. I hope someone here may. I'm also open to entirely different approaches to this problem that don't involve modifying the database.
-
You could use Flex Attributes for this, though if you want to be able to search or order by these new columns you'll have to write (a lot of) custom SQL.
-
I have seen the dynamic alteration/migration of tables offered as a solution many times but I have never actually seen it implemented. There are many reasons why this solution is rarely implemented.
- If the table is large then the table may/will be locked for extended periods of what is supposed to be up-time.
- Why is your model changing dynamically? It is quite rare for a models structure to need to change dynamically. It is more often an indication that you are trying to model something specific in a generalised way.
- This is often an attempt a producing a "Categorised" model than could be better solved by another approach.
- DDL statements are often not allowed by the same user that is being used for day to day DML requirements. Whilst this could be the case, and often is in the ROR arena it is not always the "right" way to do it.
What are you trying to achieve here? A better understanding of the problem would probably reveal a more natural solution.
Sam : My goal is to allow administrative users to add custom fields (of limited types) to specific (not all) models in the application. -
To access the columns which are currently defined for a model, use the columns method - it will give you, for each column, its name, type and other information (such as whether it is a primary key, etc.)
However, modifying the schema at runtime is delicate.
The schema is pre-loaded (and cached, from the DB driver) by each model class when it is first loaded. In
production
mode, Rails only does this once per model, around startup.- In order to force Rails to refresh its cached schema following your modification, you should force Ruby to reload the affected model's class (pretty much what Rails does for you automatically, after each request, when running in
development
mode - see how to reload a class usingremove_const
followed byload
.) - If you have a Mongrel cluster, you also have to inform the other processes in the cluster, which run in their own separate memory space, to also reload their model's classes (some clusters will allow you to create a 'restart.txt' file, which will cause an automatic soft-restart of all processes in your cluster with no additional work required on your behalf.)
Now, these having been said, depending on the actual problem that you need to solve you may not need to dynamically alter the schema after all. Instead of adding, say, columns
col1
,col2
andcol3
to some tableentries
(modelEntry
), you can use a table calleddyn_attribs
, where Entryhas_many :dyn_attribs
, and wheredyn_attribs
has both akey
column (which in this case can have valuescol1
,col2
orcol3
) and avalue
column (which lists the corresponding values forcol1
,col2
etc.)Thus, instead of:
my_entry = Entry.find(123) col1 = my_entry.col1 #do something with col1
you would use:
my_entry = Entry.find(123, :include => :dyn_attribs) dyn_attribs = my_entry.dyn_attribs.inject(HashWithIndifferentAccess.new) { |s,a| s[a.key] = a.value ; s } col1 = dyn_attribs[:col1] #do something with col1
The above
inject
call can be factored away into the model, or even into a base class inherited from by all models that may require additional, dynamic columns/attributes (see Polymorphic associations on how to make several models share the samedyn_attribs
table for dynamic attributes.)
UPDATE
Adding or renaming a column via a regular HTML form.
Assume that you have a
DynAttrTable
model representing a table with dynamic attributes, as well as aDynAttrDef
defining the dynamic attribute names for a given table.Run:
script/generate scaffold_resource DynAttrTable name:string script/generate scaffold_resource DynAttrDef name:string rake db:migrate
Then edit the generated models:
class DynAttrTable < ActiveRecord::Base has_many :dyn_attr_defs end class DynAttrDef < ActiveRecord::Base belongs_to :dyn_attr_table end
You may continue to edit the controllers and the views like in this tutorial, replacing
Recipe
withDynAttrTable
, andIngredient
withDynAttrDef
.Alternatively, use one of the plugins reviewed here to automatically put the
dyn_attr_tables
anddyn_attr_defs
tables under management by an automated interface (with all its bells and whistles), with virtually zero implementation effort on your behalf.This should get you going.
Sam : This is an elegant approach to the problem on the back end. I'm concerned about browser support, so is there an easy way using pure HTML forms (no AJAX) generated by Ruby to build a form dynamically so it would include the new dynamic attributes automatically? Googling is not turning up an easy way - In order to force Rails to refresh its cached schema following your modification, you should force Ruby to reload the affected model's class (pretty much what Rails does for you automatically, after each request, when running in
-
Say I wanted to allow an administrative user to add a field to an ActiveRecord Model via an interface in the Rails app.
I've solved this sort of problem before by having an extra model called AdminAdditions. The table includes an id, an admin user id, a model name string, a type string, and a default value string.
I override the model's find and save methods to add attributes from its admin_additions, and save them appropriately when changed. The model table has a large text field, initially empty, where I save nondefault values of the added attributes.
Essentially the views and controllers can pretend that every attribute of the model has its own column. This means
form_for
and so on all work. -
ActiveRecord::Migration.add_column(User, "email", :string)
tom : ActiveRecord::Migration.add_column(User, "email", :string); User.reset_column_information
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.