What's the best way to do this? I want to be able to give Bands and Artists genres through polymorphism. I can do it with habtm and has_many :through but I'm trying to figure out if it's possible through polymorphism.
GenreList would be a lookup table with a list of different genres (e.g. Punk, Pop, Metal). I've reviewed Ryan Bate's screencast for Polymorphic Assoiciations but I'm still stuck. Specifically, I'm not sure how to create the polymorphic table Genre which would be fed canned genres from the GenreList model (the lookup table).
Is the following correct?
rails generate model Genre genre_list_id:integer genreable_id:integer genreable_type:string
class Artist < ActiveRecord::Base
has_many :genres, :as => :genreable
end
class Band < ActiveRecord::Base
has_many :genres, :as => :genreable
end
class Genre < ActiveRecord::Base
belongs_to :genreable, :polymorphic => true
end
class GenreList < ActiveRecord::Base
end
I think your implementation is a little bit weird. The way I would do it is to create a model Genre (which it will hold all the available genres Punk, Rock, Metal etc). Then I would do all this that you've already done but without the GenreList model:
rails g model Genre genreable_id:integer genreable_type:string genre_name:string
class Artist < ActiveRecord::Base
has_many :genres, :as => :genreable
end
class Band < ActiveRecord::Base
has_many :genres, :as => :genreable
end
class Genre < ActiveRecord::Base
belongs_to :genreable, :polymorphic => true
end
Then I would do make some nested resources in my routes as:
resources :artists do
resources :genres
end
resources :bands do
resources :genres
end
and then edit my controller to handle this nested relation. With this approach say if i want to see all the genres of the first artist I would visit:
/artists/1/genres
same holds for bands. I hope that I understood your problem. Let me know if I helped!
Ok, after 6.5 hrs, I managed to figure this out. I used the inherited_resources gem to help with the controllers. To recap, I wanted to be able to add Genres to Artists and Bands through a polymorphic relationship, i.e. Genres would be a lookup table, and Genreings would be a polymorphic model that contains genres for Artists and Bands. Below is the code that worked for me:
# Generate some scaffolding
rails generate scaffold Artist name:string
rails generate scaffold Band name:string
rails generate scaffold Genre name:string
rails generate scaffold Genreing genre_id:integer genreable_id:integer genreable_type:string
# Models
class Artist < ActiveRecord::Base
has_many :genreings, :as => :genreable
has_many :genres, :through => :genreings
end
class Band < ActiveRecord::Base
has_many :genreings, :as => :genreable
has_many :genres, :through => :genreings
end
class Genre < ActiveRecord::Base
attr_accessible :name
has_many :genreings
end
class Genreing < ActiveRecord::Base
attr_accessible :genre, :genre_id, :genreable, :genreable_type, :genreable_id
belongs_to :genre
belongs_to :genreable, :polymorphic => true
end
# Controller
class GenreingsController < InheritedResources::Base
belongs_to :genreable, :polymorphic => true
end
# Artist Form View
= simple_form_for(#artist) do |f|
.inputs
= f.input :name
= f.association :genres, :as => :check_boxes
.actions
= f.button :submit
# Band Form View
... (Similar to Artist)
It is correct. One thing that seems to be missing is the has_many relationship from GenreList to Genre
class GenreList < ActiveRecord::Base
has_many :genres
end
Related
I have a polymorphic association like so (adapted from guides.rubyonrails.com):
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
has_many :employees
end
Is there a way to get all of the possible :imageable_types only given the Picture model?
For example to get the class of has_many :quotes in the Product model, you would do:
Product.reflect_on_association(:employees).klass
to get: # => Employee
Now I want to do something similar:
Picture.reflect_on_association(:imageable).klass
This obviously throws an exception, but I want to get something like: # => [Employee, Product]
Is there a way to do this? (Without trying out all models to see if they contain has_many :pictures)
I couldn't find a way to do this without looking at all the models, so I just adapted this solution: https://stackoverflow.com/a/2315469/1440599
I have this relationship between categories, products & brands:
class Brand < ActiveRecord::Base
has_many :products
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
belongs_to :brand
end
How can I select all categories by specified brand with this relations?
I try this but get an error
b = Brand.find(1)
Category.joins(:products).where(:products => b.products)
You did the right thing with the join, just add a more complex where definition:
Category.joins(:products).where(:products => {:brand_id => 1})
Controversially HABTM's are rarely, if ever, a good design and IMO just about the only thing Rails got wrong.
Introduce an xref table to join products and categories and use has_many :through on both sides of the relationship so you end up with
class Brand < ActiveRecord::Base
has_many :products
has_many categories :through => products # This is now allowed in Rails 3.x and above
end
class Category < ActiveRecord::Base
belongs_to :product_category
has_many :products :through => product_category
end
class Product < ActiveRecord::Base
belongs_to :brand
belongs_to :product_category
has_many :categories :through => product_category
end
class ProductCategory < ActiveRecord::Base
has_many :products
has_many :categories
end
This gives you the best flexibility with the least amount of code re-factoring for you plus a much more intuitive path to get whatever data you need on either side of the relationship and will enable you to achieve the following
b = Brand.find(1)
b.categories.all
Update
The above is totally untested code and I have just corrected a glaringly stupid mistake I made. If you have any issues implementing this then come back
I have the following models and running rails 3.01:
# file: app/models/product.rb
class Product < ActiveRecord::Base
has_many :categories, :through => :product_categories
has_many :product_categories, :dependent => :destroy
accept_nested_attributes_for :product_categories
end
# file: app/models/category.rb
class Category < ActiveRecord::Base
has_many :products, :through => :product_categories
has_many :product_categories, :dependent => :destroy
end
# file: app/models/product_category.rb
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
ProductCategory is my join table. What do I call in my products form? Do I build on the Categories table or the ProdcutCategories table? Im just really confused on how/which models Im supposed to nest in my products from. Thanks!
The model is already configured to accept attributes for the products_categories association. In your form just reference it like this:
<%= f.fields_for :products_categories do |pc| %>
# fields go here
Remember that you will need to build new objects for the products_categories association before this form will render anything:
products.products_categories.build
I have 3 associated models:
class Brand < ActiveRecord::Base
has_many :car_models
end
class CarModel < ActiveRecord::Base
has_many :production_years
belongs_to :brand
end
class ProductionYear < ActiveRecord::Base
belongs_to :car_model
end
So, how i can make custom filter in ActiveAdmin production_year section, if i want make filtering by Brand? Default filters there: car_model select and year value
Did you try something like this?
ActiveAdmin.register ProductionYear do
filter :brand, :as => :check_boxes, :collection => proc { Brand.all }
end
EDIT oops I didn't notice the complexity of your association, I think if you add this to your ProductionYear class things should work better:
class ProductionYear < ActiveRecord::Base
belongs_to :car_model
has_one :brand, :through => :car_model
end
A Person can have many Events and each Event can have one polymorphic Eventable record. How do I specify the relationship between the Person and the Eventable record?
Here are the models I have:
class Event < ActiveRecord::Base
belongs_to :person
belongs_to :eventable, :polymorphic => true
end
class Meal < ActiveRecord::Base
has_one :event, :as => eventable
end
class Workout < ActiveRecord::Base
has_one :event, :as => eventable
end
The main question concerns the Person class:
class Person < ActiveRecord::Base
has_many :events
has_many :eventables, :through => :events # is this correct???
end
Do I say has_many :eventables, :through => :events like I did above?
Or do I have to spell them all out like so:
has_many :meals, :through => :events
has_many :workouts, :through => :events
If you see an easier way to accomplish what I'm after, I'm all ears! :-)
You have to do:
class Person < ActiveRecord::Base
has_many :events
has_many :meals, :through => :events, :source => :eventable,
:source_type => "Meal"
has_many :workouts, :through => :events, :source => :eventable,
:source_type => "Workout"
end
This will enable you to do this:
p = Person.find(1)
# get a person's meals
p.meals.each do |m|
puts m
end
# get a person's workouts
p.workouts.each do |w|
puts w
end
# get all types of events for the person
p.events.each do |e|
puts e.eventable
end
Another option of this is to use a Single Table Inheritance (STI) or Multi Table Inheritance (MTI) pattern, but that requires some ActiveRecord/DB Table rework, but this may help others still finding this who are designing it for the first time.
Here is the STI method in Rails 3+:
Your Eventable concept becomes a class and needs a type column (which rails automatically populates for you).
class Eventable < ActiveRecord::Base
has_one :event
end
Then, your other two classes inherit from Eventable instead of AR::Base
class Meal < Eventable
end
class Workout < Eventable
end
And your event object is basically the same, just not polymorphic:
class Event < ActiveRecord::Base
belongs_to :person
belongs_to :eventable
end
This may make some of your other layers more confusing, if you've never seen this before and you're not careful. For example, a single Meal object can be accessed at /meals/1 and /eventable/1 if you make both endpoints available in the routes, and you need to be aware of the class you're using when you pull an inherited object (hint: the becomes method may be very useful if you need to override the default rails behavior)
But this is a much cleaner deliniation of responsibilities as apps scale, in my experience. Just a pattern to consider.