Alternative to Rails Single Table Inheritance (STI)? - ruby-on-rails

I have a model and table that I believe is perfectly suited to STI. My table is called Finances and has two types: Income and Expenses. Besides type there are three other columns: description, amount, and date.
I'm getting very nervous using STI in Rails, since it requires some hacking. I'm too new to Rails to hack up the code. Even though it works, I don't understand it. That seems dangerous.
My question is, how do I set up my model, controller, and view if I do NOT use STI? Any best practices to group items in my model? Or do I just do Finances.where("type = 'Income'") before setting up a view?
Edit: I made a gist to show the code I'm working with. When I run it I get the error:
undefined method `incomes_path' for #<#<Class:0x007fbc95f60b40>:0x007fbc93883220>

First, using STI is standard for Rails, so there is no need to feel nervous. And no need for "hacking".
It has been used very successfully by many developers. And as you have seen, you can find tutorials and general information on the net.
If on the other hand, you decide NOT to use STI, you have the choice of using
(a) completely separate models with their own tables, which will result in a lot of duplicated code, or
(b) create you custom "STI-like" behaviour by hand.
The second option could at least be interesting to learn more about Rails.
For example, in your Finances model you would define a scope incomes, like
scope :incomes, where(:type => 'Income')
then you can do Finances.incomes.
Then, if you have methods that apply only to one of the types, you should check that all records effectively are of the needed type.
Personally, I would advice you to use STI. You get a lot of functionality for free and you are doing it the Rails way.
Imagine, for example, other developers reading your code, they will ask themselves, why you didn't use STI, blame it on ignorance and - if need be - refactor it using STI.

STI is best if you are using inheritance structure like this. You don't really need to use Finances.where("type = 'Income'"). You can simply use Income.all. see these posts if they help you.
http://www.therailworld.com/posts/18-Single-Table-Inheritance-with-Rails
http://juixe.com/techknow/index.php/2006/06/03/rails-single-table-inheritance/

Related

Dry up Rails Active Record query conditions

In my Ruby on Rails project, I have a mailer that basically prepares a daily digest of things that happened in the system for a given user. In the mailer controller, I am gathering all the relevant records from the various models according to some common pattern (within a certain date, not authored by this user, not flagged, etc) and with minor differences from model to model.
There are half a dozen of models involved here (and counting), and most of them have unified column names for certain things (like date of publishing, or whether an item is flagged by admin or not). Hence, the 'where's that go into query are mostly the same. There are minor differences in conditions, but at least 2 or 3 conditions are exactly the same. I easily assume there may be even more similar conditions between models, since we are just starting the feature and haven't figured out the eventual shape of the data yet.
I basically chain the 'where' calls upon each model. It irritates me to have 6 lines of code so close to each other, spanning so far to the right of my code editor, and yet so similar. I am dreaded by the idea that at some point we will have to change one of the 'core' conditions, munging with that many lines of code all at once.
What I'd love to do is to move a core set of conditions that goes into each query into some sort of Proc or whatever, then simply call it upon each model like a scope, and after that continue the 'where' chain with model-specific conditions. Much like a scope on each model.
What I am struggling with is how exactly to do that, while keeping the code inside mailer. I certainly know that I can declare a complex scope inside a concern, then mix it into my models and start each of queries with that scope. However, this way the logic will go away from the mailer into an uncharted territory of model concerns, and also it will complicate each model with a scope that is currently only needed for one little mailer in a huge system. Also, for some queries, a set of details from User model is required for a query, and I don't want each of my models to handle User.
I like the way scopes are defined in the Active Record models via lambdas (like scope :pending, -> { where(approved: [nil, false]) }), and was looking for a way to use similar syntax outside model class and inside my mailer method (possibly with a tap or something like that), but I haven't found any good examples of such an approach.
So, is it possible to achieve? Can I collect the core 'where' calls inside some variable in my mailer method and apply them to many models, while still being able to continue the where chain after that?
The beauty of Arel, the technology behind ActiveRecord query-building, is it's all completely composable, using ordinary ruby.
Do I understand your question right that this is what you want to do?
def add_on_something(arel_scope)
arel_scope.where("magic = true").where("something = 1")
end
add_on_something(User).where("more").order("whatever").limit(10)
add_on_something( Project.where("whatever") ).order("something")
Just ordinary ruby method will do it, you don't need a special AR feature. Because AR scopes are already composable.
You could do something like:
#report_a = default_scope(ModelA)
#report_b = default_scope(ModelB)
private
def default_scope(model)
model.
where(approved: [nil, false]).
order(:created_at)
# ...
end

Lots of different models in tests (rspec)? Advanced

I am only looking for answers from senior/more experienced Ruby/Rails developers on this one, since I think this is a bit more advanced of a question.
I have a gem I am working on that adds some behavior to AR models. I have to test it for a lot of different associations (has_many, habtm, has_one etc), and I also have to test behavior when different options are passed for the associations (e.g. :foreign_key). Now with all these different models, I can use the same table in the database because the fields themselves do not need to change, only behavior specified through has_many, belongs_to and so on.
Keep in mind there are a lot of different options, so the number of models is quite large.
First I don't think it would be bad practice to have the definition of the models next to / in the test itself, for readability purposes (if I have multiple tests that use the same model then I would group them together and use the before method). So this is one of my goals, and you can comment on this if you don't agree.
The second thing I am not sure of is I wanted to keep the simple/same name of the model in all the tests, for example "Task", instead of TaskWithManySubtasksAndForeignKey or something ugly like that. The problem is there are so many models it's hard to come up with meaningful and simple names. I'm not quite sure about this - using the same name, since it's a constant, is a little problematic. I have a solution with a proxy class but I don't think this is the optimal solution. I was considering using variables (with the let method) like "taskModel", but it seemed a little verbose and unusual.
One other option that comes to mind, but I am not sure is possible to do easily, is to remove an existing association and then define a new one. So e.g. add a has_many and then remove it, add a habtm...
How would you go about doing this?
Defining unique models in the spec files is not necessarily a bad idea since it makes it easy to see exactly how each model is defined. The obvious problem with this approach is if you want to reuse the models in other test files. The Rails approach to this is to define all the models in separate files and then just require them in the tests that need it.
I think it really just depends on how many models you have and how much you want to reuse. In one of my gems, I took the approach of defining the models in the spec file, in another gem, I defined them in the spec helper, and in yet another I took the Rails approach and used a separate directory for them. If you asked me which one I preferred, I'd probably go with the spec that also contains the models because it's all in one place. Definitely a subjective problem though.
Another approach I've taken on occasion is to create an anonymous class that's guaranteed to only be around for the life of that test:
describe 'my test' do
let(:my_class) do
Class.new(Task) do
has_many :things
belongs_to :something_else
end
end
it 'should have many things' do
my_class.should have(100).things
end
end

Similar models in ruby on rails

I'm working on a Ruby on Rails project where I have a "post" model and a "reply" model. As their names suggest, they share a lot of common properties such as "author_id" and "body", but have their own respective unique properties as well (e.g. a post has a title, whereas a reply does not).
My question is: what's the best practice for dealing with two similar data models? Right now I'm treating them as two distinct models types, but as I start coding, I realized there will be a lot of repetition of code.
Take a look at mixins and modules in Ruby.
I think the best option here would be for you to create a module with the shared methods for each one and include it in both of them. That way, you don't have to repeat code and both will be independent.
You can check out the link provided by sczizzo above.

rails has_and_belongs_to_many or manual implementation

I'm designing a ruby on rails app for a pharmacy, and one of the features is that there are stores who have pharmacists who work there. In addition, there are pharmacists, who can work at many stores. This sounds like a job for HABTM, right? Well, being the novice I am, I manually designed a workaround (because I never heard of HABTM - I basically taught myself rails and never got to some of the more advanced relationships). Right now, when a pharmacist is saved, there's a couple of lines in the create and update action of the pharmacists controller that turns the stores that they work at into a string, with each store_id separated by a comma. Then, when a store is displayed, it does a MYSQL request by
#pharmacists = Pharmacist.find :all, :conditions => "stores REGEXP '#{#store.id}'"
Would moving this system over to a rails based HABTM system be more efficient? Of course it would require less code in the end, but would it be worth it? In other words, what benefits, other than less code, would I get from moving this association to be managed by rails?
The benefit is that you will be using the right tool for the job! The whole point of using a framework such as Rails is that it helps you solve common problems without having to re-invent the wheel, which is what you've done here. By using associations you'll also be using a relational database properly and can take advantage of benefits like foreign key indexing, which will be faster than string manipulation.
You should use a has_and_belongs_to_many relationship unless you need to store extra attributes on the join model (for example the date a pharmacist started working at a store) in which case use has_many :through.
Using Rails associations will give you all the convenient methods that Rails provides, such as these:
# Find the stores the first pharmacist works at
#stores = Pharmacist.first.stores
# Find the pharmacists who work at a store
#pharmacists = Store.find_by_name('A Store').pharmacists
A Guide to ActiveRecord Associations

Rails Multiple Table Inheritance question

I am starting to implement an MTI solution and have a basic question. I have 3 physical models - SMSNotifications, EmailNotifications, TwitterNotifications and they are subclasses of notification. At times in my code, I want to say Notifications.find(:all)so that I can get a set of results sorted by their creation time. Then I want to do things based on their subclass. What is the way to write Notifications.find(:all) and have Rails look through the subclass tables and combine the results? Right now Rails still thinks I have a physical Notifications table which goes against my MTI design.
I am also considering the possibility that I should be using STI instead. I would probably have 10 empty columns per row but if getting all notifications requires a query for each type of notification, then I feel like this could be a big issue.
Thanks!
Yes, you will need separate queries for each type. If this is a dealbreaker, then you should go with either STI or the mixed model approach advocated in your previous question.
If you use https://github.com/hzamani/acts_as_relation:
rails g model notification notification_type:string notification_id:integer <other columns>
rails g model email_notification <columns>
class EmailNotification < ActiveRecord::Base
acts_as :notification
end
....
then the code you want might be something like this:
Notification.all.each do |n|
case n.notification_type
when "EmailNotification"
...
when "SMSNotification"
...
end
end
For this purpose, you can make the subclasses have a polymorphic association with some specific model (X). You can edit the callback after_initialize to associate one entry of this specific model X with each of your subclasses on creation. In this way, you can perform find on the target (X) of the mentioned polymorphic association.
I recently forked a promising project to implement multiple table inheritance and class inheritance in Rails. I have spent a few days subjecting it to rapid development, fixes, commenting and documentation and have re-released it as CITIER Class Inheritance and Table Inheritance Embeddings for Rails.
I think it should allow you to do what you needed by asking for all notifications, it will still return the correct models. If you were to do something like
Notification.all().each do |n|
if n.class == 'EmailNotification'
#Do something
end
end
Or even define a function in the root Notification class and overload it in subclasses to return something different.
Consider giving it a look: http://peterhamilton.github.com/citier
I am finding it so useful! I would (by the way) welcome any help for the community in issues and testing, code cleanup etc! I know this is something many people would appreciate.
Please make sure you update regularly however because like I said, it has been improving/evolving by the day.

Resources