So, thought I had this working last night, couldve sworn it. Now it no worky, and I figure its about time to ask for help.
Im defining dynamic fields in the database, semi EAV style, and lets just state right now I dont care to hear your opinions on whether EAV is a good idea or not 🙂
Anyways Im doing it a little differently than Ive done in past, basically when an attribute (or field), is added, I create a add column to a particular attributes table migration and run it (or a remove it one) — ANYWAYS, because there is a category layer sitting in the middle which is the direct relationship where all the attributes are defined, I cant use the actual attribute name as a column name, as attributes are category specific.
So, if it helps you visualize
Entity
belongs_to :category
Category
has_many :entities
EntityAttribute
belongs_to :category
EntityAttributeValue
belongs_to :entity_attribute
belongs_to :entity
And EAV table spans horizontally as new attributes are created, with columns labeled attribute_1 attribute_2, which contain the values for that particular entity.
Anyways — I am trying to make the methods dynamic on the entity model, so I can call @entity.actual_attribute_name, rather than @entity.entity_attribute_value.field_5
Here is the code I thought was working —
def method_missing(method, *args)
return if self.project_category.blank?
puts "Sorry, I don't have #{method}, let me try to find a dynamic one."
puts "let me try to find a dynamic one"
keys = self.project_category.dynamic_fields.collect {|o| o.name.to_sym }
if keys.include?(method)
field = self.project_category.dynamic_fields.select { |field| field.name.to_sym == method.to_sym && field.project_category.id == self.project_category.id }.first
fields = self.project_category.dynamic_field_values.select {|field| field.name.to_sym == method }
self.project_category_field_value.send("field_#{field.id}".to_sym, *args)
end
end
Then today as I went back to code, I realized although I could set the attribute in rails console, and it would return the correct field, when I saved the record, the EntityAttributeValue was not being updated (represented as self.project_category_field_value, above.)
So after looking into it further it looked like I just had to add a before_update or before_save callback to manually save the attribute, and thats where I noticed, in the callback, it would re run the method_missing callback, as if the object was being duplicated (and the new object was copy of original object), or something, Im not quite sure. But at somepoint during save process or before, my attribute dissapears into oblivion.
So, well I guess I half way answered my own question after typing it out, I need to set an instance variable and check to see if it exists at the beginningish of my method_missing method (right?) Maybe that’s not what’s happening I dont know, but Im also asking if there is a better way of doing what I am trying to do.
And if using method_missing is a bad idea, please explain why, as going through posts regarding method missing I heard some people slamming it but not one of those people bothered offering a reasonable explanation as to why method missing was a bad solution.
Thanks in advance.
That’s some seriously intense programming to be going on in the
method_missingdepartment. What you should have is something more like this:You can then try and break this down into two parts. The first is creating a method that decides if it can handle a call with a given name, here
dynamic_attribute_method_for, and the second is the actual method in question. The job of the former is to ensure the latter works by the time it is called, possibly usingdefine_methodto avoid having to go through all of this again the next time you access the same method name.That method might look like this:
I can’t tell what’s going on in your method as the structure is not clear and it seems highly dependent on the context of your application.
From a design perspective you might find it’s easier to make a special-purpose wrapper class that encapsulates all of this functionality. Instead of calling
object.attribute_nameyou’d callobject.dynamic_attributes.attribute_namewhere in this casedynamic_attributesis created on demand:When that object is initialized it will pre-configure itself with whatever methods are required and you won’t have to deal with this method missing stuff.