How do I handle a simple polymorphic association in my views? - ruby-on-rails-3

I've got some models that look like this:
class Basket
has_many :fruits, :dependent => :destroy
end
class Fruit
belongs_to :basket # do I need a polymorphic association here?
end
class Apple < Fruit
validate :crunchy
end
class Banana < Fruit
validate :peelable
end
Fruit is abstract in the sense that you never create, update, etc., Fruits, but rather Apples or Bananas. That means that I can't write something like edit_fruit_path(#fruit) in my views and have it automatically resolve.
What should I write in my views so that it always resolves to edit_apple_path(#fruit) or edit_banana_path(#fruit)?

This is not a polymorphic but rather a Single Table Inheritance.
I am assuming that you will inherit ActiveRecord::Base to Fruit.
Add the column type to your fruits table.
Now you can do edit_fruit_path(#apple) and it will be the Apple object.

Related

Rails: Is this a use case for Single Table Inheritance (STI)?

Consider this setup. Please understand that our setup is much more detailed but this is a simple example.
competition which has name. This is an annual competition.
competition_instances which has location, starts_at.
Each competition has sports which has name.
Example:
competition.name: "Super Bowl" has different competition_instances every year but sport remains the same.
Conversely, competition.name: "Olympics" has different competition_instances and different sports in each competition_instance.
Would it be best to create competition_sports and competition_instance_sports with competition_instance_sports as a subclass of competition_sports?
GOAL: Use competition_instance_sports records if they exist, otherwise use the competition_sports record. In our real world app, each competition/competition_instance can have 20-50 sport records. How can we best achieve?
Based on what I understand from the question I cannot see where STI will be helpful in this situation. However a join table will get you where you want.
I suggest creating a new table sports, this model will have all the specific details of each sport. The competition_instance.rb will have one/many sport.rb. competiton.rb will have many sports through competition_instance.rb.
competition.rb
Class Competition < ActiveRecord::Base
has_many :competition_instances
has_many :sports, through: :competition_instances
end
competition_instance.rb
Class CompetitionInstance < ActiveRecord::Base
belongs_to :competition
belongs_to :sport
end
sport.rb
Class Sport < ActiveRecord::Base
has_many :competition_instances
end
By using this design you will be able to achieve the following:
1- You will have your predefined sports in your database along with their specific properties.
2- Each competition will have .sports which will give all the sports in this competition for the olympics case.
3- You will be able to set specific properties for each competition instance (example event_start_time and event_end_time) in the competition instance table.
I'm just thinking of the case where there are standard sports which are always in the Olympics and some which are added on, such as those proposed by the host country.
I would use Polymorphic Associations, in a "reverse manner".
class Competition < ActiveRecord::Base
has_many :competition_instances
has_many :competition_sports, as: :event
end
class CompetitionInstance < ActiveRecord::Base
belongs_to :competition
has_many :competition_sports, as: :event
def events_array # neater by sacrificing ActiveRecord methods
competition.competition_sports + competition_sports
end
def events # messier, returns ActiveRecord relationship
CompetitionSport.where( " ( event_id = ? AND event_type = 'Competition' ) OR
( event_id = ? AND event_type = 'CompetitionInstance')", competition_id, id )
end
end
class Sport < ActiveRecord::Base
has_many :events, as: :competition_sport
end
class CompetitionSport < ActiveRecord::Base
belongs_to :sport
belongs_to :event, polymorphic: true
end
This allows:
competition.competition_sports # standard sports
competition_instance.competition_sports # only those specific for this instance
competition_instance.events # includes sports from both
Aiming at the original question of "Is this a use case for STI", it will be difficult to say if this is or not without seeing the full complexity of your environment. But here's are some things to consider:
Abridged from How (and When) to Use Single Table Inheritance in Rails - eugenius blog:
STI should be considered when dealing with model classes that share much of the same functionality and data fields, but you want more granular control over extending or adding to each class individually. Rather than duplicate code or forego the individual functionalities, STI permits you to use keep your data in a single table while writing specialized functionality.
You've simplified your example, but it sounds like competition and competition_instance are NOT essentially the same object, with minor behavioral differences. With what you've described, I would probably rename these objects to event and competition, or whatever makes better sense to you in terms of illustrating what these objects are actually representing. I think of 'The Olympics' as a generic event, and '100m Dash' as a competition taking place at The Olympics. Or "The SuperBowl" only hosted one competition in 2014, "SuperBowl XLIX".
You've also tagged this question with database-normalization, which STI won't help. If the attributes differ slightly between your shared objects you'll end up with null fields everywhere.
I refer you to the other answers here to see how you should probably set those objects up to behave as desired.
This is not a case for single table inheritance, because competition_instance is not a substitute for competition and 1 competition can have many competition_instances. So you have 3 tables:
competitions
sports
competition_instances
competition_instances has a foreign key to competitions because 1 competition can have many competition_instances but each competition_instance has exactly one competition.
Whether you attach sports to competitions or competition_instances depends on the specific constraints of your use case. I don't exactly know what you mean by "each competition/competition_instance can have 20-50 sport records". I would expect each competition_instance to have exactly one sport, so you might leave it at that, or you might attach a collection of sports to a competition as well, so that you can retrieve new competitions by sport before there is a competition_instance. I'd need more details on your use case to give you further advice.

Polymorphic association and foreign key reference can reference same table. How do I make a new object?

A simplified example of what I have is
class Apple < ActiveRecord::Base
has_many :bananas, as: :bananable, dependent: destroy
has_many :bananas
end
class Orange < ActiveRecord::Base
has_many :bananas, as: :bananable, dependent: destroy
end
class Banana < ActiveRecord::Base
belongs_to :bananable, polymorphic: true
belongs_to :apple
end
So a Banana may belong_to either an Apple or an Orange as bananable, but will belong to an Apple through a foreign key relation regardless. This may seem a bit forced in the context of fruit, but the actual models are quite complex, so I simplified things. Basically the polymorphic association defines the scope the Banana exists inside, but the Banana is part of an Apple regardless of it's scope.
The issue I am having is when I try to create a new banana. For example:
#Apple.bananas.new(valid_params)
This sets the apple_id foreign key column in the bananas table, but does not set the polymorphic association columns (bananable_id and bananable_type). The only way I have found to set both is to create a new banana as above, then manually set the polymorphic association columns before saving.
Is there a better way to do this?
Perhaps something like:
#banana = #Apple.bananas.new(valid_params)
#Apple.bananables << #banana
or for oranges
#banana = #Apple.bananas.new(valid_params)
#Orange.bananables << #banana
The best I have come up with so far is
#banana = #Apple.bananas.new(valid_params)
if params.has_key?(:orange_id)
#banana.bananable = #orange
else
#banana.bananable = #apple
end
#banana.save
You could try using the :inverse_of attribute in your Banana model. I know that the Rails docs say that this doesn't have any effect, but it does seem to have done something to help in my previous code. You might try, though I make no guarantees.
belongs_to :bananable, polymorphic: true, inverse_of: :bananas
From my notes, this helps Rails understand the relationship between the association and its inverse, which for me allowed it to correctly identify item type.
As another option, be sure to check that validations are not breaking your database updates. For testing, I had to use save! to ensure exceptions were thrown and I could catch where hidden issues were breaking my associations.
First of all clearly understand the polymorphic association. Ruby on Rails Guide states “with polymorphic associations, a model can belong to more than one other model, on a single association”.
I am sure that you don't need two association to the same table. Whenever you create a banana through apple, bananable_id and bananable_type won't be set because the rails find the association through apple_id and it doesn't bother about any other associations.
As far as I know, you are unnecessarily complicating your models.
I bet the below association can get all the functionalities you need.
class Apple < ActiveRecord::Base
has_many :bananas, as: :bananable, dependent: destroy
end
class Orange < ActiveRecord::Base
has_many :bananas, as: :bananable, dependent: destroy
end
class Banana < ActiveRecord::Base
belongs_to :bananable, polymorphic: true
end
Incase you have any valid points to say why you need two associations for same model, please comment.

Joining with named scopes in Ruby

I originally asked this question about acts_as_tree but it seems to be more general so I'm rephrasing it a bit here.
I have a model "category" using acts_as_tree. I often find myself needing to join it with other models for various queries, but I don't know how to use the provided scopes such as children, ancestors, siblings, descendants to create the conditions that I need.
Concrete example:
class Category < ActiveRecord::Base
acts_as_tree
has_many :photo
end
class Photo
belongs_to :category
end
cat = Category.first
How can I query for all the photos of children of cat or all the photos of its siblings?
Seems like I need something like:
Photo.joins(cat.children)
Photo.joins(cat.siblings)
which I know is not a valid code.
Since act_as_tree doesn't really use scopes but methods that return results, you can do it like:
class Photo
belongs_to :category
scope :from_category, -> (category) {
joins(:category).where(category: category)
}
end
cat = Category.first
Photo.from_category(cat.children)
Photo.from_category(cat.siblings)
And that should work.

Can a one to many relationship in rails create a parent object?

If I have an category class that has many products and each product would only have one category so that my models looked like this:
class Product < ActiveRecord::Base
belongs_to :category
end
and this:
class Category < ActiveRecord::Base
has_many :products
end
Then, from the belongs_to side of products, can I create a category_name in my Product Model using: create_category? How can I tell what auto-generated methods are available to me on the product side of things?
How can I tell what auto-generated methods are available to me on the product side of things?
By reading the corresponding documentation on api.rubyonrails.org (i.e. has_many and belongs_to). It tells you what methods are added.
In your case, you get my_product.create_category and my_category.products.create along many other methods.
you can used nested forms to create a category when a category doesn't exist or choose one if it already does exist within the new product create form.
This video from railscast should help
http://railscasts.com/episodes/196-nested-model-form-part-1

Association between one model and an attribute of another

I'm trying to create an association for a beta meat-sale application between one model, Cuts, and the "animal_type" attribute of another model, Animal, such that I could list all the cuts associated with a particular animal_type (or associated with an animal that has that type as an attribute).
In other words, if animal_type is "cow", I should be able to call up a list of all the cuts (ribeye, tenderloin, etc) associated with cows. I'm new to Rails, and this is fairly above my head.
My idea was to create an animal_type column in Cuts and Animals, to associate each cut with a type of animal, so I could do something along the lines of
#cuts = Cut.where(:animal_type => Animal::animal_type[:Cow])
No idea if that works, though, and what else I need to do to make this association possible. Can anybody help point me towards a way of thinking this through? Or does anyone have any good resources I could look at to help me with this specific problem? I've been looking through Rails Guides, and they're helpful, but they don't really give me a way to answer this.
You could have a Cuts model and an Animal model. Cuts could have a string attribute called "name" which would store the cut type such as ribeye, tenderloin etc. Animal could have a string attribute called animal_type. You could then setup a has_many association between Animals and Cuts. Something like this:
class Animal < ActiveRecord::Base
attr_accessible :animal_type
has_many :cuts
end
class Cuts < ActiveRecord::Base
attr_accessible :name
belongs_to :animals
end
This should be a good start