I created a joined table for the models category and products (both created with scaffold). The Product model is this:
class Product < ActiveRecord::Base
belongs_to :category
def category_id
category.id if category
end
def category_id=(id)
self.category = Category.find_by_id(id) unless id.blank?
end
end
and the category model is this:
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
end
In the form.html.erb I create a dropbox with all the classes for the user to choose from:
<p>
<label for="product_category_id">Category:</label><br />
<%= f.collection_select :category_id, Category.find(:all), :id, :name, :prompt => "Select a Category" %>
</p>
Yet when I take a look at the show of the product:
<p>
<b>Category:</b>
<%= #product.category_id %>
</p>
or the list of the products (index.html.erb):
<td><%= product.category_id %></td>
There's no category. Just blank. I don't get it. Is something wrong with the category_id method or the association?
Firstly, you don't need the explicit category_id and category_id= methods. ActiveRecord will handle those for you for a belongs_to association.
Secondly, there seems to be a mismatch between whether you want a has_and_belongs_to_many or a has_many/belongs_to association. If you have a join table then you have the former, in which case both sides of the association should be declared with has_and_belongs_to_many. If you are just using a category_id on the products table then the other end of your association on Category should be has_many :products.
With a join model:
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :product
end
you would define in your Product class:
has_many :categorizations
has_many :categories, :through => :categorizations
Then, because your association is a 'many' association, you do not get a .category method on a product. You do however get a categories method (plus several more methods - look at the has_many documentation). If you name your collection_select category_ids then it should work as expected. You may also want to add the 'multiple' option to the select in order to choose more than one category.
Your association is obviously incorrect. As pointed out, the category has_many products. And in case you want to use a many-to-many relationship you're strongly advised to use the has_many :through relationship.
Related
I am stuck struggling with rich join tables in Rails 5 and need help getting on track. The app I'm writing will help me track which of our company's suppliers carry which brands of products. Because I also need to track whether each supplier is authorized or unauthorized for each of the brands they sell, and whether they carry those brands in stock, I thought the best approach was to use a join table and store the attributes there. In other words:
Suppliers <---> Lines <---> Brands
Beyond the foreign key references for a Supplier and a Brand, the Line record also has two boolean attributes: .is_authorized and .carries_stock.
My models:
/models/supplier.rb
class Supplier < ApplicationRecord
has_many :lines, :dependent => :destroy
has_many :brands, :through => :lines
accepts_nested_attributes_for :lines
end
/models/brand.rb
class Brand < ApplicationRecord
has_many :lines, :dependent => :destroy
has_many :suppliers, :through => :lines
end
/models/line.rb
class Line < ApplicationRecord
belongs_to :supplier
belongs_to :brand
validates_presence_of :supplier
validates_presence_of :brand
end
I've been able to set up the controller and supplier edit form to allow creating records in the Lines table, but have no clue how to allow the users to edit the .is_authorized and .carries_stock attributes. I have been able to get the create/edit supplier form to work by adding the following snippet:
/views/suppliers/_form.html.erb
<h4>Brands</h4>
<%= form.collection_check_boxes(:brand_ids, Brand.all, :id, :name) do |b| %>
<%= b.label class:"label-checkbox" do%>
<%= b.check_box + b.text%>
<%end%>
<br />
<% end %>
The form looks like this now but doesn't allow me to edit the rich attributes .is_authorized and .carries_stock. I'd like the form to look something more like this. Where do I go from here?
Thanks!!!
I have the following Rails Model Relation:
class Grade < ApplicationRecord
has_and_belongs_to_many students, join_table: :grade_student_mappings
has_many :students
end
class Student < ApplicationRecord
has_and_belongs_to_many :grads, join_table: :grade_student_mappings
belongs_to :grade
end
Now when I want to access the grade table and the student table using the join_table - rails will fail.
Example: Grade.find_by_id(1).student will always response me the content of the direct relation between Grade and Student. The Relation via the join_table will be ignored.
Only when I uncomment the model line has_many and belongs_to then the indirect relation via the join_table will be considered.
How I can say to RAILS, which relation between Grade and Student I want to use ?
Rails supports two kinds of many-to-many relationships. has_many through and has_and_belongs_to_many. You are mixing the two. You need to pick one and the more flexible one is has_many through. Change your model files to the following:
# app/models/grade_student_mapping.rb
belongs_to :grade
belongs_to :student
# app/models/grade.rb
has_many :grade_student_mappings, dependent: :destroy
# app/models/student.rb
has_many :grade_student_mappings, dependent: :destroy
has_many :grades, through: :grade_student_mappings
Then you can access the student's grades on their show template for example by setting the grades instance variable in the students_controller show action
# app/controllers/students_controller.rb
def show
#grades = #student.grades
end
And display the grades on the student's show page. Something like the below:
# app/views/students/show.html.erb
<% #grades.each do |grade| %>
<%= grade.semester %>
<%= grade.course %>
<%= grade.letter %>
<% end %>
I have 3 Rails 4.2 tables: books, tags, categorizations where categorizations is a many to many join table for the other two.
Inside the show.html.erb file, I can say
<%= #categorization.book.title %>
and the book title will be displayed, but inside index.html.erb, if I say something similar like
<%= categorizations.each do |categorization| %>
<%= categorization.book_id %> # this is ok
<%= categorization.book.title %> # not ok
...
<% end %>
The error message is
undefined method `title' for nil:NilClas
I'm sure there is a way to get rails to do this, but I am not doing it right. Is there a way to ask the controller to do a join using all three tables instead of just the join table? Or do I need to instantiate a book object inside the loop (sounds ugly, but ...)?
It sounds like you're missing the ActiveRecord Associations in your models. In order to have a book accessor on your categorization model, you would need to define the association in the models. From what you say above, it sounds like you would want to create a has_many :through association.
See the Active Record Associations documentation for more details. I imagine it would look something like this:
class Book < ActiveRecord::Base
has_many :categorizations
has_many :tags, through: :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :book
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :categorizations
has_many :books, through: :categorizations
end
Once you have the associations in place, you should be able to do #categorization.book without issue.
I have a project-specific question. I'm trying to determine the most efficient and logical way of setting up my models in my app. The relevant players to model here are: Vendors, Vendor Inventories, and Products. Here is a breakdown of what each item should be able to return in some way:
Inventories:
Store ID
Products and associated details (Price, Name, Brand, Details, Product Code)
Vendors: Store ID, Location, Name
Products: Price, Name, Brand, Details, Product Code
Obviously there is a lot of duplication in this scheme between products and inventories. My issue is that while vendors might have similar items in their inventories, the price will always be different. So I can't simply relate the models by product code. Because most vendors will have the same products, if I were to model store inventories with all of the product information, wouldn't that be a lot of duplication? It's also possible that I don't need to have a separate model for vendors and could just try and keep it all inventories, but I'm lost. Help please?! Thanks in advance.
EDIT:
Here is my model structure, though I'm not sure it's ideal.
class Vendor < ActiveRecord::Base
attr_accessible :name, :address
has_one :inventory
has_many :products, through: :inventories
end
class Inventory < ActiveRecord::Base
has_many :products
belongs_to :vendor
end
class Product < ActiveRecord::Base
attr_accessible :upc, :brand, :product, :details, :price
has_many :inventories
has_many :vendors, through: :inventories
end
I'm not sure if I understand your specific needs, especially if the set of models are just for example, but judging from the names, here would be my implementation.
A Vendor is the main entity, it will own many products through the join table inventory,
class Vendor < ActiveRecord::Base
attr_accessible :name, :address
has_many :inventories
has_many :products, through: :inventories
end
However there are aspects of the product that need to differ between Vendors, in this case, price and quantity( I just made that up), which can be stored in the join table, since it would be independent and made for each product and vendor relationship, is ideal for storing info like price and quantity.
class Inventory < ActiveRecord::Base
attr_accessible :product_id, :vendor_id, :price, :quantity
belongs_to :product
belongs_to :vendor
end
Product should only have attributes that would be universal regardless how the vendor handles it, likewise if the product where to be changed, it should be okay with all the vendors.
class Product < ActiveRecord::Base
attr_accessible :upc, :brand, :product, :details
has_many :inventories
has_many :vendors, through: :inventories
end
Doing this you may have difficulties editing this in the view, setting prices and quantity on inventory. Again, my way of doing it would be to use nested fields and interact directly with the join table, and not products, here's some quick partial code using simple_form gem,
on you form,
<%= f.simple_fields_for :inventories do |inventory_fields| %>
<%= render partial: "inventory_fields", locals: {f: inventory_fields }%>
<% end %>
<%= link_to_add_fields "Add", f, :f %>
and in inventory_fields.html.erb
<%= link_to "Remove", '#', class: "remove_fields btn btn-danger" %>
<%= f.input :product_id, collection: Product.all.somethingsomething, include_blank: false %>
<%= f.input :price %>
<%= f.input :quantity %>
Now you can choose your products from a dropdown, and set price and quantity, and whatever you need dynamically.
I have three tables via many-to-many-association: Supermarket, Product and Supply.
Each Supermarket can hold many products and each product can be sold in many supermarkets. The association is build via the Supply-model.
Supermarket:
class Supermarket < ActiveRecord::Base
attr_accessible :name, :address, :products_attributes
has_many :supplies
has_many :products, :through => :supplies
accepts_nested_attributes_for :products
end
Product:
class Product < ActiveRecord::Base
attr_accessible :name, :supermarkets_attributes
has_many :supplies
has_many :supermarkets, :through => :supplies
accepts_nested_attributes_for :supermarkets
end
Association via Supply:
class Supply < ActiveRecord::Base
attr_accessible :supermarket_id, :product_id
belongs_to :supermarket
belongs_to :product
end
I have created the scaffolds and populated the Supermarket-table.
In my Product form, i want to use one (or more) drop-down-menu(s) to select the correspondent Supermarket-name(s). Goal is to create a new product while also creating the association via the Supply-table.
What should the code look like in form and/or controller for the products if I want to select the corresponding supermarkets from there?
In you products form you need to add this line...
<%= collection_select(:product, :supermarket_ids, SuperMarket.all, :id, :name, {}, { :multiple => true } )%>
You also shouldn't need to use an accepts_nested_attributes for this, the many to many association you already have set up should take care of the rest.
I think in views
<%= f.collection_select "super_market_ids[]",#super_markets,:id,:name,{},{:multiple=>"multiple'} %>
I am not sure about super_market_ids or super_market_ids[] and syntax just verified once.
In select tag if you want checkbox type multi select there is an chosen library that will help you to build nicer UI,