rails creating model with multiple belongs_to, with attr_accessible - ruby-on-rails

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)

Related

An equivalent to first_or_create on a nested attribute in Rails 4

I'm using a has many through pattern with these 3 models
class User < ActiveRecord::Base
has_many :user_topics
has_many :topics, through: :user_topics
end
class Topic < ActiveRecord::Base
validates_presence_of :name
validates :name, :uniqueness => true
end
class UserTopic < ActiveRecord::Base
belongs_to :user
belongs_to :topic
accepts_nested_attributes_for :topic
end
At the moment a new topic model is trying to be created every time a new user_topic is created. I'd like to create a new topic model only if the topic name doesn't already exist, otherwise if it does, use the existing topic_id.
So something like:
class UserTopic < ActiveRecord::Base
belongs_to :user
belongs_to :topic
accepts_nested_attributes_for :topic, :first_or_create(:name)
end
Is it possible to do something similar to this?

Getting Association through self-inheriting STI model

In my Rails app I have Users and Forms.
class User < ActiveRecord::Base
has_many :admin_roles
#desired association below
#has_many :forms, through: :admin_roles
end
class Form < ActiveRecord::Base
has_one :department
end
The Users need to administrate the Forms through any level of an Organization.
class AdminRole < ActiveRecord::Base
belongs_to :organization
belongs_to :user
end
If assigned to a non-department organization the forms they have control over should come through the child departments.
The Forms are assigned to a departmental level only.
My model for the Organization is an STI model of 3 levels: market>subdomain>department
class Organization < ActiveRecord::Base
self.inheritance_column = :org_level
has_many :admin_roles
end
class Department < Organization
belongs_to :sub_domain, primary_key: :id, foreign_key: :parent_id
has_many :forms
end
class SubDomain < Organization
belongs_to :market, primary_key: :id, foreign_key: :parent_id
has_many :departments
end
class Market < Organization
has_many :sub_domains
end
The desired capability is to do user.forms and get all the associated forms back.
For example: Given there was a hierarchy of FooMarket>BarDomain>LoremDepartment
and a Form associated to LoremDepartment.
If a User is then tied to any of those 3 Organizations through the AdminRole it would allow for the return of the LoremDepartment Form.
Do you have to necessarily do it with associations ? u can always define an instance method in user model and back track it to forms.
But before that, just a reminder, you have to mention the foreign key in both the models for the association to work both ways.
class User < ActiveRecord::Base
attr_accessible :name
has_many :admin_roles
has_many :organizations, :through => :admin_roles
def forms
organizations.map(&:forms).flatten.uniq
end
end
class Department < Organization
belongs_to :sub_domain, primary_key: :id, foreign_key: :parent_id
has_many :forms, :foreign_key => :organization_id
end
class SubDomain < Organization
belongs_to :market, primary_key: :id, foreign_key: :parent_id
has_many :departments, foreign_key: :parent_id
def forms
departments.map(&:forms).flatten
end
end
class Market < Organization
has_many :sub_domains, foreign_key: :parent_id
def forms
sub_domains.map(&:forms).flatten
end
end
I tested this and it does work. But kinda round about.

Buid two associations between two tables

I have a strange requirement in my project. Actually, I have two tables (users,galleries). I would like to access galleries table with has_one as well as has_many associations. The main purpose is to get the profile snap of the user by using has_one relation and to get the personally uploaded pictures by using has_many relation.
Initially I go with polymorphic association to resolve this (FYI, please find the below code snippet) .But I think it is not the right approach for this problem.
Would anybody explain how to handle this case in an efficient way.
class User < ActiveRecord::Base
attr_accessible :name
has_many :galaries, as: :imageable
has_one :galary, as: :imageable
end
class Galary < ActiveRecord::Base
attr_accessible :name
belongs_to :imageable, polymorphic: true
end
You need to add a column user_id in galaries table in order to link the the user to the galary (profile snap).
rails generate migration add_user_id_to_galaries user_id:string
This will generate the migration:
class AddUserIdToGalaries < ActiveRecord::Migration
def change
add_column :galaries, :user_id, :integer
end
end
Then run rake db:migrate
Moving to our Models now:
class User < ActiveRecord::Base
attr_accessible :name
has_many :galaries, as: :imageable
has_one :galary #profile snap
end
class Galary < ActiveRecord::Base
attr_accessible :name
belongs_to :imageable, polymorphic: true
belongs_to :user #profile snap user
end
Can be done with scoped has_one association. Although not necessary, you can define which gallery is selected in the has_one using a scope. If no scope is given, the has_one would return the first match.
class User < ActiveRecord::Base
attr_accessible :name
has_many :gallaries
has_one :gallery, -> { where primary: true }
end
class Gallery < ActiveRecord::Base
#user_id, primary (boolean variable to select the gallery in has one association)
attr_accessible :name
belongs_to :user
end

Rails: How to create a new entry in the join table

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

How to save data with has_many :through

I have many-to-many relationship between Game and Account models like below:
class Account < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :games, :through => :account_games
end
class Game < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :accounts, :through => :account_games
end
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
end
Now I know let's say I want to create a record something like:
#account = Account.new(params[:user])
#account.games << Game.first
#account.save
But how am I supposed to update some of the attributes in AccountGame while I'm doing that? Lets say that AccountGame has some field called score, how would I update this attribute? Can you tell me about the best way to do that? To add any field in the through table while I'm saving the object.
#account = Account.new(params[:user])
#accountgame = #account.account_games.build(:game => Game.first, :score => 100)
#accountgame.save
Though I'd strongly recommend that if you start adding columns to your join-model that you call it something different eg "subscription" or "membership" or something similar. Once you add columns it stops being a join model and starts just being a regular model.
This should work:
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
attr_accessible :account_id, :game_id #<======= Notice this line
end

Resources