Really useful software project management and source code hosting.    tell me more...
BROWSE: projects   /   users


Really Useful Social coding!

Codaset is an open system, so you can browse and search through all the open source projects, and check out what your friends are coding. Follow them, befriend them, and fork their code; quickly and easily.
Every single open source project you create is free, so come on and use Codaset at no cost. Your first private or semi-private project is also free. Read more about what it costs after that.
posted by Joel Moss
2 months ago

Create schema-less, dynamic model attributes

Tags: tag_greenruby on rails tag_greenplugin tag_greendynamic attributes

So while working on the payment system for Codaset, I wanted to provide a little future-proofing in the form of allowing more than one method of payment. Only credit and debit cards will be supported at first, but I would like to allow payments via other methods later down the line, such as Paypal, etc. But I came across an issue that related to the fact that the database fields that would be required for credit card and paypal payments, would not be the same.

For example, for credit card payments, I would need the following fields in my payment_methods table:

create_table :payment_methods do |t|
  t.integer :user_id
  t.string :card_number, :card_holder
  t.date :start_date, :end_date
end

But for Paypal payments, I wouldn't need any of the above fields:

create_table :payment_methods do |t|
  t.integer :user_id
  t.string :paypal_email
end

Now I could simply create all these fields in one table, and that would work fine, but not so elegant. I wanted a nice easy way to create and save any fields without having to create a database table column for each one. If more payment methods come along, I don't want to have to modify the database schema, by adding more columns.

So I came up with a nice little Rails plugin that lets me do exactly that.

Introducing Dynamic Attributes

Dynamic Attributes is a Rails plugin that lets you create dynamic attributes on any ActiveRecord model, and saves them in a schema-less fashion within a single table column. I can now save a model attribute in the usual Rails way, using any of its getters and setters, without having to actually create a table column for each attribute. And I also don't want to create a new record for each new attribute.

All I need is a single extra column in my PaymentMethod table called dynamic_attributes, and tell my model that it has dynamic attributes:

PaymentMethod < ActiveRecord::Base
  has_dynamic_attributes
end

I don't even need to tell the model the names of my dynamic attributes, and I can of course mix my dynamic attributes with normal attributes.

So now I can create a new PaymentMethod record with two dynamic attributes: card_holder and card_number:

@payment_method = PaymentMethod.new :card_holder => 'Joel Moss', :card_number => '4111111111111111'
@payment_method.save

And I can call any of those dynamic attributes just like any other model attribute:

@payment_method.card_holder
=> 'Joel Moss'

@payment_method.card_number
=> '4111111111111111'

And that is pretty much it! All my dynamic attributes are indeed dynamic, and are saved in one single database column as a YAML hash. If I want to get a hash of all my dynamic attributes, I can do this:

@payment_method.dynamic_attributes
=> { :card_holder => 'Joel Moss', :card_number => '4111111111111111' }

The plugin also supports defined dynamic attributes to prevent just any old attributes being created, and you can also specify a custom database column name to save your dynamic attributes in. Read the ReadMe for more info on that, and full details of how to use the plugin with your Rails apps.

You can checkout the source on Codaset at http://codaset.com/joelmoss/dynamic-attributes. Please play with it, and let me know your thoughts. And please feel free to fork the source; your contributions are most welcome.


dmitry left a comment 2 months ago

my plugin do the similar things, but offers type checking, type converters and good STI support - http://github.com/dmitryelastic/serialized_attributes

Joel Moss left a comment 2 months ago

Nice! But I didn't want to have to specify the attributes to be used as dynamic, which my plugin lets you do. I also have no current need for associations and/or STI support.

Hey, but good job. Nice to know I'm not the only one who needed something like this.

dmitry left a comment 2 months ago

ok, similar ideas, different purposes

Andris left a comment 2 months ago

Young'uns these days.. (facepalm). I feel kind of old, thinking that a new table for each new payment method would be the proper solution. There were times when data normalization and access to other applications (implemented in another language or framework) was important.

Haig Evans-Kavaldjian left a comment 2 months ago

Also, suppose you want to query on your dynamic attributes. Isn't that going to be terribly expensive? Won't you need to scan your entire table, loading each row/object, unpacking the dynamic attributes, and then testing them? Sounds like a performance nightmare.

Joel Moss left a comment 2 months ago

@Haig Evans-Kavaldjian: Yes you are most likely correct, but querying on dynamic attributes won't work anyway, as I do not have a need for it. I only need to be able to use it as a generic data store for each record. But if this is soemthing you need, please go ahead and fork the project and commit your changes. I would be happy to roll it into my project.

Haig Evans-Kavaldjian left a comment 2 months ago

Aha, there's the rub! Time, time, never enough time! If I do find the time, though, I will be sure to share. Thanks for the offer to roll it back out. My first thought is that you could use a separate table relating existing objects to key/value pairs for each dynamic property attached to those objects. Pared down, I think you'd need four columns: primary object type, primary object id, the dynamic property name/key, and the dynamic property value. But this is a simplification that assumes both keys and values will be simple strings, when I can easily imagine wanting at the least the values to be other types, including integers and dates at least. I remember reading an article a year or two back in PHP Architect magazine, which talked about a general solution to this problem, maybe the January 2008 issue. Sadly I no longer have a copy and they haven't made their back issues available freely on the web to date. I'll cross my fingers and put this on my to do list. Thanks again.

Walter McGinnis left a comment 2 months ago

I've done the same thing for quite some time now (3+ years), but unfortunately I made the mistake of using XML within what I called "extended_content" column.

Originally I chose XML as I thought I would simply pull the whole thing out and present it in complete form. In practice, I pulled it apart. If I was to do over again (and I couldn't use a NoSQL solution), I would opt for storing Marshalled hash in the column. Marshalling is way faster than YAML from what I have read.

However, I wouldn't even bother with that now. I would use something like a hash in Mongodb with MongoMapper instead. It really is a better solution for the "custom fields" problem.

Cheers,
Walter

Joel Moss left a comment 2 months ago

I was going to use MongoDB, but I already use MySQL, and wanted to continue using MySQL while still be able to take advantage of ActiveRecord's associations, etc. So Dynamic Attributes fit the bill nicely.

WP Themes left a comment about 1 month ago

Good post and this mail helped me alot in my college assignement. Gratefulness you seeking your information.

please let us know who you are

or better still, login here...

 


Most commented posts...