Array Attribute for Ruby Model

Array Attribute for Ruby Model

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.

Ruby model with an array as an attribute

I can see two initial approaches, namely define a class to represent your key,value pair or just use a hash to represent each data item. The advantage of a separate class is that you can extend it in the future, if for example you wanted to provide the exact value in a chart where you were rounding to the nearest 100k.

The following code shows three classes which together will do what you want

class Chart

attr_accessor :title, :series

def initialize(title = nil, series = [])
@title, @series = title, series
end

def show
puts title
@series.each do |ser|
puts "\t#{ser.legend} (#{ser.units})"
ser.data.each do |item|
puts "\t\t#{item}"
end
end
end

end

class Series

attr_accessor :legend, :units, :data

def initialize(legend = nil, units = nil, data = [])
@legend, @units, @data = legend, units, data
end

end

class DataItem
attr_accessor :key, :value

def initialize(key, value)
@key, @value = key, value
end

def to_s
"#{key}, #{value}"
end

end

Running this as follows :-

c = Chart.new("Sweet sales by Quarter")
c.series << Series.new("Bon-Bons", "£000",
[ DataItem.new("Q1", 220),
DataItem.new("Q2", 280),
DataItem.new("Q3", 123),
DataItem.new("Q4", 200)]
)
c.series << Series.new("Humbugs", "£000",
[ DataItem.new("Q1", 213),
DataItem.new("Q2", 254),
DataItem.new("Q3", 189),
DataItem.new("Q4", 221)]
)

c.show

Produces the following output


Sweet sales by Quarter
Bon-Bons (£000)
Q1, 220
Q2, 280
Q3, 123
Q4, 200
Humbugs (£000)
Q1, 213
Q2, 254
Q3, 189
Q4, 221

If you wanted to take the Hash approach then you would no longer need the DataItem class and you could instantiate a new Series with code like this

c = Chart.new("Sweet sales by Quarter")
c.series << Series.new("Bon-Bons", "£000",
[ { "Q1" => 220}, {"Q2" => 280}, {"Q3" => 123}, {"Q4" => 200}]
)

The show method of Chart would then look like this

  def show
puts title
@series.each do |ser|
puts "\t#{ser.legend} (#{ser.units})"
ser.data.each do |item|
item.each_pair {|key, value| puts "\t\t#{key}, #{value}" }
end
end
end

Ruby class with array attribute

The type of a variable doesn't matter in Ruby.

attr_accessor just creates getter and setter methods that set and return instance variables; @countries in this case. You can set the instance variable to your array, or use the setter:

class User
attr_accessor :countries

def initialize
@countries = %w[Foo Bar Baz]
# Or...
self.countries = %w[Foo Bar Baz]
end
end

> puts User.new.countries
=> ["Foo", "Bar", "Baz"]

Personally I prefer using the instance variable instead of self.xxx; it's too easy to forget the self. bit and you end up setting a local variable, leaving the instance variable nil. I also think it's ugly.

If the countries won't be changing between instances, why not a constant?

Edit/Clarification

Tadman's point is well-taken, e.g., this diatribe on state. The circumtances under which I don't care about that are limited to small, self-controlled, stand-alone classes. There are inherent risks in making those assumptions, the level of those risks is project-dependent.

Can a Rails attribute be an array?

Yes indeed. Here is a sample migration to add an integer and a string array to a model:

class AddToBooks < ActiveRecord::Migration
def change
add_column :books, :category_ids, :integer, array: true, default: [], null: false
add_column :books, :subjects , :text , array: true, default: [], null: false
end
end

For arrays of strings, use :text as the type.

I'd strongly recommend using an empty array instead of a null to indicate "no data", and this migration ensures that.

where for querying an array attribute in rails model

Depends on what "roles" means:

If it is an association as in

class User < ActiveRecord::Base
has_many :roles
end

Then you look up the role and fetch all users:

Role.find_by_name("admin").users

or roles is just a column and it is serialised, in which case you can instantiate all users (slow)

User.all.select { |u| u.roles.include? "admin" }

Or query the database directly which is more complex and depends on the adapter.

My opinion: I would avoid using serialised columns, when relations can do. They are cumbersome in every way: Forms, Searches, Selects...

Get array of attributes from array of database items

You can grab an array of specific class attributes using ActiveRecord's pluck method.

Category.pluck(:name)

If you have a regular array of Category objects, then you can use the Array method map

Category.map(&:name)

Both will yield an array containing the value of the name attribute of each Category.



Related Topics



Leave a reply



Submit