Friday, February 11, 2011

Ruby on Rails: Aggregating several columns into an Array

I'm developing a Ruby on Rails app where one of my database tables has 10 columns (pile_1 through to pile_10). It would be convenient to access these columns in the model as a 10-element Array.

It feels like I should be able to coerce composed_of into doing what I want, but I can't figure out how. Can anyone enlighten me, or suggest a better tactic?

  • Would

    def piles
        (1..10).map{ |num| self[ "pile_#{ num }"]}
    end
    

    not suffice?

    Chris : Hah. That's neat. In this case, yes, that should do all I need, because I don't expect to need to /write/ to piles_n at any point. Presumably Model.[]() is a method of ActiveRecord::Base?
    tadman : For clarity you might want to use read_attribute("pile_#{num}") instead, but the square-brackets accessor is functionally similar.
    From Farrel
  • Since you have the power to change the schema, you should. Storing an array as separate columns in a table is denormalized. Whether or not your schema is normalized might not matter to you, but your current difficulty is a direct result of a denormalized schema.

    What you ought to do is to create a new table, piles, like so. I'll use postgres syntax, since that's what I know. I don't know the name of the table which currently contains all of the piles* columns, so I'll call it "foo":

    create table piles (
      id serial primary key,
      foo_id int not null references foo(id),
      value text not null,
    );
    

    Every column you now have in foo exists instead as a row in piles. In the model for piles, add:

    belongs_to: foo
    

    and in the model for foo, add:

    has_many: piles
    

    In your controller, once you have a foo in hand, you can access its piles with foo.piles

    Chris : Well, I am doing this somewhat to teach myself RoR, so I should probably teach myself properly and do it like this (although Farrel's solution is tempting in simplicity). What would be the best way to grab input values for the Piles objects in a Foo creation page, in this case? form_for(@foo) presumably won't Just Work; do I want a loop over <% for @pile in @foo.piles %> creating
    s within my form_for? I can't figure out the right syntax. ... should I be creating a new question for this?
    Chris : Having futzed about with a lot of plausible variants, I can't get this to work, and it seems complex. I'm going to mark this answer as Correct (although I may end up resorting to Farrel's less architectural simple solution), and ask a new question about forms.

0 comments:

Post a Comment