AssociationTypeMismatch nested attributes - ruby-on-rails

I'm running into an issue with an associated model. I have a nested attributes for my user, to a reviewer. A user can essentially review another person, thus be a reviewer and be the person reviewed.
It's set up like this:
# User
has_many :reviewers
accepts_nested_attributes_for :reviewers
has_many :active_managements, class_name: 'Reviewer',
foreign_key: 'reviewer_id',
dependent: :destroy
class Reviewer < ActiveRecord::Base
belongs_to :user
belongs_to :reviewer_id, class_name: 'User'
end
now in my users controller I have:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#user.reviewers.build
redirect_to root_url && return unless #user.activated?
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user)
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:invitation_token, :first_name, :admin,
:last_name, :title, :email, :password,
reviewers_attributes: [:reviewer_id])
end
the error that I get is:
User(#70197180889680) expected, got String(#70197172430700)
happening on "user_params", so I assume it has to do with my attributes. Anybody know what's up?

The line belongs_to :reviewer_id, class_name: 'User' is incorrect. Try changing it to something like belongs_to :reviewing_user, class_name: 'User' (replacing reviewing_user with whatever name you want to use for this association :), not field name.
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to

The immediate fix is here:
#app/models/review.rb
class Reviewer < ActiveRecord::Base
belongs_to :reviewer
end
--
This is how I'd fix the systemic issue:
#config/routes.rb
resources :users do
resources :reviewers #-> url.com/users/:user_id/reviews/new
end
#app/controllers/reviewers_controller.rb
class ReviewersController < ApplicationController
def new
#user = User.find params[:user_id]
#review = user.reviewers.new
end
def create
#user = User.find params[:user_id]
#review = user.reviewers.new reviewer_params
end
private
def review_params
params.require(:reviewer).permit(:user_id, :reviewer_id)
end
end
Models
Apart from this, I think your main issue is the way your models are set up.
Ideally, you want to have reviewer and user as the same data-set (I presume they're both users), which makes your current setup really inefficient...
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :reviewers,
class_name: "User",
join_table: :reviewers_users
foreign_key: :user_id,
association_foreign_key: :reviewer_id
end
What you're looking for is something called a self referrential association, which basically allows you to associate the same model in a many-to-many relationship.
In most cases, this will be used with a has_and_belongs_to_many relationship; you could also use a has_many :through although it's not as common.
The above will allow you to use the following:
#user = User.find params[:id]
#user.reviewers #-> collection of users who are reviewers of original user

Related

Ruby on rails: dependent destroy not working with renamed foreign key

I didn't find any related threads on this, so I made a new question myself.
I have following models. I added foreign_key: :sender in the class User because the field name :sender of the model class Comment differs from the model class User.
I added dependent: :destroy to the class User to make sure when an user is deleted all of its comments are deleted as well.
However when I destroy an user, their comments do not get destroyed. The comments remain in the database. I don't see why.
class Comment < ApplicationRecord
belongs_to :sender, :class_name => "User"
belongs_to :event
end
class User < ApplicationRecord
has_many :comments, dependent: :destroy, foreign_key: :sender
end
class CommentsController < ApplicationController
<snip>
def destroy
#event = Event.find(params[:event_id]) # dont mind this line
#comment = Comment.find(params[:id])
#comment.destroy
redirect_to event_path(#event) # dont mind this either
end
<snip>
end
class UsersController < ApplicationController
<snip>
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to users_path
end
<snip>
end
I managed to fix it by changing foreign_key: :sender to foreign_key: :sender_id. In other words, the real db column :sender_id has to be given instead of the field name :sender.
However, when I was using foreign_key: :sender before I did not get any errors from rails. It just did not the delete the comments when deleting an user.
If anyone can explain why I did not see any errors, I would like to hear from you.

Mutual relationships in rails 4

I'm trying to allow users in my app to be mutual friends with one another via friend requests and I'm a little confused with the how the relationships work... When a friendship is created by one user and accepted by the other, I would like the friendship to be visible from both users (obviously).
I'd like to achieve an implementation that allows me to do something similar to the following:
user1 friend requests user2
user2 accepts
user1.friends now contains user2
user2.friends now contains user1
Here's what I have so far, but I've read some weird things about nested has_many :through relationships
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :class_name => "User", :through => :friendships
end
class Friendship < ActiveRecord::Base
has_many :users, :limit => 2
end
Is this a viable implementation? If not, what could I change/improve? I'd like to avoid 2 rows representing one relationship if possible.
What you're looking for is a has_and_belongs_to_many relation, but to the same table, kind of like as described in detail by Many-to-many relationship with the same model in rails?. However, since you want the relation to be bi-directional ("my friends are all also friends with me"), you have two options:
Use a single join table, each row of which links two user_ids, but insert two rows for each friendship.
# no need for extra columns on User
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, through: :friendships
end
# t.belongs_to :user; t.belongs_to :friend
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: "User"
end
u1 = User.create!
u2 = User.create!
u3 = User.create!
# make users 1 and 2 friends
u1.friendships.create(friend: u2)
u2.friendships.create(friend: u1)
# make users 2 and 3 friends
u2.friendships.create(friend: u3)
u3.friendships.create(friend: u2)
# and now, u1.friends returns [u1],
# u2.friends returns [u1, u3] and
# u3.friends returns [u2].
Use a single record, but hackery to locate who you're friends with:
# no need for extra columns on User
class User < ActiveRecord::Base
has_many :friendships_as_a, class_name: "Friendship", foreign_key: :user_a_id
has_many :friendships_as_b, class_name: "Friendship", foreign_key: :user_b_id
def friends
User.where(id: friendships_as_a.pluck(:user_b_id) + friendships_as_b.pluck(:user_a_id))
end
end
# t.belongs_to :user_a; t.belongs_to :user_b
class Friendship < ActiveRecord::Base
belongs_to :user_a, class_name: "User"
belongs_to :user_b, class_name: "User"
end
That's not the cleanest way to do it, but I think you'll find there isn't really a particularly clean way when set up like that (with a denormalized table). Option 1 is a much safer bet. You could also use a SQL view to hit the middle ground, by generating the mirror entries for each friendship automatically.
Edit: Migration & usage in an API
Per the OP's comment below, to use Option 1 fully, here's what you'd need to do:
rails g migration CreateFriendships
Edit that file to look like:
class CreateFriendships < ActiveRecord::Migration
create_table :friendships do |t|
t.belongs_to :user
t.belongs_to :friend
t.timestamps
end
end
Create the Friendship model:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: "User"
end
Then on your User model:
class User < ActiveRecord::Base
# ...
has_many :friendships
has_many :friends, through: :friendships, class_name: 'User'
# ...
end
And in your API, say a new FriendshipsController:
class FriendshipsController < ApplicationController
def create
friend = User.find(params[:friend_id])
User.transaction do # ensure both steps happen, or neither happen
Friendship.create!(user: current_user, friend: friend)
Friendship.create!(user: friend, friend: current_user)
end
end
end
Which your route for looks like (in config/routes.rb):
resource :friendships, only: [:create]
And a request to would look like:
POST /friendships?friend_id=42
Then you can refer to current_user.friends whenever you want to find who a user is friends with.
You'd use a has_many :through:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, through: :friendships, -> { where(status: "accepted") }
end
#app/models/friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: "User"
enum status: [:pending, :accepted]
validates :user, uniqueness: { scope: :friend, message: "You can only add a friend once" }
def decline
self.destroy
end
def accept
self.update status: "approved"
end
end
The above is a self-referential join, allowing the following:
#user = User.find params[:id]
#friend = User.find params[:friend_id]
#user.friends << #friend
--
This will add a new friendship for the user, with its default status set to pending. The #user.friends association is set so that only accepted friends appear from a call.
Thus, you'll be able to do the following:
#config/routes.rb
resources :users do
resources :friendships, only: [:index, :destroy, :update], path_names: { destroy: "remove", update: "accept" }
end
#app/controllers/Frienships_controller.rb
class FriendshipsController < ApplicationController
def index
#user = User.find params[:user_id]
#friendship = #user.friendships
end
def update
#user = User.find params[:user_id]
#friendship = #user.friendships.find params[:id]
#friendship.accept
end
def destroy
#user = User.find params[:user_id]
#friendship = #user.friendships.find params[:id]
#friendship.decline
end
end
#app/views/friendships/index.html.erb
<%= #friendships.pending.each do |friendship| %>
<%= link_to "Accept", user_friendships_path(user, friendship), method: :put %>
<%= link_to "Decline", user_friendships_path(user, friendship), method: :delete %>
<% end %>

How to save throught model with extra field?

I have three models Company, User and Division
User have many Division for different Companies
I need to determine in what company owns Divisions
So I build has_many :through association between Users and Divisions
Model UsersDivision have this fields id|user_id|division_id|company_id but when I update User model rails delete old records and create new without company_id field How i can update model UsersDivision and merge company_id ?
Callback?
class UsersDivision < ActiveRecord::Base
after_update :set_company
belongs_to :user
belongs_to :division
belongs_to :company
validates :user_id, :division_id, presence: true
private
def set_company(company)
self.company_id = company
end
end
or in the controller?
class UsersController < ApplicationController
def update
#company = Company.find(params[:company_id])
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to :back
end
end
end
How to merge company_id when create UsersDivision record?
So I build has_many :through association between Users and Divisions
I would expect there to be a table for Divisions, and then a table for CompanyDivisions, and then we can associate users to that.
Here's how I would have it set up:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :user_divisions
has_many :divisions, through: :user_divisions
has_many :company_divisions, through: :user_divisions
has_many :companies, through: :company_divisions
end
#app/models/user_division.rb
class UserDivision < ActiveRecord::Base
belongs_to :user
belongs_to :company_division
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :company_divisions
has_many :divisions, through: :company_divisions
end
#app/models/company_division.rb
class CompanyDivision < ActiveRecord::Base
belongs_to :company
belongs_to :division
end
#app/models/division.rb
class Division < ActiveRecord::Base
has_many :company_divisions
has_many :companies, through: :company_divisions
end
This is very bloated, but should give you the ability to call:
#user.divisions
#user.divisions.each do |division|
division.companies.first
How to merge company_id when create UsersDivision record
This will depend on several factors:
params hash
How your associations are set up
I don't have your params hash, but I do have your current code:
class UsersController < ApplicationController
def update
#company = Company.find params[:company_id]
#user = User.find params[:id]
redirect_to :back if #user.update user_params
end
private
def user_params
params.require(___).permit(___).merge(company_id: #company.id)
end
end

Dealing with Comments in Ruby on Rails

Currently, I'm making a simple blog-like app, where a user can make a post, and several other users can comment on it.
Is there a way to have a polymorphic attribute belong to more than one Model?
For example,
a Comment will always have an author (User model)
However, a Comment can belong to many other models (Posts, Journals, Articles, etc etc)
so, for (Posts, Journals, Articles) models, polymorphic association is best.
However, for the author (or User relationship), polymorphic would not work, since polymorphic can only belong to one at a time.
Is there a better way around this?
EDIT:
What are the pros/cons of doing this:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
EDIT2:
with the solution above, is there a more elegant way of doing this
def create
#comment = #commentable.comments.new(params[:comment])
#comment.user_id = current_user.id
if #comment.save
flash[:success] = 'Comment created'
redirect_to #commentable
else
flash[:error] = 'Comment not created - is body empty?'
redirect_to #commentable
end
end
without having to save the user_id manually in the controller?
#comment.user_id = current_user.id
You can have both a User relationship as well as a polymorphic relationship representing the model it is associated with. For example:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :document, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :comments, as: :document
end
class Journal < ActiveRecord::Base
has_many :comments, as: :document
end
class Article < ActiveRecord::Base
has_many :comments, as: :document
end
class User < ActiveRecord::Base
has_many :comments
end
Now, you can call comment.user to get the User model for the person who created the comment and comment.document to get the Post, Journal, or Article that the comment is associated with.

Facing problems with association

I have three models User, Blog and Comment.
User.rb
class User < ActiveRecord::Base
attr_accessible blah blah
has_many :blogs
has_many :comments
end
Blog.rb
class Blog < ActiveRecord::Base
attr_accessible :user_id, :title, :content
belongs_to :user
has_many :comments
end
Comment.rb
class Comment < ActiveRecord::Base
attr_accessible :user_id, :blog_id, :comment
belongs_to :blog
belongs_to :user
end
In create action of Comments Controller
def create
#blog = Blog.where('id=?', params[:blog_id])
#comment = #blog.comments.new(params[:comment])
#comment.save
end
Here how will I pass id of current_user in the :user_id field of comments table, I can make a hidden field for that but it's not safe. PLease help! Thanks in advance.
Would this do what you want?
def create
#blog = Blog.where('id=?', params[:blog_id])
#comment = #blog.comments.new(params[:comment])
#comment.user = current_user # force the user to be the logged-in user
#comment.save
end

Resources