how to avoid repetition with identical models in rails - ruby-on-rails

In my project I have two models which have identical attributes, methods and everything the same.
at the moment they are in app/models/ in separate rb files, with quite some repetition of code.
I would like to export all that code to a separate file and have the two files referring to it, and have mode DRY code.
I've tried the following, but it didn't work:
# app/models/order.rb
class Order < ActiveRecord::Base
before_save { self.version += 1 }
attr_accessible :order
attr_accessible :filled_date
validates :order, :presence => true
end
and one of the referring orders is:
# app/models/real_order.rb
class RealOrder < Order
belongs_to :User, inverse_of: :real_orders
end
but this doesn't work, and I get a Could not find table 'orders' when I try to use the models.
Also I'm thinking that Orders is not a real model, so probably the app/models is not really the right place for that file, though I'm not sure in what directory it should be.
thanks,
UPD1:
The structure I would like to achieve in the end is a situation where I have two identical database tables, with two separate models that are based on the same code. I would like to write such code only once in a separate superclass file. So I'm looking for DRY code, not DRY database.

There are a few different ways to share code between models. If it makes sense (for your problem domain) to use inheritance (as in your example above), then you need the following in your Order class:
self.abstract_class = true
You can also use mixins.
Here's a good question about this: ruby inheritance vs mixins

Related

Override gem behaviour

I'm using the acts_as_bookable gem for some basic reservation/booking stuff in a Rails app, and I need to add an additional validation to the Booking model that the gem creates.
What I mean by that is, inside the gem, located at lib/acts_as_bookable/booking.rb is the following module/class:
module ActsAsBookable
class Booking < ::ActiveRecord::Base
self.table_name = 'acts_as_bookable_bookings'
belongs_to :bookable, polymorphic: true
belongs_to :booker, polymorphic: true
validates_presence_of :bookable
validates_presence_of :booker
validate :bookable_must_be_bookable,
:booker_must_be_booker
# A bunch of other stuff
end
end
Which is fine. However, I want to add an additional piece of logic that stops a booker from booking the same instance of a bookable. Basically, a new validator.
I thought I could just add a file in my /models directory called acts_as_bookable.rb and just modify the class like this:
module ActsAsBookable
class Booking
validates_uniqueness_of :booker, scope: [:time, :bookable]
end
end
But this doesn't work. I could modify the gem itself (I've already forked it to bring a few dependencies up to date, since it's a pretty old gem) but that doesn't feel like the right solution. This is logic specific to this app's implementation, and so my gut feeling is that it belongs in an override inside this specific project, not the base gem.
What am I doing wrong here? And is there a better/alternative approach that would be more suitable?
A clean way to create monkeypatches/augmentations to objects outside of your control is to create a seperate module:
module BookingMonkeyPatch
extend ActiveSupport::Concern
included do
validates_uniqueness_of :booker, scope: [:time, :bookable]
end
end
This lets you test the monkeypatch seperately - and you can "turn the monkeypatch on" by including the module:
ActsAsBookable::Booking.include(BookingMonkeyPatch)
This can be done in an initializer or anywhere else in the lifecycle.
Altough if bookable is a polymorpic assocation you need to use:
validates_uniqueness_of :booker_id, scope: [:time, :bookable_id, :bookable_type]
The uniqueness validation does not work correctly when just passed the name of an assocation as it creates a query based on database columns. This is an example of a leaky abstraction.
See:
Justin Weiss - 3 Ways to Monkey-Patch Without Making a Mess

Rails inheritance for classes of same behaviour but different attributes

I have been researching on the best approach for my problem which I originally had implemented as a single table inheritance but am deeply concerned about the scalability, as potentially will have thousands of columns in the table.
So the problem is I would like to have products which the methods of each are exactly the same the only difference being the attributes each one contains. It seems that in this situation that mutli-class inheritance (not supported natively in rails?) would be the best approach or some sort of polymorphic associations.
I want to work towards the following
#product.rb
Class Product < ActiveRecord::Base
attr_accessible :title .....
def to_s # some arbitrary method used by all extending classes
....
end
end
#book.rb
class Book < Product
attr_accessible :author...
end
So I want the book to inherit the methods from product and not for the product to know about the attributes required by each subclass. And if possible get all of the products through one query.
I need to know the best way of approaching this, and if I am doing it completely wrong, please note the code written above is just for example to simplify my problem.
What you can do is create a module and include it in several different models.
First, create a file in your lib directory
i.e.) my_module.rb
module MyModule
def full_name
"#{first_name} #{last_name}"
end
end
Then, make sure the module is loaded when your Rails App starts:
In config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Finally, include it in your models:
i.e.) app/models/thing.rb
class Thing < ActiveRecord::Base
attr_accessible :first_name, :last_name
include AdditionMod
end
You can test it in the console:
#thing = Thing.create(first_name: "Awesome", last_name: "Module")
#thing.full_name
=> "Awesome Module"
Found out that I can use H-store in conjunction with postgres that allows me to have a column that contains a schema less hash that can be used with the power of postgres (for an example take a look at http://hstoredemo.herokuapp.com/)

ActiveRecord model inheritence and polymorphism

I have a class heirarchy but not single-table inheritence.
ThrowAway < ActiveRecord::Base
and
Junk < ThrowAway
the problem is that all ThrowAway objects have references to a Location. A Location
belongs_to :throw_away, :polymprohic => true
The problem is that if I define
ThrowAway < ActiveRecord::Base
has_many :locations, :as => :throw_away
end
then even if Junk inherits from it and defines a different table name, the throw_away_type column will always be set to ThrowAway where I actually want it set to Junk.
Now there will be many of these subclasses so there will be Stuff < ThrowAway Rags < ThrowAway etc. I want them all to have a Location without defining a location relationship in each individual class.
Is this possible with rails? Problem is that there is not just location, there are other of these sort of relationships and I'd rather follow some DRY here. I'm assuming I need to create a generator method which will execute on the current object and generate these relationships generators on runtime.
Thanks.
ActiveRecord doesn't seem to be able to cooperate with model inheritance that's not STI. Fortunately, that's probably not what you really want anyway. You probably want a mixin instead.
Create a module that contains all of the functionality that you want your models to share in common, and have all of your models include that module. I would probably put the module in your models directory, but give it an adjective as a name. Some folks might put the module in the lib directory or create a lib directory within the models directory for it.
In your case, you'll want the association to be defined at the time the module is mixed in, so you'll need to use a callback. Something like..
module Trashable
def self.included(base)
base.send :has_many, :locations, :as => :throw_away
end
end

How should I model these relationships?

I have a post model. Each post has a title and many snippets.
class Post < ActiveRecord::Base
has_many :snippets
end
class Snippet < ActiveRecord::Base
belongs_to :post
attr_accessible :position, :post_id
end
I want to have 4 different types of snippet i.e.:
body (text)
image (string)
code (text)
video (string)
Q1
Should I create four new models (called text, code, video and image) which extend the snippet model like so?:
class Text < Snipppet
attr_accessible :body
end
class Image < Snippet
attr_accessible :image
end
class Video < Snippet
attr_accessible :title
end
class Code < Snippet
attr_accessible code
end
Q2
How can I refer to the content of each snippet in my view when each snippet can be one of 4 different things?
In my view I'd like to put something like this:
- for snippet in #post.snippets
= place the content of the snippet here
Q3
I don't think it sounds like a good idea to have a "type" field on the snippet model as this would possibly lead to strong coupling of the database and the code. Is there some kind of rails magic that will help me out in this situation?
I kinda like the type field, actually. You could use the magic of single table inheritance, but it's well-known to be a fragile system that, surprise, includes a type field, anyway.
And then you could use the polymorphism solution, but seems a bit silly to have four different tables to represent almost exactly the same thing.
Honestly, using a simple type field and changing the interpretation based on that will probably give you the best results and fewest headaches.
As far as what you'd do in your template goes, that you can probably play by ear. One clean solution might be a simple helper to call like snippet_content which, based on the type field, will call the helper snippet_content_text or snippet_content_image or snippet_content_code or snippet_content_video. Or you can just do the if-then branching in the template, or refer to any number of partial templates (though be careful with that, since those can get slow when used unnecessarily).
I like this approach, but I always run into some subtle complications later on.
Create the appropriate views (texts/_text.html.haml, images/_image.html.haml, etc), so you can let Rails handle it for you:
= render #post.snippets
Like it or not: the "type"-field is the way to have Rails magic help you create the proper instances.
Update: polymorphic relations work too, let every snippet have their own tables. Is it a is a relation or a behaves like type of relation? You could say that Images and Texts both behave like snippets. In that case, go for a module named Snippet and mix it in.
class Post < ActiveRecord::Base
has_many :snippets, :polymorphic => true
end
class Text < ActiveRecord::Base
include Snippet
belongs_to :post, :as => :snippet
end
module Snippet
# shared behavior here
end
Many thanks to everyone who answered, but I think I've managed to fix this by using jystewart's rails 3 version of the acts_as_polymorphs gem
Here's what I did:
So let's recall, we have Posts and we have four different types of snippet. So that's 6 different models (post.rb, snippet.rb, code.rb. text.rb, image.rb and video.rb)
Posts have many snippets.
Snippets belong to posts.
Code, text, video and image are all types of snippet
And the big problem is that we don't know what type of object a snippet is, because it can be one of 4 different things.
At first, I tried to do this with Single Table Inheritance, but the objects (code, text, video and image) are, in my opinion, too different from one another for this to work well and I didn't like the fact that it would likely result in lots of empty database cells so I went along the Polymorphic route.
I couldn't get standard polymorphic associations to work here because this is not your usual polymorphic situation. Normally, when dealing with polymorphism, we're talking about something like a comments model, which can attach to multiple other models. The polymorphic entity is always the same thing. But in this case, we're talking about snippets model which can be one of 4 different things. This is not a simple belongs_to situation. Polymorphism was not happening.
Then I stumbled upon this article by m.onkey.org - which is a few years old but he basically explained that this sort of thing needs the acts_as_polymorphs gem.
So the solution was to do the following:
I create 6 models, all extending ActiveRecord::Base
add has_many_polymorphs to the post model
create a polymorphic association called "snippetable" in the snippet model
add some new fields to the snippets table through my migration file
Here's my code:
class Post < ActiveRecord::Base
has_many_polymorphs :snippets, :from => [:codes, :texts, :videos, :images], :through => :snippets
end
class Snippet < ActiveRecord::Base
belongs_to :snippetable, :polymorphic => true
end
class Code < ActiveRecord::Base
# don't have to put anything in here
end
class Text < ActiveRecord::Base
# don't have to put anything in here
end
class Video < ActiveRecord::Base
# don't have to put anything in here
end
class Image < ActiveRecord::Base
# don't have to put anything in here
end
The only other thing we need, is to stick a few new fields in the CreateSnippets migration:
class CreateSnippets < ActiveRecord::Migration
def self.up
create_table :snippets do |t|
t.references :post
t.column :snippetable_id, :integer, :null => false
t.column :snippetable_type, :string, :null => false
t.timestamps
end
end
end
And that's it! Unbelievably I can now go to rails console and do the following:
p = Post.first
p.codes << Code.create(:code=>"This is a code snippet")
p.images << Image.create(:image=>"xyz.png")
p.images << Image.create(:image=>"123.png")
p.codes.count # returns 1
p.images.count # returns 2
p.snippets.count # returns 3 !!!
Yaldi!
Anyway, it's taken me 11 days to fix this, and it REALLY depressed me that I couldn't do it. I hope this helps someone.
Here are some decent reading materials for acts_as_polymorph:
jstewart's rails 3 gem
Pratik Nait (of 37 signals) Blog post on it
What a carry on

How many classes is too many? Rails STI

I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.

Resources