How to use a many to many relationship - ruby-on-rails

I have a many to many relationship between businesses and categories
class Business < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :businesses
end
How would I create a business with 2 associated categories?
cat1 = Category.create(name: 'cat1')
cat2 = Category.create(name: 'cat2')
biz = Business.create(name: 'biz1'....

One option would be to use accepts_nested_attributes_for, like this:
class Business < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :categories
accepts_nested_attributes_for :businesses_categories
end
class Category < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :businesses
end
class BusinessesCategories < ActiveRecord::Base
accepts_nested_attributes_for :categories
end
You'd then be able to create your form like this:
<%= form_for #business do |f| %>
<%= f.fields_for :businesses_categories do |b| %>
<%= b.fields_for :categories do |c| %>
<%= c.text_field :cat %>
<% end %>
<% end %>
<% end %>
To do this, you'd have to build the category objects in the controller:
#app/controllers/businesses_controller.rb
def new
#business = Business.new
2.times do { #business.categories.build }
end
Or you'd have to call from a separate function to input the category data into their own table with the business_id set to the one you want

This is the Anothe way of specifying the many to many relationship
class Business < ActiveRecord::Base
has_many :business_categories
has_many :categories, through: :business_categories
end
class Category < ActiveRecord::Base
has_many :business_categories
has_many :businesses, through: :business_categories
end
class Business_category < ActiveRecord::Base
belongs_to :categories
belongs_to :businesses
end
see the link below:: http://guides.rubyonrails.org/association_basics.html

Related

Cannot access attributes from associated model rails

I have three models, ingredient, recipe_ingredient and recipy
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
end
class RecipeIngredient < ApplicationRecord
belongs_to :recipy, :dependent => :destroy
belongs_to :ingredient
end
class Recipy < ApplicationRecord
has_many :recipy_steps
has_many :recipe_ingredients, :dependent => :delete_all
end
I am trying to access the ing_name attribute in the ingredients table from recipies show page.
<% #recipe_ingredients.each do |ing| %>
<p> <%= ing.amount %> <%= ing.unit %>
<%= ing.ingredient.ing_name %>
</p>
def Show from the recipies controller:
def show
#recipe_ingredients = #recipy.recipe_ingredients
end
But I keep receiving the following error msg:
undefined method `ing_name' for nil:NilClass
My ingredient_params:
def ingredient_params
params.require(:ingredient).permit(:ing_name)
end
It does seem to work like this:
<%= Ingredient.where(id: ing.ingredient_id).pluck(:ing_name) %>
But this does not use the connection between the tables if I understand correctly? Any help? Thanks.
You have ingredient nil thats why you got the error.
Must be your controller has some before_action hook to load recipy
class RecipesController < ApplicationController
before_action :load_recipy, only: :show
def show
#recipe_ingredients = #recipy.recipe_ingredients
end
private
def load_recipy
#recipy = Recipy.find(params[:id])
end
end
You can try this to avoid this nil error(undefined method 'ing_name' for nil:NilClass)
<% #recipe_ingredients.each do |ing| %>
<p> <%= ing.amount %> <%= ing.unit %>
<%= ing.try(:ingredient).try(:ing_name) %>
</p>
From Rails 5 by default you got one required option to make ingredient always not nullable
like below
belongs_to :ingredient, required: true
It will also prevent this error of
class RecipeIngredient < ApplicationRecord
belongs_to :recipy, :dependent => :destroy
belongs_to :ingredient, required: true
end
the problem is because inside your show method #recipy is nil,
here is usually code for show
controller
def show
#recipy = Recipy.find(params[:id]) # --> you missed this line
#recipe_ingredients = #recipy.recipe_ingredients # #recipy will be null without line above
end
view
<% #recipe_ingredients.each do |ing| %>
<p> <%= ing.amount %> <%= ing.unit %> <%= ing.ingredient.ing_name %> </p>
<% end %>
I would like also add some suggestion to your model relationship as follow since Ingredient and Recipy shows many to many relationship
class Ingredient < ApplicationRecord
# -> recipe_ingredients -> recipies
has_many :recipe_ingredients, :dependent => :destroy
has_many :recipies, through: :recipe_ingredients
end
class RecipeIngredient < ApplicationRecord
belongs_to :recipy
belongs_to :ingredient
end
class Recipy < ApplicationRecord
has_many :recipy_steps
# -> recipe_ingredients -> ingredients
has_many :recipe_ingredients, :dependent => :destroy
has_many :ingredients, through: :recipe_ingredients
end

Nested forms with has_one :through

I'm having some trouble to implement a nested form with a has_one :through association.
Models
# model: member.rb
belongs_to :user
has_one :academic
# model: user.rb
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member, reject_if: :all_blank
accepts_nested_attributes_for :academic, reject_if: :all_blank
# model: academic.rb
belongs_to :member
belongs_to :user
Controller
# users_controller.rb
def new
#user = User.new
#user.build_member
#user.build_academic
end
I also have tried with:
#user.member.build_academic
View
# new.html.erb
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |ff| %>
<%= ff.text_field :email %>
# member belongs to user so I can call a fields_for
<% ff.fields_for :member do |f| %>
<%= f.text_field :name %>
# this part is not shown. What is wrong with my association?
<% f.fields_for :academic do |a| %>
<%= a.text_field :major %>
<% end %>
<% end %>
<% end %>
I've taken a look into the Rails documentation. The first fields_for is shown in the page (:member), but the second one (:academic), which has the has_one :through association, is not shown in the page.
Any help will be appreciated. Thank you.
Through
If you want to build your data through a relation, you have to pass the associated data as it's constructed:
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
has_one :academic
accepts_nested_attributes_for :academic
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :member
belongs_to :user
end
This will allow you to do the following:
#app/controllers/members_controller.rb
class MembersController < ApplicationController
def new
#member = Member.new
#member.build_member.build_academic
end
def create
#member = Member.new member_params
#member.save
end
private
def member_params
params.require(:member).permit(:x, :y, :z, academic_attributes: [:some, :attributes, member_attributes:[...]])
end
end
This would permit the following:
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :member do |m| %>
<%= f.text_field :name %>
...
<%= m.fields_for :academic do |a| %>
<% a.text_field :name %>
...
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This works to build a new member object, and a new academic object from the user. Although not strictly what you're asking, it looks like it could benefit you in some form.
Associations
If you want to do what you're asking (IE build_member and build_academic exclusively), you'll need to get rid of the has_one :through relationship...
#app/models/user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :academics, through: :memberships
end
#app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :academic
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
The problem is you're basically trying to build a relationship for a direct association (member) and an indirect relationship (academic).
If you want to build both exclusively, you have to make them have a direct association with your main model. The above should allow you to do the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.members.build.academics.build
end
end
Much like my top example, this will pass nested data through your form - if you wanted to have it completely exclusively, do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic
has_and_belongs_to_many :academics
end
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :users
end
This will allow the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.build_member
#user.build_academic
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:user, :params, member_attributes: [], academic_attributes:[])
end
end
For future reference or for anyone that has the same problem, I found a way around to fix this problem, in case you have same structure.
I could fix this by going to the view, before the form, and writing:
<% resource.member.build_academic %>
In my case resource is User, set by devise, a Rails gem used for authentication.
And you don't need to reference any :through or whatsoever in your model.
It is not the most efficient way, but I haven't found any other solution. Hope it helps.

Rails where to create :through object

Hi I`m learning has_many :through and I have a association like this.
student:
has_many :subjects, through: :participations
has_many :participations
subject:
has_many :students, through: :participations
has_many :participations
belongs_to :student
participation:
belongs_to :student
belongs_to :subject
The student subjects are updated through checkboxes in update view:
= f.association :subjecs, label_method: :title, value_method: :id, label: 'Subjects', as: :check_boxes
And I went so far :( My student have subjects id, but it can`t get them since no participation is created.
My update action:
def create
student = Student.new(student_params)
if student.save
redirect_to students_path
else
render 'edit'
end
end
My question is when should I create participation object, and where is the appropriate place for the function ?
When you have a has_many relationship, you actually get a bunch of methods which can help you.
One of these is association_singular_ids- which if you populate it correctly, will automatically create the associative data you need.
The way to do this will be to use the following:
#app/views/students/new.html.erb
<%= form_for #student do |f| %>
<%= f.collection_check_boxes :subject_ids, Subject.all, :id, :name %>
<%= f.submit %>
<% end %>
I know you're using f.association (which is built through simple_form) - you'll be much better suited to using collection_check_boxes (it even explains an example of what you're having problems with).
You shouldn't need to pass the params or anything - and because your participations model acts as a join, it should be populated automatically if you use the above code.
HABTM
You may also wish to look at has_and_belongs_to_many:
#app/models/student.rb
class Student < ActiveRecord::Base
has_and_belongs_to_many :subjects
end
#app/models/subject.rb
class Subject < ActiveRecord::Base
has_and_belongs_to_many :students
end
#join table - students_subjects
This is often preferred over has_many :through because it requires less maintenance (as described in the link above).
Nested Attributes
Finally, to give you some more perspective, you'll need to know about the nested attributes aspect of Rails.
I originally thought your answer would be that you're not using accepts_nested_attributes_for, but I don't think so now. Nonetheless, you'll still gain benefit from knowing about it.
--
One of the reasons you'd use has_many through would be to populate the join model with other attributes. HABTM does not allow this; because has_many :through has a join model (in your case participations), it allows you to add extra attributes into it.
As such, if you're looking to change any of those attributes, you'll need to pass them through your various models:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :participations
has_many :subjects, through: :participations
accepts_nested_attributes_for :participations
end
#app/models/participation.rb
class Participation < ActiveRecord::Base
belongs_to :student
belongs_to :subject
accepts_nested_attributes_for :student
end
#app/models/subject.rb
class Subject< ActiveRecord::Base
has_many :participations
has_many :students, through: :participations
end
This will allow you to use the following:
#app/controllers/students_controller.rb
class StudentsController < ApplicationController
def new
#student = Student.new
#student.participations.build
end
def create
#student = Student.new student_params
#student.save
end
private
def student_params
params.require(:student).permit(:student, :params, participations_attributes: [subject_attributes:[]])
end
end
This should allow you to use the following:
#app/views/students/new.html.erb
<%= form_for #student do |f| %>
<%= f.fields_for :participations do |p| %>
<%= p.text_field :name %>
<%= p.collection_check_boxes :subject_id, Subject.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
My problem was that I didn`t permited the chenge od subject_ids in the student controller:
params.require(:student).permit(:first_name, :last_name, subject_ids: [])

Rails 4 one-to-many relationship: find parent attribute from child parent_id attribute

I have five models:
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
has_many :comments
end
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
has_many :posts
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Post < ActiveRecord::Base
belongs_to :calendar
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
The comment table has the following columns: id, post_id, user_id and body.
In different views, for instance in the show.html.erb post view, I need to display the relevant comments with the first name of the user who posted the comment.
In other words, I am trying to retrieve user.first_name from comment.user_id.
To achieve this, I defined the following method in the comment.rb file:
def self.user_first_name
User.find(id: '#{comment.user_id}').first_name
end
and then I updated the show.html.erb post view as follows:
<h3>Comments</h3>
<% #post.comments.each do |comment| %>
<p>
<strong><%= comment.user_first_name %></strong>
<%= comment.body %>
</p>
<% end %>
When I do that, I get the following error:
NoMethodError in Posts#show
undefined method `user_first_name' for #<Comment:0x007fc510b67380>
<% #post.comments.each do |comment| %>
<p>
<strong><%= comment.user_first_name %></strong>
<%= comment.body %>
</p>
<% end %>
I don't really understand Why I get an error related to Posts#show.
Any idea how to fix this?
Replace:
comment.rb
def self.user_first_name
User.find(id: '#{comment.user_id}').first_name
end
with:
comment.rb
delegate :first_name, to: :user, prefix: true
If you do this, you can just make the same call comment.user_first_name and it will give you the user's first name. Add , allow_nil: true if you don't want it to break if the user doesn't have a first_name.
You also might want to add:
has_many :comments
user.rb
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
has_many :comments
end

Using a Many-to-Many Relationship with the Public Activity gem in Rails

I have the scenario where an author has and belongs to many books, vice versa. Following the instructions for setting up associations in a one-to-many relationship works fine but when a many-to-many relationship introduced I get this error message whenever I try to create or update my book model.
undefined method `author' for #<Book:0x007fb91ae56a70>
As far as setting up how authors are chosen for a book I'm using the code provided by the token-input railscast here with a few alterations.
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
def self.tokens(query)
authors = where("name like ?", "%#{query}%")
if authors.empty?
[{id: "<<<#{query}>>>", name: "Add New Author: \"#{query}\""}]
else
authors
end
end
def self.ids_from_tokens(tokens)
tokens.gsub!(/<<<(.+?)>>>/) {create!(name: $1).id}
tokens.split(',')
end
end
class Book < ActiveRecord::Base
attr_reader :author_tokens
include PublicActivity::Model
tracked owner: :author
has_many :authorships
has_many :authors, through: :authorships
def author_tokens=(ids)
self.author_ids = Author.ids_from_tokens(ids)
end
end
Form View
<%= form_for(#book) do |f| %>
...
<div class="field">
<%= f.text_field :author_tokens, label: 'Author', input_html: {"data-pre" => #book.authors.to_json} %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
There is no author relationship in your Book model.
What
tracked owner: :author
does is basically calling method author on your Book instance. You should try :authors
But!
That won't solve your problem because owner can only be one. So you can do something like:
tracked owner: proc {|_, book| book.authors.first }
to set the owner to the first author the book has.
class Author < ActiveRecord::Base
has_many :author_books, inverse_of: :author, dependent: :destroy
accepts_nested_attributes_for :author_books
has_many :books, through: :author_books
end
class Book < ActiveRecord::Base
has_many :author_books, inverse_of: :book, dependent: :destroy
accepts_nested_attributes_for :author_books
has_many :authors, through: :author_books
end
class AuthorBook < ActiveRecord::Base
validates_presence_of :book, :author
end
============= view ==============
<%= form_for #book do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :author_books do |f2| %>
<%# will look through all author_books in the form builder.. %>
<%= f2.fields_for :author do |f3| %>
<%= f3.text_field :name %>
<% end %>
<% end %>
<% end %>

Resources