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_many
s, 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
Ruby: Put Request with JSON Body
Use Multiple Versions of Rubygems with Rvm
Asserting That a Particular Exception Is Thrown in Cucumber
Best Way to Monitor for Completion of a Sidekiq Job
Token Based Authentication for Rails JSON APIs
Where Are the Gems When Ruby Compiled Manually in MAC Os X 10.6.8
Gem Dependencies Versions Meaning
Parsing Simple Xml with Nokogiri
How to Add a Method to the Global Scope in Ruby
What's the Difference Between Controllers and Actions in Ruby on Rails
Is Autoload Thread-Safe in Ruby 1.9
Heroku Gem Not Working with Rvm
How to Stop the Rails Debugger for the Current Request
Binary String Literals in Ruby 2.0
Declaring Instance Variables Iterating Over a Hash!
How to Call Rake Tasks That Are Defined in the Standard Rakefile from an Other Ruby Script
Specifying a Layout and a Template in a Standalone (Not Rails) Ruby App, Using Slim or Haml