constant enums in rails models and database - ruby-on-rails

I want to find a clean way to maintain enumerations in both the database and model.
Say I have an ActiveRecord class with a constant enumeration of types
class Foo < ActiveRecord::Base
TYPES = {1 => :hello, 2 => :hi}
end
I've gone further and written an Enumeration class so I can do Foo::TYPES.HELLO to get 1, or Foo::TYPES.HI to get 2.
I want these types in the database so I can do joins. I'm currently creating a FooType model, and having Foo belongs_to :foo_type, so Foo will have a foo_type_id field.
class Foo < ActiveRecord::Base
belongs_to :foo_type # 1 -> hello, 2 -> hi
end
However this is inconvenient because:
Test environments break unless I seed the test db every time. Types are assumed to be constant so they are directly used in code. This is probably the biggest pain
If I do a static enumeration pulling foo_type_ids from the db into the model, this also breaks in tests
If I add a new type, I have to reflect this in every database
Each environment needs to be seeded with types

I used the enumerated_attribute gem from https://github.com/jeffp/enumerated_attribute
This lets you define the enumeration and its values in the model. It's really designed to make dealing with them easier on the Rails / UI side (not so much database), but it will save you the hassles of maintaining a separate model for your enumeration and ensure consistency.
It will work in test environments, you won't need to update your DB when you add a new value and you won't need to seed the database. Just create a new column for the value of the enumeration and the gem does the rest. It stores the value as a string so it's pretty easy to manipulate from within the database if that's what you need.

Having enumerators in your code just make your code readable, nothing other than that. Rather than having an integer (which doesn't have readability for developers), you use come kind of String to represent that value
TYPES = {1 => :hello, 2 => :hi}
I dont think you can use AR relations like 'belongs_to' inside a normal class (non AR)
I think what you need is two tables
table -
foos
id
type
foo_type_id
foo_types
id
foo_type
then U can have the AR
class Foo < ActiveRecord::Base
belongs_to :foo_type
end
class FooType < ActiveRecord::Base
has_many : foos
end
If you are going for DB solution (other than having static enumerators), you dont have an option but to add types to each env.
HTH
cheers
sameera

Related

Hierarchy within one active record table?

I've been banging my head against this for over a day so here we go:
I have a User model. But there are 3 types of user (more actually, but let's get this done). Let's say there is Talent, Managers, and Directors. But they are all users. A manager has many Talents, Talent has one Manager. Director has many Managers, Manager has one Director. Talent has one Director through Manager.
Obviously, with three separate models, this would be trivial. But they are all users and it is necessary to keep them in the User model.
What is the easiest way of doing this? If I have to use a gem, so be it, but I'd rather not...
Thank you!
Single Table Inheritance is most fitting to your problem description:
Single table inheritance Active Record allows inheritance by storing
the name of the class in a column that by default is named “type” (can
be changed by overwriting Base.inheritance_column). This means that an
inheritance looking like this:
class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end
When you do Firm.create(name: "37signals"), this record will be saved in
the companies table with type = “Firm”. You can then fetch this row
again using Company.where(name: '37signals').first and it will return
a Firm object.
Be aware that because the type column is an attribute on the record
every new subclass will instantly be marked as dirty and the type
column will be included in the list of changed attributes on the
record. This is different from non Single Table Inheritance(STI)
classes:
Company.new.changed? # => false
Firm.new.changed? # => true
Firm.new.changes # => {"type"=>["","Firm"]} If you don't have a
type column defined in your table, single-table inheritance won't be
triggered. In that case, it'll work just like normal subclasses with
no special magic for differentiating between them or reloading the
right type with find.

belongs_to relationship does not work until I save -- am I 'doin it wrong'?

Probably this is just the way Rails works, but I want to double-check because I am new to it and I'm not sure if I am missing something.
I am creating a fairly complicated set of model that has a number of nested has_many/belongs_to relationships. Initially, I tried just using the create() method, but since that initiates a database connection every single time it ended up taking a lot longer than it ought to. So instead I use new() to create all of the objects, use << to add the nested models to higher models, and then at the very end I do a save() on the top-level model.
This all works, but I noticed that the belongs_to relationship does not work until I do the save. In other words:
class Foo < ActiveRecord::Base
has_many :bars
end
class Bar < ActiveRecord::Base
belongs_to :foo
end
f = Foo.new
b = Bar.new
f.bars << b
puts "It's nil!" if b.foo == nil #yup, it's nil
f.save
puts "It's good!" if b.foo != nil #yup, it's good
It makes sense I guess; I mean, it can't set up the id field to point to its Foo until that Foo actually has an id, and that won't happen until I save. It's not a big deal, I only noticed it because the way I had initially written the (rather complicated) initialization, one of the lower-level models was calling up to get some information from its grandparent, and that wasn't working. The code's cleaner now that I took that out anyway, but I can't help this nagging feeling that I'm doing it totally the wrong way, and there should never be a situation where the has_many relationship is temporarily broken until I actually save to the database.
Anyone want to set my mind at ease and/or tell my the "right" way of doing it?
You just answered your own question with this line:
It makes sense I guess; I mean, it can't set up the id field to point to its Foo until that Foo actually has an id, and that won't happen until I save
Since belongs_to works off of a foreign key on Bar matching up to the id on Foo, you can't have a relationship until that ID exists.
As a side note, you can do
f.bars.new
To set up the relationship automatically without having to do f.bars << b
This is correct; until there's an ID in the Foo, there's none for the Bar to use to get its Foo.

Abstracting ActiveRecord Attributes

What is the best way to abstract an ActiveRecord attribute without further normalizing the database?
For example, let's assume a database table named addresses with a column zip_code and a method to determine if the zip code is valid:
class Address < ActiveRecord::Base
def zip_code_valid?
..
end
end
I would prefer to have:
class Address < ActiveRecord::Base
..
end
class ZipCode
def valid?
..
end
end
and when I execute Address.find(1).zip_code, it returns a ZipCode vs. a string. I would prefer not to normalize the database by creating a table called zip_codes. The example is hypothetical, and I currently do not have a real world example of this; I simply want to know how I could potentially do this.
Thank you.
I'm not sure why you'd want to do this for ZipCode as you discussed, but to answer your question, you should consider using Rails Aggregations.
Here's the documentation :
http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html
If you have specific questions about things you want to accomplish, let me know and I can try to answer those specific questions.
I don't think abstracting the zip code out into a class makes sense if you're not going to do the same with the street, house number, city, etc. You already have it as a separate column in the Address table, so it doesn't make sense to store it in its own class (from an ActiveRecord point of view). Alternatively, if you store zip codes in their own table (and hence class), what are you gaining? In my opinion it's going too far to keep a single attribute in a separate class/table if it makes sense as to be part of the aggregate that it's currently in.

Rails Single Table Inheritance using Foreign Key (ID)

I have to model an association structure and the association is divided into divisions/subdivisions/sections etc. So I've created a simple Entity-Attribute Model:
I'd like to use rail's single-table-inheritance but it seems like this works only if the type column is a string. My question is how to achieve this with my approach? Since I'm using a foreign key as "type" I'd have to query the "type name" first. Has anybody done this before?
I would recommend adding a String "type" attribute to your structure table to satisfy single table inheritance, and to add before_save callbacks to set correct values on either table.
Say you have a StructureType with name "Basic". In Rails that means you'd want to have class hierarchy:
Structure < ActiveRecord::Base
BasicStructure < Structure
In Structure class add:
before_create :set_structure_type_fk
def set_structure_type_fk
self.structure_type = StructureType.find_by_name(\
self.class.name.gsub(/Structure/, '').downcase)
end
Hope this helps.
Note that this approach means that StructureType.name should be immutable: once created it should never be changed, except by a database migration that updates both tables correspondingly.
K

RoR: has_one "or the other"? (Or, polymorphism without the inheritance.)

Hey all, I have something of an interesting requirement for my project. I need a has_one relationship where it is either one class or the other, but without inheritance. I could get away with inheritance if it is the only way, but the two associate records have completely different data and aren't related at all.
What I need to figure out is something like the following.
# 1. Foo never belongs to anything.
# 2. Foo MUST have one assigned sub-record for validity.
# 3. Foo can only have either Bar or Baz assigned.
# 4. Bar and Baz have only ONE common property, and aren't
# related in either data or implementation.
class Foo < ActiveRecord::Base
# Attributes: id, name, value
has_one :assignment, :foreign_key => 'assigned_to', :readonly => true
# Could really use an :object_type for has_one here...
end
class Bar < ActiveRecord::Base
# Attributes: name,...
end
class Baz < ActiveRecord::Base
# Attributes: name,...
end
Where Foo has one assignment, of type either Bar or Baz; they only share one common column, so perhaps I can make a parent object from that. However, if I make them inherit from a common object (when the data they contain really is oranges and apples) must I make a table for the record? Can I perhaps get away with it if the record is an abstract record, but the children aren't?
I suppose by now you can see my difficulty. I'm rather new to RoR but loving it so far. I'm sure there's a way around this, but I'll be darned if I can't figure out what it is.
You're trying to model something that doesn't fit the relational database paradigm. All references in SQL have one origin and one target.
FWIW, Polymorphic Associations is also an anti-pattern because it breaks this rule. It should be a clue that it's a broken design when the documentation says you must forgo a referential integrity constraint to make it work!
You need Foo to have two has_one relationships: one to Bar and one to Baz. Then implement some class logic to try to ensure only one reference is populated in any instance of Foo. That is, of the references to Bar and Baz, one must have a value and the other must be nil, but this is something for your code to check for and enforce.
Perhaps one way to do this, is to create to has-one associations in Foo, for Bar and Baz. Then create a method called assignment and assignment= which can be the sole way to access Bar and Baz. You can check which of the two has_ones is not nil in the get method and return that one. In the assignment method, you can-check what is the type of the variable passed in and set the correct has-one relationship to that object and set the other to nil. That ought to cover all your bases without being too complicated.

Resources