Dynamic # of Attributes for a Model (key/val table) Get/Set?

I’m working on a simple product catalog. Products have user defined
attributes, such as: color, size, weight. More attributes can be added
at any time. Since these attributes are not known ahead of time, they
cannot be created as columns.

My table structure (simplified):

products

id
name

attrs

id
name

product_attrs

id
attr_id
value

And some sample code:

Create some attributes

1 - size

2 - color

3 - weight

[‘size’,‘color’,‘weight’].each do |attr|
Attr.create( { ‘name’ => attr } )
end

Create a new product

p = Product.new( { ‘name’ => ‘Widget’ } )

Assign some attributes

p.product_attrs << ProductAttr.create( { ‘attr_id’ => 1 } ) # size
p.product_attrs << ProductAttr.create( { ‘attr_id’ => 2 } ) # color
p.product_attrs << ProductAttr.create( { ‘attr_id’ => 3 } ) # weight

Output

p.product_attrs.collect{ |product_attr| product_attr.name } => [size,
color, weight]

Product ‘p’ now has 3 ProductAttributes associated with it (which in

turn are associated with the three attributes [size, color, weight]
created above)

Now that the product has associated attributes – I’d like an easy way
to get and set their values. Is there any way to do something like:

p.product_attrs[‘color’] = ‘red’
-or-
p.product_attrs.color = ‘red’

Right now, the only way I’ve gotten this to work is with:

p.product_attrs.find( Attr.find_by_name(‘color’) ).value = ‘red’

…which isn’t all that nice looking. I could probably hack something
up, but I figure I’d ask first.

Maybe I’m even on the wrong path here and somebody else has a clue as to
how I can organize this data all together. Is there an ideal way of
handling a dynamic number of Model attributes with (or without) using a
key/value type table structure as I describe above (The ‘attributes’
table defines the key, the ‘product_attributes’ defines the value).

Best,
J

On 12/5/06, J. Riddel [email protected] wrote:

I’m working on a simple product catalog. Products have user defined
attributes, such as: color, size, weight. More attributes can be added
at any time. Since these attributes are not known ahead of time, they
cannot be created as columns.

Assign some attributes

p.product_attrs << ProductAttr.create( { ‘attr_id’ => 1 } ) # size
p.product_attrs << ProductAttr.create( { ‘attr_id’ => 2 } ) # color
p.product_attrs << ProductAttr.create( { ‘attr_id’ => 3 } ) # weight

…which isn’t all that nice looking. I could probably hack something
up, but I figure I’d ask first.

I would start with creating some collection class, that will allow using
indexers (or even through method_missing) to access product’s custom
attributes. Something like that:

my_product.attrs[:size] or my_product.attrs.size

It is easy: instantiate collection class and pass product id to it. Then
when you try to access some attributes, just do ProductAttr.find( yada
yada
) to get attribute value.

After that you could think even further and implement custom attributes
interception in Product’s method_missing (to allow my_product.size).

J. Riddel wrote:

I’m working on a simple product catalog. Products have user defined
attributes, such as: color, size, weight. More attributes can be added
at any time. Since these attributes are not known ahead of time, they
cannot be created as columns.

Let me suggest I a plugin I have created for this purpose.

https://rubyforge.org/projects/flex-attributes/

The plugin is still in “beta” since the application it is made for has
not been rolled out to production but it seems to work well.

Basically your model looks something like:

class Product < ActiveRecord::Base
has_flex_attributes
end

Then you can just add/remove attributes as you need. If the attribute
exists on the model it will be stored there. If it does not then it will
be stored in a “thin” table called product_attributes.

There are also hooks so you can define what attributes are valid.
Finally the plugin works with
acts_as_versioned/acts_as_versioned_association.

The docs are a little scattered right now because the product it was
created for is still in development but you are welcome to use what I
have.

Eric

J. Riddel wrote:

Let me suggest I a plugin I have created for this purpose.

https://rubyforge.org/projects/flex-attributes/

I came up with a solution using MethodMissing, but your plugin looks to
be what I need. However, there is nothing to download from that link?
Can you add the files?

J

Nevermind, it was in SVN. RubyForge just showed that it had 0 CVS and
SVN commits.

Let me suggest I a plugin I have created for this purpose.

https://rubyforge.org/projects/flex-attributes/

I came up with a solution using MethodMissing, but your plugin looks to
be what I need. However, there is nothing to download from that link?
Can you add the files?

J

Eric –

Thanks a lot. This works great. Slightly different than what I came up
with (in 10 lines with MethodMissing), but it “feels” better and
actually makes my relationship less complicated. Thanks a lot. Great
timing.

J

J. Riddel wrote:

Thanks a lot. This works great. Slightly different than what I came up
with (in 10 lines with MethodMissing), but it “feels” better and
actually makes my relationship less complicated. Thanks a lot. Great
timing.

Glad it is working for you. The plugin started out simple but I needed
to also support acts_as_versioned (so the related attributes are
versioned) which made the plugin much more complicated.

If you (or anybody that uses this plugin) get any problems send me a
message. It is fairly new (the site it was developed for hasn’t even hit
production yet) so there may be some issues with it still.

Eric

Good idea, I’ll take a look at it as well.