Array Attribute for Ruby Model
Asked Answered
S

5

33

Is it possible to create an attribute for a class that is an array? I tried reading this but I didn't get much out of it. I want to do something like this:

class CreateArches < ActiveRecord::Migration
  def change
    create_table :arches do |t|
      t.string :name
      t.array :thearray
      t.timestamps
    end
  end
end

such that when I call .thearray on an instance of Arch I get an array that I can add new elements to.

ruby-1.9.2-p290 :006 > arc = Arch.new
ruby-1.9.2-p290 :007 > arc.thearray
 => [] 
Stealing answered 11/11, 2011 at 17:32 Comment(0)
C
14

While you can use a serialized array as tokland suggested, this is rarely a good idea in a relational database. You have three superior alternatives:

  • If the array holds entity objects, it's probably better modeled as a has_many relationship.
  • If the array is really just an array of values such as numbers, then you might want to put each value in a separate field and use composed_of.
  • If you're going to be using a lot of array values that aren't has_manys, you might want to investigate a DB that actually supports array fields. PostgreSQL does this (and array fields are supported in Rails 4 migrations), but you might want to use either a non-SQL database like MongoDB or object persistence such as MagLev is supposed to provide.

If you can describe your use case -- that is, what data you've got in the array -- we can try to help figure out what the best course of action is.

Cankered answered 11/11, 2011 at 17:51 Comment(7)
Ah ok, my array will be of strings only. What do you think?Stealing
What does the array represent? Why do you have it in the first place?Cankered
I originally wrote that I didn't know of any SQL database that stored arrays or hashes natively. Turns out Postgres deals with both of these types natively, and Rails 4 supports them in migrations and ActiveRecord. Just one more reason to use Postgres. :)Cankered
+1 for composed_ofFairman
@Fairman Depends on the use case. There's not enough information here to know.Cankered
@MarnenLaibow-Koser It's just that I discovered composed_of and found it really neat (for money attributes for instance). And about your answer, Postgres supports SQL fieldsFairman
@Fairman Yes, composed_of is great for things like money. It's not clear whether it's the right thing here, however. And about Postgres arrays: see #8098250 :)Cankered
S
54

Create a model with a text field

> rails g model Arches thearray:text
  invoke  active_record
  create    db/migrate/20111111174052_create_arches.rb
  create    app/models/arches.rb
  invoke    test_unit
  create      test/unit/arches_test.rb
  create      test/fixtures/arches.yml
> rake db:migrate
==  CreateArches: migrating ===================================================
-- create_table(:arches)
   -> 0.0012s
==  CreateArches: migrated (0.0013s) ==========================================

edit your model to make the field serialized to an array

class Arches < ActiveRecord::Base
  serialize :thearray,Array
end

test it out

ruby-1.8.7-p299 :001 > a = Arches.new
 => #<Arches id: nil, thearray: [], created_at: nil, updated_at: nil> 
ruby-1.8.7-p299 :002 > a.thearray
 => [] 
ruby-1.8.7-p299 :003 > a.thearray << "test"
 => ["test"] 
Steeple answered 11/11, 2011 at 17:44 Comment(1)
Note that this method preserves symbols in arrays, whereas using PG's array column type does not.Nishanishi
C
14

While you can use a serialized array as tokland suggested, this is rarely a good idea in a relational database. You have three superior alternatives:

  • If the array holds entity objects, it's probably better modeled as a has_many relationship.
  • If the array is really just an array of values such as numbers, then you might want to put each value in a separate field and use composed_of.
  • If you're going to be using a lot of array values that aren't has_manys, you might want to investigate a DB that actually supports array fields. PostgreSQL does this (and array fields are supported in Rails 4 migrations), but you might want to use either a non-SQL database like MongoDB or object persistence such as MagLev is supposed to provide.

If you can describe your use case -- that is, what data you've got in the array -- we can try to help figure out what the best course of action is.

Cankered answered 11/11, 2011 at 17:51 Comment(7)
Ah ok, my array will be of strings only. What do you think?Stealing
What does the array represent? Why do you have it in the first place?Cankered
I originally wrote that I didn't know of any SQL database that stored arrays or hashes natively. Turns out Postgres deals with both of these types natively, and Rails 4 supports them in migrations and ActiveRecord. Just one more reason to use Postgres. :)Cankered
+1 for composed_ofFairman
@Fairman Depends on the use case. There's not enough information here to know.Cankered
@MarnenLaibow-Koser It's just that I discovered composed_of and found it really neat (for money attributes for instance). And about your answer, Postgres supports SQL fieldsFairman
@Fairman Yes, composed_of is great for things like money. It's not clear whether it's the right thing here, however. And about Postgres arrays: see #8098250 :)Cankered
S
8

Migration:

t.text :thearray, :default => [].to_yaml

In the model use serialize:

class MyModel
  serialize :thearray, Array
  ...
end

As Marnen says in his answer, it would be good to know what kind of info you want to store in that array, a serialized attribute may not be the best option.

[Marten Veldthuis' warning] Be careful about changing the serialized array. If you change it directly like this:

my_model.thearray = [1,2,3]

That works fine, but if you do this:

my_model.thearray << 4

Then ActiveRecord won't detect that the value of thearray has changed. To tell AR about that change, you need to do this:

my_model.thearray_will_change!
my_model.thearray << 4
Setscrew answered 11/11, 2011 at 17:37 Comment(2)
It will be an array of strings. Is serialize the way to go?Stealing
@Stealing Almost certainly not. See my answer.Cankered
V
3

If using Postgres, you can use its Array feature:

Migration:

add_column :model, :attribute, :text, array: true, default: []

And then just use it like an array:

model.attribute # []
model.attribute = ["d"] #["d"]
model.attribute << "e" # ["d", "e"]

This approach was mentioned by Marnen but I believe an example would be helpful here.

Violaceous answered 29/1, 2019 at 11:24 Comment(1)
Note that symbols are not preserved when using this methodNishanishi
R
0

Rails 6+

In Rails 6 (and to a lesser degree Rails 5) you can use the Attribute API which will allow you to create a typed, "virtual"/non-db backed column and even a default attribute. For example:

attribute :categories, :jsonb, array: true, default: [{ foo: 'bar' }, { fizz: 'buzz' }]

Which results in:

Example.new


#<Example:0x00007fccda7920f8> {
            "id" => nil,
    "created_at" => nil,
    "updated_at" => nil,
    "categories" => [
                      [0] {
                        "foo" => "bar"
                      },
                      [1] {
                        "fizz" => "buzz"
                      }
                    ]
}

Note that you can use any type, but if it's not already available, you'll have to register it. In the above case, I use PostgeSQL as the database and which has already registered :jsonb as a type.

Rapturous answered 24/11, 2020 at 4:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.