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 ;)
Related
I think this is a lot simpler than the title probably lets on. Here are my three models with the associations:
Update: associations were incorrect previously. Here are the corrected associations:
#app/models/call_service.category.rb
class CallServiceCategory < ActiveRecord::Base
has_many :call_services
end
#app/models/call_service.rb
class CallService < ActiveRecord::Base
belongs_to :call_service_category
has_many :calls
end
#app/models/call.rb
class Call < ActiveRecord::Base
belongs_to :call_service
end
So I have a group of call_ids for the calls I want:
#call_ids = [1,2,3,4]
Initial step which works:
What I want to do is grab only the calls with the ids specified in #call_ids. Then, I want to eager load only the associated call_services for those grabbed calls. The following does this perfectly:
#call_ids = [1,2,3,4]
#calls_by_service = CallService.includes(:calls).where("calls.id IN (?)", #call_ids).references(:calls)
This is great. Now I can iterate through only those selected calls' call_services, and I can even list all of those selected calls per service like so:
<% #calls_by_service.each do |call_service| %>
<p> <%= call_service.description %> </p>
<% call_service.calls.each do |call| %>
<%= call.name %><br>
<% end %>
<% end %>
What is great about this too is that #calls_by_service does not contain ALL of the call_services, but instead only those call_service records associated with the calls specified in #call_ids. Exactly what I want at this level.
One Level Deeper which is where I am having trouble:
This is great but I need to go one level deeper: I want to display only the associated call_service_categories for the associated call_services of those selected calls specified by #call_ids.
In other words: I want to grab only the calls with the ids specified in #call_ids. Then: I want to eager load only the associated call_services for those grabbed calls. Then: I want to eager load only the associated call_service_categories for those grabbed calls.
A visual of the structure is like this:
So I want to be able to iterate through those associated call_service_categories (ex: 'Emergency Relief', 'Employment'), and then iterate through the associated call_services of those calls specified in the #call_ids, and then display those calls per service.
I figured out one level (by call_service), now I just need to figure out one level deeper (by call_service_category).
In the rails guides, I attempted looking at the section on specifying conditions on eager loaded associations. I was not having success, but I think the answer is in that section.
Any help is much appreciated. Thanks!
One of the belongs_to associations (in CallService or Call) should be actually a has_one (one-to-one relationship – belongs_to on the one side and has_one on the other). Apart from that, you can try the following code to produce a chained query with left joins and retrieve fields from all 3 tables:
CallServiceCategory.includes(call_services: :calls)
.where(calls: {id: #call_ids})
.references(:call_services, :calls)
I've noticed that you have a through association on your CallServiceCategory model, but as there would be no :call_services in includes, you can't reference fields from CallService model in references (you can, but they just won't appear in the actual sql query).
I have a category model, which is basically what products will be assigned to.
So my category model looks like this:
attr_accessible :name
has_many :category_products do
def with_products
includes(:product)
end
end
has_many :products, :through => :category_products
What I would like to do is to have categories within categories - so a product should be able to be put in Men and then Shoes.
That way, I can have a Men drop-down menu, that produces all the subcategories within Men like Shoes.
But, if a user clicks on Men they will see all the products in that category.
Thoughts?
You can use a gem like Ancestry to organize your categories into a hierarchy.
If you only need one level deep of nesting, you can use a self-join.
belongs_to :parent_category, class: "Category"
has_many :subcategories, class: "Category"
You'll also need a migration to add category_id to your category table.
Stick a drop down in your app/views/categories/_form.html.erb for category_id and populate it with your list of Categories. You can use this to select a "Parent" for your categories.
Then you can do stuff like:
# Get parent category's name
<%= somecategory.parent.name %>
# Iterate through subcategories
<% someothercategory.subcategories.each do |category| %>
<%= category.name %>
<% end %>
Using a gem like Ancestry will give you a lot more flexibility when it comes to working with these categories and their associations though.
A bit about how Ancestry works:
Ancestry stores a path from the root to the parent for every node.
This is a variation on the materialised path database pattern. It
allows Ancestry to fetch any relation (siblings, descendants, etc.) in
a single SQL query without the complicated algorithms and
incomprehensibility associated with left and right values.
Additionally, any inserts, deletes and updates only affect nodes
within the affected node’s own subtree.
I don't know how Ancestry gem works, but in nested solutions the main concern is - speed.
If the model Category doesn't subject to a hi-freq creation, try awesome_nested_set. It uses not only parent_id, but :lft and :rgt columns which are significantly speed up the searching of mutually nested objects, when children are very, very much... 30-50 categories and thousands of products - one of that case.
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.
You have a set of related models created through a scaffold e.g. a house, which has many rooms, which each have many windows, which each has a selection of locks.
These resources are already full of data i.e. someone has entered all the information, such as: a room called 'kitchen' has various windows associated with it and these windows each have five different locks associated with them.
Someone comes along and says:
Can you create a form that lets someone create a new project where they can select the different rooms, windows and then specify the locks that they would like for that project? (these are already in the system, nothing new to add, just the associations to a new project)
This sounds like a nested form but I have wasted a lot of time trying to solve this - there are many levels of nesting, which make this tricky. Any suggestions?
session based solution
With such deeply nested models select box on the front end wouldn't be enough...
Assuming this, you may want to create a current_house who's id live in a session (just like current_user works).
Once you have your current_house add different items by navigating to your list of items view and clicking on the add_to link :
# house_controller.rb
def add_to
current_house.polymorphic_items << Kitchen.find(params[:id])
redirect_to :back
end
But there are many approaches to this session based solution which sort of implements a cart/order system. You may want to add a current_item to add stuff in each leaf of your tree aka room of your house.
E.G after clicking on the kitchen you just added :
before_filter :set_current_item
def add_to
current_item.windows << Window.find(id)
end
current_item beeing polymorphic : a living room, a bathroom etc.
But how you implement that precisely depends on your Domain Model....
As a rule of thumb regarding nested forms I'd follow rails guidance for routes : don't go deeper than one level or you'll end up in a mess.
Yes this is a nested form. Railscasts nested forms is a great place to start.
If everything is already in the system you probably just want select boxes so they can select what they want. Also check out the .build method. If you have multiple levels of nesting you can also manually set the association by passing in the foreign key yourself.
I think you can model this with a single level of nested attributes, given the models below (based on Windows/Locks pre-existing and a room just needing to mix and match them into a set of windows with given locks):
class House < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :house
has_many :window_configs
end
class WindowConfig < ActiveRecord::Base
belongs_to :room
belongs_to :window
belongs_to :lock
end
class Lock < ActiveRecord::Base
has_many :window_configs
end
class Window < ActiveRecord::Base
has_many :window_configs
end
... based on that model setup, you could have a single house form that you dynamically add child 'room' definitions to that each have a name and a collection of window_configs which have two select boxes for each one (choose a window definition and then a lock definition). Because you're dynamically adding multiple rooms with multiple windows, you'd need some JS to populate new form elements, but it could all live in a single nested form.
form_for :house do |form|
# Dynamically add a Room form for each room you want with js
form.fields_for :room do |room_attributes|
room_attributes.text_field :name
# Dynamically add window_config forms on Room w/ JS
room_attributes.fields_for :window_config do |window_attributes|
window_attributes.select :window_id, Window.all
window_attributes.select :lock_id, Lock.all
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