Consider the following polymorphic relationship
class Person
has_many :photos, :as => :attachable
end
class Animal
has_many :photos, :as => :attachable
end
class Photo
belongs_to :attachable, :polymorphic => true
end
The view pages of a Person or Animal object contains a gallery of their respective photos.
The pages contain the following code:
...
<%= link_to(image_tag(photo.image_url(:thumb).to_s), [#attachable, photo]) %>
...
However in the view page of my photo, I want to present different information depending on whether it belongs to a Person or an Animal. For example, if it is a Person it should display his/her height, list of pets; if it is an Animal, it should display its breed, owner, etc.
What is the best solution to do this?
I can think of two possible solutions:
One way could be to direct to a separate controller action in the photo for each class.
Something like
<%= link_to(image_tag(photo.image_url(:thumb).to_s), show_person_path[#attachable, photo]) %>
<%= link_to(image_tag(photo.image_url(:thumb).to_s), show_animal_path[#attachable, photo]) %>
However I think this approach introduces improper delegation of view logic in the polymorphic class.
Another approach is to introduce a controller action in each Person and Animal class to display their corresponding information. The gallery should link to this action instead of the one above, and the action should render the photo.
However I feel like the corresponding information to be displayed is only few to merit its own action for each of the class. Maybe helpers are enough, but I am not sure of the right approach (please correct me if the above assumptions are true).
If the differences between Animal and Person do not justify a separate controller, you could have one controller and in the action for the gallery, just render different views based on the type of your #attachable:
if #attachable.is_a? Person
render 'gallery_person'
elsif #attachable.is_a? Animal
render 'gallery_animal'
end
The only downside I see here is that you would need to come back and extend the logic whenever you introduce a new type of attachable. You could add a default gallery view to mitigate this.
Related
In application user can enter new post which contain title, content of the post and category of post. So creating new post will be through some simple html form with few fields. Now i don't know where to put logic for creating new post for following reasons:
Post(or posts collection) is object which is constructed from different tables, for example.
#posts = User.joins(entries: [{storage: :vote}, :category])
.where("votes.count > ?", 0)
.select("users.username AS username,
storages.id AS storage_id,
storages.title AS title,
storages.content AS content,
votes.count AS votes,
categories.category_name AS category_name")
.order("votes.count DESC")
So when user create new post application must create new entries in different tables:
1.Create new entry in entries table. (id, user_id, category_id)
2. Create new entry in storages table.(id, title, content, entry_id)
3. Create new entry in vote table.(id, count, storage_id)
In situation where post is model i can use something like resources: posts then in posts controller through new and create i can create new post, but what in situation like this where i don't need posts controller nor post model? So, question is: which place is more appropriate to put logic for creating new post? Q1
My solution is to craete Storages controller with resource: storages, :only => [:new, :create] then through new and create of this controller to populate different tables in db? I'm forcing here only because i dont see any point of other CRUD actions here(like showing all or one storage), because i will not use storages individually but in conjunction with other tables. So from views/storages through new.html.erb and create.html.erb i can construct new post? Q2
Another solution is to create Post controller which doesn't have "corresponding" post model as i stated before. Here i'm guessing i can't use restful routes(CRUD) because there is not :id of post? I only can make manually non-restful routes like:
post 'posts/create/:title/:content/:category' => 'posts#create', :as => 'create_post' then from params[:title], params[:content] and params[:category] to populate other tables. Q3
Im new to rails so dont yell please :D
This sound like a call for nested forms, its covered in a screen cast
here.
You use the resources of the top model, in this case Entry.
and drill down to the 3rd model.
Simple sample of what to do is bellow.
Model should look like so,
entry.rb
class Entry < ActiveRecord::Base
has_many :storages, :dependent => :destroy
accepts_nested_attributes_for :storages, :allow_destroy => true
end
storage.rb
class Storage < ActiveRecord::Base
belongs_to :entry
has_many :votes, :dependent => :destroy
accepts_nested_attributes_for :votes, :allow_destroy => true
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :storage
end
and the form should look like so, in simple_form style
<%= simple_form_for #entry do |f| %>
<%= f.simple_fields_for :storages do |storage_fields| %>
<%= storage_fields_for :votes do |vote_fields| %>
<% end %>
<% end %>
<% end %>
and if you have all the models set up, you shouldn't have to do anything to the controller.
This approach is also nice because you can add multiple storages and votes ajax style(without reloading the page) if needed, which is always nice.
I'd use a form class instead of nested attributes any day, see http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ for an example of this pattern (Chapter "3. Extract Form Objects")
I've used the pattern often enough to gemify it https://github.com/bbozo/simple_form_class and it's used roughly in this way: https://gist.github.com/bbozo/5036937, if you're interested to use it I'll push some docs
EDIT: reason why I tend to go the form class route most of the time is because nested attributes never failed to bite me in the end, either because strong parameter handling got cumbersome, or validators get too complicated (if persisted? else ...), or persistence logic needs to be extended to support some little knack that resolves into callback hell or recursive saves in the model (before/after save spaghetti troubles)
How can i authentically use rails relationships in my case?
I'm making a nightlife website, just for you to know the context. It has three models, i want to join on one page: Places, Albums, Photos.
So, photos in albums work great, i use there a custom-coded sql with joins.
But when i click on a place i want to see info from place model and a list of albums with a sample photo (first for example)
Now i have the following:
class Place < ActiveRecord::Base
has_many :albums
end
class Album < ActiveRecord::Base
has_many :photos
has_one :place
end
class Photo < ActiveRecord::Base
belongs_to :album
end
And there how i get it in my controller:
#place = Place.find( params[:id], :include => :albums )
and i get #place.albums as an object with albums data. But i dont know how to dig even deeper to reach thephotosmodel. And anywhay i can:include => :albumsbecause i have no direct relations betweenplaceandalbum, but i have them inalbum > photo` so sql join is needed?
That's a complicated question, but i just don`t get it fully.
Thank you for any help, maybe a good link to read!
First of all, the association between Place and Album isn't correct. The way you have it setup, it looks like neither would have a foreign_key. In other words, it should be Album belongs_to :place, with place_id in the albums table.
Second of all, to get the first photo for each album:
<% #place.albums.each do |album| %>
Sample Photo: <%= image_tag(album.photos.first.url) %>
<% end %>
If you really wanted to, you could get the first photo from the first album of a place in one line:
<%= image_tag(#place.albums.first.photos.first.url) %>
First note that this is prone to nil errors if you don't add in conditionals, so I don't actually recommend this method. But, if you were to use it, I would just add a method to the Place model:
def first_photo
albums.first.photos.first
end
And in your view:
<%= image_tag(#place.first_photo.url) %>
You'll want to join this stuff together as well so you're not performing too many queries. Wizard of Ogz answer covers that.
There is a hash syntax for nested includes. It also works for joins.
#place = Place.find( params[:id], :include => {:albums => :photos} )
Here is the documentation for this http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Look under the heading "Eager loading of associations"
I've been trying to switch my Orders model to a polymorphic association with my Product and Service models. However, I have a few questions that I haven't been able to find answers to, even after watching the RailsCast and reading the documentation (so, those suggestions are appreciated, but I need a more concrete answer).
Question:
Is a polymorphic association the best thing to use in this case? Prior to this, I was using a Transaction model that had multiple belongs_to associations and used a custom Parent function to determine which one it was. This was working fine, but someone suggested a polymorphic association may clean things up.
I set up the polymorphic association properly and have been unable to have the transactable_id and transactable_type automatically populated. The code is below. I have side-stepped this by manually putting them in inside the form, but if anyone knows the proper way to do it, that would be great!
How can I access elements with polymorphic associations? For example, in my Cart object (which has_many Transactions and which Transactions belongs_to) I can no longer access things using #cart.transactions.each do |t| ... #t.product.name type coding.
My model associations look like this:
class Order < ActiveRecord::Base
belongs_to :orderable, :polymorphic => true
end
class Product < ActiveRecord::Base
has_many :orders, :as => :orderable
end
My forms used to look like this:
<% form_for [#orderable, #order] do |f| %>
...
<% end %>
And were rendered like this in my Product Show view:
<%= render 'orders/form' %>
Now, I pass a variable for the product.id in the render partial and use it to populate the transactable_id field. But, I feel like that is very messy.
Again, I have read the tutorials and API docs and have been unable to solve this, so any help would be greatly appreciated!!
Answers to your questions:
If your business login implies that multiple models will have related model with the same fields so you should use polymorphic association. (In your case you can use it).
If set up polymorphic association Rails will automatically handle setting *_id and *_type fields depending on associated parent model.
Lets say you have Product with many orders in polymorphic association and you want to define which model order belongs to:
order = Order.first
order.orderable
I am trying to make an application wherein Users have many Items, and each Item they have through Possession is an entity in its own right. The idea behind this is if I have a MacBook item, eg, and a user adds it to their inventory, they may apply attributes (photos, comments, tags, etc) to it without directly affecting them Item itself, only their Possession.
The Item will in turn aggregate attributes from its corresponding Possessions (if you were to go to /item/MacBook, rather than /user/101/possession/5). I have the following models setup (ignoring attributes like photos for now).
class User
has_many :possessions
has_many :items, :through => :possessions
end
class Item
has_many :possessions
has_many :users, :through => possessions
end
class Possession
belongs_to :user
belongs_to :item
end
My first question is, am I doing this right at all. Is has_many :through the right tool here?
If so, how would I deal with class inheritance here? I might not be stating this right, but what I mean is, if I were to do something like
#possession = Possession.find(params[:id])
#photos = #possession.photos.all
and there were no photos available, how could it fall back to the corresponding Item and search for photos belonging to it?
Your initial data structure seems appropriate.
As for the second part, with the "fall back" to a corresponding item, I don't think there would be a direct Active Record way of doing this. This behavior seems pretty specific, and may be confusing to future developers working on your app unless you have a clear method for this.
You could create a method inside Possession like:
def photos_with_fallback
return self.photos if self.photos.size > 0
self.item.photos
end
There is a huge consequence to doing this. If you have a method like this, you won't be able to do any write activities down the wrode like #photos.build or #photos.create because you won't know where you're putting them. They could be linked to the Item or the Posession.
I think you're better of pushing the conditional logic out to your controller and checking for photos on the Posession first and then on the Item.
#In the controller
#photos = #posession.photos
#photos = #posession.item.photos if #photos.size == 0
This will be more clear when you go to maintain your code later, and it will allow you to make other decisions down the road.
I am trying to finish building a Rails "Home Inventory" app as an example to help me learn rails. Following gives a general overview of what I am trying to achieve:
The main purpose of this application is to show a page with detailed information.
So, http://localhost:3000/living-room-couch would display the information regarding the couch.
Each Item, can belong one( or has one?) of three categories:
Book
Furniture
Electronics.
Book has the following properties:
- isbn,
- pages,
- address,
- category
Furniture has following properties:
- color,
- price,
- address,
- category
Electronics has following properties:
- name,
- voltage,
- address,
- category.
--
Now on my View side, I have made 3 templates in rails, that contain elements suited to display an item belonging to one of the 3 categories. Template for Book shows isbn, and template for Electronics shows the voltage.
How do I model this in ActiveRecord? I will put it in English, maybe someone can help translate into Rails:
An Item, belongs_to or has_one category. Category can be one of the three: Book, Furniture, or Electronic.
I don't know how to do this. I know that each category like Book will be a model of its own, due to having different characteristics.
Do I need to have Category has a model too, because it would only consist of Book, or Furniture or Electronics. If I was to take the route of making Category a model of its own, how would I make it relate to a model such as Book.
--or
Would I just go this route (or join model perhaps):
class BookModel < ActiveRecord::Base
has_many :categories
End
And then, select to which category belongs, based on the Model name.
I hope I put the question right, I am just so confused regarding this.
Thank You for your time.
Why not have an Item have the following relationships:
belongs_to :book
belongs_to :furniture
belongs_to :electronic
You can then only actually set on of these, and can do a test of
if item.book
#do things
end
for each item, if you care about whether it's a book, etc. You can even use validations to make sure it only ever belongs to one thing at a time.
I don't think you need the category model. You can just create the book, furniture and electronic models and one controller for each one.
Edit
No, there's no advantage. I just said to create different models/controllers because what you're modeling, in my opinion, are different things. But if you think they'll have a lot in common, I'd suggest for you to use single table inheritance. This way you'd have a single table for every item and can have only one controller, but each item would have one model.
the Item model could be declared as polymorphic (se here: http://guides.rails.info/association_basics.html#polymorphic-associations)
so you'll declare Item:
class Item < ActiveRecord::Base
belongs_to :category, :polymorphic => true
end
then the 3 models that act as category (I'll show one, the others are same):
class Employee < ActiveRecord::Base
has_one :item, :as => :category
end
# and so on...
this way you can associate an Item to one of the 3 models (Book, Furniture and Electronics).
each instance of these models will have an 'items' atribute, like this:
#book.items # returns all the items associated to a #book object
You can also use has_many association using a polymorphic model.
For the views, you can use Nested Form Objects (see here: http://guides.rails.info/form_helpers.html). basically, in each form, you should nest a form to create an Item object. to follow the Book example, you'll have something like this:
<%= form_for :book, #book do |form| %>
<%= form.text_field :isbn %>
<!-- other fields -->
<%= fields_for #book.item do |item_form| %>
<%= item_form.text_field :name %>
<% end %>
<% end %>
hope this helped you ;)