RoR | Best Practices when abstracting an Active Record - ruby-on-rails

My task is to abstract/inherit an active record class. I'm making a blog where post is a base super with title, slug dates etc... all the redundant stuff you would expect to find.
Here's where things take a turn, I want to sub class out Post into many other sub post types such as audio post, video post, image post, vanilla post. I think you get the point. Obviously each sub type will have their own respective attributes and members.
Instead of creating a name, slug, etc., for each sub post type, what is the best practice to inherit or possibly interface the base class? ("I do favor composition over inheritance")
Once I figure out how to properly abstract out my models, I would like to then figure out some polymorphic way to say something like Blog.find(1).posts and get an array of all the posts types.
I realize that this may not be performance optimal to query all the post types in a polymorphic way so feel free to seguest a better way.

While I personally also prefer composition over inheritance, ActiveRecord does not. In this case, if you want to use tools that ActiveRecord offers, you should take a look at Single Table Inheritance, which would take care of both of your questions. It does use inheritance, however.
Switching to a non-ActiveRecord orm may offer you a way of doing this without having to do everything via inheritance. I've used DataMapper, which prefers composition, with success in the past, but it isn't as feature-packed as ActiveRecord and may not offer what you need.

Other then single table inheritance, you may also consider using has_one association.
All your sub-types has one post-info, which is the general post name, slug etc (and a post-info belongs to a sub-type polymorphically).
In this way, you would have a table of post-info, and tables for every sub-types.
However, in the model you have to do a little bit more handling:
class PostInfo < ActiveRecord::Base
belongs_to :post, :polymorphic => true
# will need these 2 fields: :post_id, :post_type (might be AudioPost, ImagePost, etc)
end
class AudioPost < ActiveRecord::Base
has_one :post_info, :as => :post
# you may also want these:
accept_nested_attributes_for :post_info
delegate :name, :slug, :posted_at, :to => :post_info
end
So now if you want to get all the posts, you may:
Blog.find(1).post_infos
post_info.post # => audio_post, image_post, or whatever depending on post_type
If you don't want to use .post_infos, you may also change all those names, such as:
class Post < ActiveRecord::Base
belongs_to :actual_post # actual_post_id, actual_post_type
end
class AudioPost < ActiveRecord::Base
has_one :post, :as => :actual_post
accept_nested_attributes_for :post
delegate :name, :slug, :posted_at, :to => :post
end
Now, you have:
posts = Blog.find(1).posts
actual_post = posts.first.actual_post # => an audio_post instance
actual_post.name # => same as actual_post.post.name, so you do not need the name field in the AudioPost model

Related

Mongoid: How do I query for all object where the number of has_many object are > 0

I have a Gift model:
class Gift
include Mongoid::Document
include Mongoid::Timestamps
has_many :gift_units, :inverse_of => :gift
end
And I have a GiftUnit model:
class GiftUnit
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :gift, :inverse_of => :gift_units
end
Some of my gifts have gift_units, but others have not. How do I query for all the gifts where gift.gift_units.size > 0?
Fyi: Gift.where(:gift_units.exists => true) does not return anything.
That has_many is an assertion about the structure of GiftUnit, not the structure of Gift. When you say something like this:
class A
has_many :bs
end
you are saying that instance of B have an a_id field whose values are ids for A instances, i.e. for any b which is an instance of B, you can say A.find(b.a_id) and get an instance of A back.
MongoDB doesn't support JOINs so anything in a Gift.where has to be a Gift field. But your Gifts have no gift_units field so Gift.where(:gift_units.exists => true) will never give you anything.
You could probably use aggregation through GiftUnit to find what you're looking for but a counter cache on your belongs_to relation should work better. If you had this:
belongs_to :gift, :inverse_of => :gift_units, :counter_cache => true
then you would get a gift_units_count field in your Gifts and you could:
Gift.where(:gift_units_count.gt => 0)
to find what you're looking for. You might have to add the gift_units_count field to Gift yourself, I'm finding conflicting information about this but I'm told (by a reliable source) in the comments that Mongoid4 creates the field itself.
If you're adding the counter cache to existing documents then you'll have to use update_counters to initialize them before you can query on them.
I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
So, get this done, you can define a class function like so:
def self.with_units
ids = Gift.all.select{|g| g.gift_units.count > 0}.map(&:id)
Gift.where(:id.in => ids)
end
The advantage is, that you can do all kinds of queries on the associated (GiftUnits) model and return those Gift instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:
Gift.with_units.where(:some_field => some_value)

Flattening a polymorphic AR relation with Elasticsearch/Tire

I'm working with a Rails 3 application to allow people to apply for grants and such. We're using Elasticsearch/Tire as a search engine.
Documents, e.g., grant proposals, are composed of many answers of varying types, like contact information or essays. In AR, (relational dbs in general) you can't specify a polymorphic "has_many" relation directly, so instead:
class Document < ActiveRecord::Base
has_many :answerings
end
class Answering < ActiveRecord::Base
belongs_to :document
belongs_to :question
belongs_to :payload, :polymorphic => true
end
"Payloads" are models for individual answer types: contacts, narratives, multiple choice, and so on. (These models are namespaced under "Answerable.")
class Answerable::Narrative < ActiveRecord::Base
has_one :answering, :as => :payload
validates_presence_of :narrative_content
end
class Answerable::Contact < ActiveRecord::Base
has_one :answering, :as => :payload
validates_presence_of :fname, :lname, :city, :state, :zip...
end
Conceptually, the idea is an answer is composed of an answering (functions like a join table, stores metadata common to all answers) and an answerable (which stores the actual content of the answer.) This works great for writing data. Search and retrieval, not so much.
I want to use Tire/ES to expose a more sane representation of my data for searching and reading. In a normal Tire setup, I'd wind up with (a) an index for answerings and (b) separate indices for narratives, contacts, multiple choices, and so on. Instead, I'd like to just store Documents and Answers, possibly as parent/child. The Answers index would merge data from Answerings (id, question_id, updated_at...) and Answerables (fname, lname, email...). This way, I can search Answers from a single index, filter by type, question_id, document_id, etc. The updates would be triggered from Answering, but each answering will then pull in information from its answerable. I'm using RABL to template my search engine inputs, so that's easy enough.
Answering.find(123).to_indexed_json # let's say it's a narrative
=> { id: 123, question_id: 10, :document_id: 24, updated_at: ..., updated_by: root#me.com, narrative_content: "Back in the day, when I was a teenager, before I had...", answerable_type: "narrative" }
So, I have a couple of questions.
The goal is to provide a single-query solution for all answers, regardless of underlying (answerable) type. I've never set something like this up before. Does this seem like a sane approach to the problem? Can you foresee wrinkles I can't? Alternatives/suggestions/etc. are welcome.
The tricky part, as I see it, is mapping. My plan is to put explicit mappings in the Answering model for the fields that need indexing options, and just let the default mappings take care of the rest:
mapping do
indexes :question_id, :index => :not_analyzed
indexes :document_id, :index => :not_analyzed
indexes :narrative_content, :analyzer => :snowball
indexes :junk_collection_total, :index => :not_analyzed
indexes :some_other_crazy_field, :index
[...]
If I don't specify a mapping for some field, (say, "fname") will Tire/ES fall back on dynamic mapping? (Should I explicitly map every field that will be used?)
Thanks in advance. Please let me know if I can be more specific.
Indexing is the right way to go about this. Along with indexing field names, you can index the results of methods.
mapping do
indexes :payload_details, :as => 'payload_details', :analyzer => 'snowball',:boost => 0
end
def payload_details
"#{payload.fname} #{payload.lname}" #etc.
end
The indexed value becomes a duck type, so if you index all of the values that you reference in your view, the data will be available. If you access an attribute that is not indexed on the model of the indexed item, it will grab the instance from ActiveRecord, if you access an attribute of a related model, I am pretty sure you get a reference error, but the dynamic finder may take over.

Rails Advanced Sorting

I have three models, basically:
class Vendor
has_many :items
end
class Item
has_many :sale_items
belongs_to :vendor
end
class SaleItem
belongs_to :item
end
Essentially, each sale_item points to a specific item (but has an associated quantity and sale price which might be different from the item's base price, hence the separate model), and each item is made by a specific vendor.
I'd like to sort all sale_items by vendor name, but this means going through the associated item, because that's where the association is.
My first attempt was to change SaleItem to the following:
class SaleItem
belongs_to :item
has_one :vendor, :through => :item
end
Which allows me to look for SaleItem.first.vendor, but doesn't allow me to do something like:
SaleItem.joins(:vendor).all(:order => "vendors.name")
Is there an easy way to figure out these complex associations and sorting? It would be especially great if there were a plugin that could take care of these sort of things. I have a lot of different types of tables to add sorting to in this application, and I feel like this will be a big chunk of the figuring-out work.
This could definitely be done with a more complex SQL query (possibly using find_by_sql), but you could also do it pretty easily in Ruby. Try something like the following:
SaleItem.find(:all, :include => { :items => :vendors }).sort do |first,second|
first.vendor.name <=> second.vendor.name
end
I haven't tested it, so it might not work exactly like this, but it should give you a good idea of one possible solution.
Edit: Found an old blog post that seems to have solved this issue. Hopefully this still works in the lastest version of ActiveRecord.
source: http://matthewman.net/2007/01/04/eager-loading-objects-in-a-rails-has_many-through-association/
Second Edit: Straight from the Rails documentation
To include a deep hierarchy of associations, use a hash:
for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
That’ll grab not only all the comments but all their authors and gravatar pictures. You can mix and match symbols, arrays and hashes in any combination to describe the associations you want to load.
There's your explanation.
Do you really need your sale_items sorted by the database, or could you wait until it is presented and do the sorting client side via javascript (there are some great sorting libraries out there) - that would save server CPU and (backend) code complexity.

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