Rails Single Table Inheritance - What is the best way to explicitly set type? - ruby-on-rails

I am using single table inheritance in my rails application, and want to explicitly set the type of an instance.
I have the following;
class Event < ActiveRecord::Base
class SpecialEvent < Event
which is implemented through single table inheritance.
SpecialEvent.new works as expected, but I want to be able to do things like
Event.new(:type => 'SpecialEvent')
So I can create different sub_types easily in the application.
However this doesn't work and seems to set :type to nil, not the value I set it to; I suspect this is because by calling Event.new it is overwriting the :type argument.
Has anyone got a good way of doing this?

If you're trying to dynamically instantiate a subtype, and you have the type as a string, you can do this:
'SpecialEvent'.constantize.new()

from "Pragmatic - Agile Web Development with rails 3rd edition", page 380
There’s also a less obvious constraint (with STI). The attribute type
is also the name of a built-in Ruby method, so accessing it directly
to set or change the type of a row may result in strange Ruby
messages. Instead, access it implicitly by creating objects of the
appropriate class, or access it via the model object’s indexing
interface, using something such as this:
person[:type] = 'Manager'
man, this book really rocks

No, I want to create instances of
sub-types, where I want to
programmatically determine which
sub_type they are
– HermanD
You could use a factory pattern, although I have heard recently that people frown on the overuse of this pattern. Basically, use the factory to create the actual types you want to get
class EventFactory
def EventFactory.create_event(event_type)
event_type.constantize.new()
end
end

To me it sounds like you'll need some mojo in the event#create action:
type = params[:event].delete(:type)
# check that it is an expected value!!!
die unless ['Event', 'SpecialEvent'].include(type)
type.constantize.new(params[:event])

Apparently, Rails does not allow you to set Type directly. Here's what I do...
klass_name = 'Foo'
...
klass = Class.const_get(klass_name)
klass.new # Foo.new
I believe .constantize is a Rails inflector shortcut. const_get is a Ruby method on Class and Module.

Up front I'll agree that STI is often NOT the best way to deal with things. Polymorphism, yes, but it's often better to use a polymorphic association than STI.
That said, I had a system in which STI was important. It was a judicial system and things like court cases were remarkably similar across their types and generally shared all their essential attributes. However, a civil case and a criminal case differed in the elements they managed. This happened at several levels in the system so abstracted my solution.
https://github.com/arvanasse/sti_factory
Long story short, it uses a factory method to leverage the common approach described above. As a result, the controller can remain neutral/ignorant of the particular type of STI class that you're creating.

You can use the Rails safe_constantize method, which will ensure the object/class actually exists.
For example:
def typeify(string)
string.classify.safe_constantize
end
new_special_event = typeify('special_event').new

Related

Rails, activerecord: self[:attribute] vs self.attribute

When accessing active record column/attributes in rails, what is the difference between using self[:attribute] vs self.attribute? Does this affect getters and setters?
They're both just methods to get to the attribute - they're both just getters. self.attribtue is a more "traditional" getter, whereas self[:attribute] is basically just the [] method. Switching between using either has no ramifications.
I'd recommend using only the self.attribute method because it's syntactically nicer. However, using the self[:attribute] can come in handy when something else overrides the self.attribute method.
For example, suppose you have a User model with a name database column, so you'd get user.name. But let's say you install a gem that adds a #name method to each of your models. To avoid the complication, one option is to use user[:name] to access it directly without going through the compromised method.
There's a key difference that the accepted answer misses. If you are attempting to modify the attribute while setting the value, then you must use self[:attribute].
For example...
def some_attr=(val)
self.some_attr = val.downcase # winds up calling itself
end
This won't work, because it's self-referencing (you'll get a "Stack too deep"
error). Instead you must assign the value by doing...
def some_attr=(val)
self[:some_attr] = val.downcase
end
There's also a named method write_attribute, which performs the same action as self[:attribute]. Both do what you need, it's a matter of coding style and personal preference. I like write_attribute when the attribute I'm actually defining is variable, e.g.
write_attribute(var, 'some value')

Force reload another model's methods in rails?

I have a model that defines methods based off of the entries in another model's table: eg Article and Type. An article habtm types and vice versa.
I define in Article.rb:
Type.all.each do |type|
define_method "#{type.name}?" do
is?(:"#{type.name}")
end
end
This works great! it allows me to ensure that any types in the type db result in the methods associated being created, such as:
article.type?
However, these methods only run when you load the Article model. This introduces certain caveats: for example, in Rails Console, if I create a new Type, its method article.type_name? won't be defined until I reload! everything.
Additionally, the same problem exists in test/rspec: if I create a certain number of types, their associated methods won't exist yet. And in rspec, I don't know how to reload the User model.
Does anyone know a solution here? Perhaps, is there some way to, on creation of a new Type, to reload the Article model's methods? This sounds unlikely.. Any advice or guidance would be great!
I think you'll be better off avoiding reloading the model and changing your api a bit. In Article, are you really opposed to a single point of access through a more generic method?
def type?(type)
return is? type if type.is_a? String # for when type is the Type name already
is? type.name # for when an instance of Type is passed
end
If you're set on having separate methods for each type, perhaps something like this would work in your Type class
after_insert do
block = eval <<-END.gsub(/^ {6}/, '')
Proc.new { is? :#{self.name} }
END
Article.send(:define_method, "#{self.name}?", block)
end

What is the proper way to validate the type when using single table inheritance (STI)?

I am trying to use single table inheritance for some of my models. The base model is a Tournament, and I wish to extend this to create different types of tournaments. For instance, I might want to add a SingleEliminationTournament, or a DoubleEliminationTournament, both of which would inherit from Tournament. I have 2 questions, both of them somewhat related.
1) I would like the user to be able to create tournaments with a form, and to do this they would need to select one of the subclasses. Is there a way to get all of the subclasses and use them to populate a select box or something like that?
2) Since this information is going into a form, it would be nice to be able to validate the input into type. To do this, I would like to add a validation in the Tournament class that could check to make sure the Type was valid.
Obviously, I could hard code the values into the validation and the form, but I would not like to do that. Any help would be appreciated. Thanks!
TheModel.subclasses
would give you a list of types you need to include, but only if the models are loaded at runtime. They will always be loaded in production mode. You will have to load them manually in development mode.
You could create a directory with tournaments in them and load them with Dir.glob('app/tournaments/**/*_tournament.rb'). This gives you a nice listing all the tournament files you've specified. Because of convention, you can then infer the proper class name for each tournament.
Store this list of tournament names somewhere for reference in you validations and forms.
I'm not a Rails expert and I'm not sure if this can be considered clean, but for the validation part of your question, this worked for me:
Inside Tournament model:
def validate_type_implemented
klass = type.constantize rescue Object
raise "Given type not available." unless klass.class == Class and klass <= self.class
end

Create new classes or overloading with type in Rails

I have a Meeting model, with multiple Participants in different roles (say, Tutor and Student). Right now, I have a single class Participant, which has attribute :type with two possible values (Tutor/Student). These two types share some exactly the same methods. Each also has its own version of other methods. (say, a Tutor when schedule a meeting must get approval from Director).
I handle the differences in methods by overloading with type:
def make_appointment
do stuff
if type = "Tutor"
do something extra
end
end;
I am undecided whether to go this way, or to have two classes, Tutor and Student that inherit Participant class.
What are the issues/pitfalls should I consider in deciding which way to implement this?
Thank you.
For methods that differ only slightly, there are options--build in extension points, pass blocks around to enhance behavior, etc.
Almost every time there's type-dependent behavior implemented via type comparisons it's not a good idea.
You should use some authorization gem like cancan which is the most poular.
There is a short tutorial about role based authorization. => https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
Its absolutly okay to store the data in one Model. If there are many different attributes you may use polymorpic associations to expand the user Model but in most cases thats not needed!

Best practice to isolate data access layer in Ruby/Rails

So, I have Ruby on Rails application. Blank for now. And let me say right from the beginning that most of my experience is from Java, so I might be thinking not the way RoR devs do. :-)
What I need to do is to create some Data Access Layer, say it will be access users, so let it be
UserDAO.rb which will be basically then using ActiveRecord or directly accessing the database or accessing or some key-value storage or anything else I can think of.
Technically, as we don't have interfaces in Ruby, I can make UserDAO.rb to "have" the implementation (basically, I am talking about composition), which may be anything we need, say UserDAOActiveRecord.rb or UserDAOMongo.rb or anything else like that. UserDAO.rb will basically call the methods of the implementation and that's it. Should be easy to switch between implementations.
While it does sound like a possible solution, I am looking forward to hear what are the best practices for this problem in the Ruby world. Thanks!
You will have to look for a Ruby Class other than ActiveRecord (which as pointed out is an Object Relational Mapper, so has no separate data access layer).
You might want to look at: https://github.com/jeremyevans/sequel
You could create a class, Person, which contains methods which use an instance of Sequel to talk to the database.
This untested code demonstrates why this might not be a great idea:
class Person
attr_reader :first_name, :last_name
DataSource = Sequel.sqlite('my_app.db')[:people]
def initialize(record)
#first_name = record.first_name
#last_name = record.last_name
end
# Find a record from the database and use it to initialize a new Person object
def self.find_by_id(id)
self.new(Table.where(:id => id))
end
end
Remember that ActiveRecord isn't only a way to access a database, it's also a pattern for how your data is integrated into your application: the idea that each model controls its own data and stores/retrieves/queries it as needed, with a model instance representing a database row.
Naturally, you don't have to use that pattern, but it's one of the cores of Rails, so by treating ActiveRecord as just another data access method to be abstracted, you're losing a lot of functionality.
Note also that ActiveRecord already abstracts out the database type by using a database adapter, so it's easy to drop in MySQL, Oracle, etc. But it does assume a relational database.
However, to answer your question, it's not really necessary to wrap your data access implementation in another class just to ensure a consistent interface. As you say, ruby doesn't have Java-type interfaces, but the ruby world also doesn't generally try to ensure that developers can only do legal things. You can create a bunch of data access classes that offer the same set of methods, create unit tests to ensure that those methods are consistent (and to act as executable documentation of those methods), and then trust developers to make the correct calls against whichever implementation they pick. It's a big cultural difference from the Java world, where everything is coded to interfaces and methods are final and objects are immutable. It takes some getting used to.
Alex, I strongly suggest you to get the book Agile Web Devoplment With Rails (4th edition). Active Record implements the ... active record pattern, so the Class is the DAO (the class methods represent the dao), while the instance methods represents the object.
So for example you can have
person = Person.find(3) //Person is the dao
person.name = 'mick' //name = is the setter for he instance
person.save // well.. saves the object
I've coded in Java for 10 years and just started with ruby..... and it's quite a change.

Resources