Let's consider a practical example: A product that has many reviews, written by clients. We have a many-to-many relationship between product and client through reviews.
class Product < ActiveRecord::Base
has_many :reviews
has_many :clients, :through => :reviews
end
class Client < ActiveRecord::Base
has_many :reviews
has_many :products, :through => :reviews
end
class Reviews < ActiveRecord::Base
belongs_to :product
belongs_to :client
end
Here, I'm using the has_many :through to create the many-to-many relation, because the review table needs to have extra attributes, like the score, content, likes,...
The user logs into my app, so I can get his data through:
client = Client.find_by_id current_user.id
He goes to the product page, so I can get product data:
product = Product.find_by_id params[:id]
How can I create client review of the product?
I tried:
review = Review.create :client => client, :product => product, :comment => params[:review][:comment]
but it gives me: MassAssignSecurity: can't mass-assign protected attributes: product, client
Any idea? Thanks in advance.
As soon as you create a Review object and explicitly pass arguments you need to make them accessible in your Review model. In this case it must be foreign keys
class Reviews < ActiveRecord::Base
belongs_to :product
belongs_to :client
attr_accessible :client_id, :product_id
end
That should work, but this's bad practice, which causes security issues. Instead of making foreign keys accessible and explicitly passing them in Review.create, I recommend to replace review.create with the following:
review = Review.new
review.client = client
review.product = product
review.comment = params[:review][:comment]
review.save
That will create a new Review object avoiding mass-assignment.
Hope this helps.
Add to ur model where the attributes are :product and :client
attr_accessible :product, :client
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html
The :client and :product attributes are private, you have to make them accesible on each class setting attr_accessible :client and attr_accessible :product respectively like:
class Reviews < ActiveRecord::Base
belongs_to :product
belongs_to :client
attr_accessible :client, :product
end
Hope this helps
Related
I have 3 models, User, Product, Coupon.
A User has many Products, Product belongs to User.
A User has many Coupons, Coupon belongs to User.
My goal is to apply a Coupon to a Product. A Product can have one coupon and a Coupon can be applied to many Products. Currently I have the models set up like this:
#coupon.rb
class Coupon < ApplicationRecord
belongs_to :user
has_many :products
validates_presence_of :code, :discount_percent, :description
end
#user.rb
class User < ApplicationRecord
has_many :products
has_many :coupons
end
#product.rb
class Product < ApplicationRecord
belongs_to :user
has_one :coupon, dependent: :destroy
end
Currently a user can successfully create a coupon, but if I apply the coupon to the product and try to delete the coupon, it gives me a foreign key error.
I've thought about making the product.coupon_id = nil inside the destroy action of the coupons_controller but I feel that is a bad practice. Ex.)
#coupons_controller.rb
def destroy
products = Product.where(coupon_id: #coupon.id)
products.each do |product|
product.coupon_id = nil
product.save
end
#coupon.destroy
end
I think I have something wrong with my associations but can't seem to figure it out! Using Postgres.
I appreciate any help!
class Coupon < ApplicationRecord
belongs_to :user
has_many :products, dependent: :nullify
validates_presence_of :code, :discount_percent, :description
end
I am tryng to develop a rent system where a user can login, create products, and his products can be rented by other users.
My problem is, I don't know how to create a rent that retrieves the product_id and customer_id. I made the relations but it isn't working.
I also create the CRUD for each one, even the rent. How can I store the information and pass to the Rent?
I have 4 models:
User
Product
Rent
Category
I created a new column in Rent called customer_id, and I've passed the class "User":
class Product < ActiveRecord::Base
belongs_to :user
belongs_to :category
belongs_to :rents
class User < ActiveRecord::Base
has_many :products
has_many :rents
has_many :customer_id, :through => :rent
class Rent < ActiveRecord::Base
belongs_to :user
belongs_to :product
belongs_to :customer, :class_name => "User"
end
I think I need to create a button that retrieves the information that I need. I searched through the documentation but I couldn't find it.
This line: has_many :customer_id, :through => :rent would never make sense this way in Rails. If you say has_many :customer_id, you are making two mistakes:
Whatever you write after has_many, it should be plural.
If what you write after has_many doesn't correspond directly to a model name as this the case with you, you have to explicit mention the class_name.
Same mistake you are repeating when you say:
class Product < ActiveRecord::Base
belongs_to :rents # it should be rent.
end
And now coming to what you are actually trying to implement:
class Rent < ActiveRecord::Base
belongs_to :user
has_one :product
has_one :customer
end
And in Product and Customer tables, you need to define rent_id as a foreign key. And you should also mention that each of them belongs_to Rent.
I have 4 models (Users, Post, Comments, Value) like this:
One user can post something, and someone can comment this post. After that, another user can add a numeric value from 0 to 10 to the comment.
I have done the relation between Users and Post helping me with the rails tutorial, but now i don't know the next step.
I believe you can set up a relation between that value you would like to assign (lets call it rating) and 2 other models.
Based on what you said I guess your relations (associations) would look like this:
class User < ActiveRecord::Base
has_many :post
has_many :comment
has_many :rating, :through => :comment
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comment
has_many :rating, :through => :comment
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
has_many :rating
end
class Rating < ActiveRecord::Base
belongs_to :comment
belongs_to :post
belongs_to :user, :through => :comment
end
Read more about it here
My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)
A User can have many roles, but only one role per Brand.
Class User < AR::Base
has_and_belongs_to_many :roles, :join_table => "user_brand_roles"
has_and_belongs_to_many :brands, :join_table => "user_brand_roles"
end
The problem with this setup is, how do I check the brand and the role at the same time?
Or would I better off with a BrandRole model where different roles can be set up for each Brand, and then be able to assign a user to a BrandRole?
Class User < AR::Base
has_many :user_brand_roles
has_many :brand_roles, :through => :user_brand_roles
end
Class BrandRole < AR::Base
belongs_to :brand
belongs_to :role
end
Class UserBrandRole < AR::Base
belongs_to :brand_role
belongs_to :user
end
This way I could do a find on the brand for the user:
br = current_user.brand_roles.where(:brand_id => #brand.id).includes(:brand_role)
if br.blank? or br.role != ADMIN
# reject access, redirect
end
This is a new application and I'm trying to learn from past mistakes and stick to the Rails Way. Am I making any bad assumptions or design decisions here?
Assuming Roles,Brands are reference tables. You can have a single association table Responsibilities with columns user_id, role_id, brand_id.
Then you can define
Class User < AR::Base
has_many : responsibilities
has_many :roles, :through => responsibilities
has_many :brands,:through => responsibilities
end
Class Responsibility < AR::Base
belongs_to :user
has_one :role
has_one :brand
end
The you can define
Class User < AR::Base
def has_access?(brand)
responsibility = responsibilities.where(:brand => brand)
responsibility and responsibility.role == ADMIN
end
end
[Not sure if Responsibility is the term used in your domain, but use a domain term instead of calling it as user_brand_role]
This is a conceptual thing. If BrandRole is an entity for your application, then your approach should work. If BrandRole is not an entity by itself in your app, then maybe you can create a UserBrandRole model:
class User < AR::Base
has_many :user_brand_roles
end
class Brand < AR::Base
has_many :user_brand_roles
end
class Role < AR::Base
has_many :user_brand_roles
end
class UserBrandRole < AR::Base
belongs_to :user
belongs_to :brand
belongs_to :role
validates_uniqueness_of :role_id, :scope => [:user_id, :brand_id]
end