I’ve been finding myself using YAML a lot for silly enums — maybe because I’m lazy, but mostly because there is no need to manage them via a database table. Here is just a simple example — I need to associate a region and division to a client. These will not change much and are simple enough to keep as a Yaml array until more logic can be brought to the table. Here is how that YAML file looks:
# RAILS_ROOT/config/app.yml
regions:
- 'northeast'
- 'southeast'
- 'west'
- 'central'
- 'emerging east'
- 'emerging west'
- 'opportunity east'
- 'opportunity west'
- 'opportunity national east'
- 'opportunity national west'
divisions:
<%= (1..71).map{ |i| "Region #{i}" }.to_yaml %>
Loading it up using an initializer:
# RAILS_ROOT/config/initializers/load_app_config.rb
fdata =File.open(File.join(RAILS_ROOT, "config", "app.yml")).read
APP = YAML::load(ERB.new(fdata).result(binding)).symbolize_keys
Now we have our APP hash chock full o’ YAML goodness. My only problem with this is that I might want to eventually put this data set into a table and it is a pain to hunt and search for where the application references APP[:regions] and so on sooo: time to objectize.
I decided that since they are stupid simple objects they can all have the same parent: YamlArrayObject:
# RAILS_ROOT/app/models/yaml_array_object.rb
# or in lib
class YamlArrayObject
attr_reader :name
def id
0
end
def initialize(name)
@name = name
end
def to_s
name
end
def display_name
to_s.titleize
end
def <=>(b)
name <=> b.name
end
end
Simple enough. Now we just inherit and overload as appropriate.
# RAILS_ROOT/app/models/region.rb
class Region < YamlArrayObject
def self.all
APP[:regions].map{ |r| Region.new(r) }
end
end
# RAILS_ROOT/app/models/division.rb
class Division < YamlArrayObject
def <=>(b)
name.to_i <=> b.name.to_i
end
def self.all
APP[:divisions].map{ |d| Division.new(d) }
end
end
This is all Jim Dandy right now since we can populate select boxes using cleaner code (bonus: won’t break if you decide to add Divisions to a database table)
# i like
<%= f.select :division, Division.all.sort.map{ |d| [d.display_name, d] } -%>
# better than
<%= f.select :division, APP[:divisions].sort{|a,b| a.to_i <=> b.to_i}.map{ |d| [d.titleize, d] } -%>
The only real issue is that calling Client.first.region still returns a string, which is bad since we want to keep our code as ambiguous as possible. The last step is to override the Client class’ methods for region and division (and whatever else we want to use):
# RAILS_ROOT/app/models/client.rb
def division
Division.new(@attributes['division']) unless @attributes['division'].blank?
end
def region
Region.new(@attributes['region']) unless @attributes['region'].blank?
end
Now in our views (client/show) we can simply call @client.region.display_name or whatever and not have to change the code when we finally get around to DBing those object.
(There might be better ways to do this that I’m missing, but this seems pretty okay for my needs right now.)
Recent Comments