i have three models, all for a has_many :through relationship. They look like this:
class Company < ActiveRecord::Base
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
accepts_nested_attributes_for :company_users, :users
end
class CompanyUser < ActiveRecord::Base
self.table_name = :companies_users #this is because this was originally a habtm relationship
belongs_to :company
belongs_to :user
end
class User < ActiveRecord::Base
# this is a devise model, if that matters
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
accepts_nested_attributes_for :company_users, :companies
end
this loads fine, and the joins are built fine for queries. However, whenever i do something like
#company = Company.last
#user = #company.users.build(params[:user])
#user.save #=> true
#company.save #=> true
both the User record and the CompanyUser records get created, but the company_id field in the CompanyUser record is set to NULL
INSERT INTO `companies_users` (`company_id`, `created_at`,`updated_at`, `user_id`)
VALUES (NULL, '2012-02-19 02:09:04', '2012-02-19 02:09:04', 18)
it does the same thing when you #company.users << #user
I'm sure that I'm doing something stupid here, I just don't know what.
You can't use a has_many :through like that, you have to do it like this:
#company = Company.last
#user = User.create( params[:user] )
#company.company_users.create( :user_id => #user.id )
Then you will have the association defined correctly.
update
In the case of the comment below, as you already have accepts_nested_attributes_for, your parameters would have to come like this:
{ :company =>
{ :company_users_attributes =>
[
{ :company_id => 1, :user_id => 1 } ,
{ :company_id => 1, :user_id => 2 },
{ :company_id => 1, :user_id => 3 }
]
}
}
And you would have users being added to companies automatically for you.
If you have a has_many :through association and you want to save an association using build you can accomplish this using the :inverse_of option on the belongs_to association in the Join Model
Here's a modified example from the rails docs where tags has a has_many :through association with posts and the developer is attempting to save tags through the join model (PostTag) using the build method:
#post = Post.first
#tag = #post.tags.build name: "ruby"
#tag.save
The common expectation is that the last line should save the "through" record in the join table (post_tags). However, this will not work by default. This will only work if the :inverse_of is set:
class PostTag < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :post_tags # add inverse_of option
end
class Post < ActiveRecord::Base
has_many :post_tags
has_many :tags, through: :post_tags
end
class Tag < ActiveRecord::Base
has_many :post_tags
has_many :posts, through: :post_tags
end
So for the question above, setting the :inverse_of option on the belongs_to :user association in the Join Model (CompanyUser) like this:
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user, inverse_of: :company_users
end
will result in the following code correctly creating a record in the join table (company_users)
company = Company.first
company.users.build(name: "James")
company.save
Source: here & here
I suspect your params[:user] parameter, otherwise your code seems clean. We can use build method with 1..n and n..n associations too, see here.
I suggest you to first make sure that your model associations works fine, for that open the console and try the following,
> company = Company.last
=> #<Tcompany id: 1....>
> company.users
=> []
> company.users.build(:name => "Jake")
=> > #<User id: nil, name: "Jake">
> company.save
=> true
Now if the records are being saved fine, debug the parameters you pass to build method.
Happy debugging :)
Related
I have quite a complicated setup of associations to allow my model :thing to be rated, which I think can be best understood by looking at my models. Basically, when a new :thing is created, new :thing_ratings are also created based on the :ratings that belong to the :categories that the :thing belongs to.
For example, if a :category “Books” has a :rating “Plot”, then a new :thing that is created with an association to Books should have a :thing_rating also named “Plot”.
The problem is, though the :thing and :thing_ratings are created without problems, on the :thing show page, I'm getting this error:
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly in ThingsController#show
Cannot modify association 'Thing#thing_ratings' because it goes through more than one other association.
How can I get around this problem? I saw an answer to a similar problem that suggested to make the association between :thing and :thing_rating readonly, but I also want to be able to create instances of another model :up_votes for :thing_ratings, and I don't think I can do that if the association is readonly.
models/thing.rb
has_many :category_things
has_many :categories, :through => :category_things
has_many :category_ratings, through: :categories
has_many :ratings, :through => :categories
has_many :thing_ratings, through: :ratings
models/category.rb
has_many :category_ratings
has_many :ratings, :through => :category_ratings
has_many :category_things
has_many :things, :through => :category_things
models/rating.rb
has_many :category_ratings
has_many :categories, :through => :category_ratings
has_many :thing_ratings
has_many :things, :through => :thing_ratings
models/category_thing.rb
belongs_to :category
belongs_to :thing
models/category_rating.rb
belongs_to :category
belongs_to :rating
models/thing_rating.rb
belongs_to :rating
belongs_to :thing
has_many :up_votes, as: :voteable
controllers/things_controller.rb
def show
#thing = Thing.find(params[:id])
#thing.categories.build
#category_thing = CategoryThing.all
#category_rating = CategoryRating.all
#thing_ratings = #thing.category_ratings
#thingcats = #thing.categories
#thing.thing_ratings.build
# ...
end
def create
#thing = Thing.new(thing_params)
#category = Category.all
#thing.categories.build
#thing_ratings = #thing.category_ratings
#thingcats = #thing.categories
#thingrats = #thing.ratings
if #thing.save
params["categories"].strip.split(',').map(&:strip).each do |name|
CategoryThing.create!(category_id: Category.where(name: name).first.id, thing_id: #thing.id)
end
#thingrats.each do |r|
ThingRating.create!(rating_id: r.id, thing_id: #thing.id, name: r.name)
end
redirect_to new_thing_path
end
end
Well it turns out that I didn't actually have to associate thing_ratings with ratings at all. I just made thing_ratings belong directly to things.
In my app I have a page which render all the projects in the database. What I want is to be able to filter the results, for example by showing only the once in which the user is a member.
Below is from my projects controller, where I want to do exactly that (show only projects where the user is a member). What do is to first get all the projects from 'ProjectUser' where the user_id is found. Then I want to use this array to retrieve all the relevant projects from the table 'Projects', with the use of #user_is_member.project_id.
This does not work because it only give me ONE project, not all.
How can I change the code so I accomplish what I want?
The code:
#user_is_member = ProjectsUser.where(:user_id => current_user.id)
#user_is_member.each do |member|
#projects = Project.where(:id => member.project_id)
end
Tables:
projects_users:
project_id
user_id
projects:
id
...non relevant fields...
Models:
User model:
class User < ActiveRecord::Base
has_many :project_users
has_and_belongs_to_many :projects
end
Project model:
class Project < ActiveRecord::Base
has_and_belongs_to_many :users # => , :class_name => 'User'
belongs_to :user
end
ProjectUser model:
class ProjectsUser < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Note: current_user is the currently logged in user (which has access to all the user fields, e.g "id")
This is not a good way to associate the models. If you want to associate user to project and make one user to admin/creator then you should associate your models as:
User
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
has_secure_password
has_many :projects_users
has_many :projects, :through => :projects_users
end
Project
class Project < ActiveRecord::Base
has_many :projects_users
has_many :users, :through => :projects_users
end
ProjectsUser
class ProjectsUser < ActiveRecord::Base
attr_accessible :role
belongs_to :user
belongs_to :project
end
I have added an extra column/attribute to your ProjectsUser Model that will have a string value (admin/member). And now you can get the members for a project by doing #project.users.where(:role => 'member') and admin by #project.users.where(:role => 'admin').first.
If you don't want to change your way then in your controller do something like:
#user_is_member = ProjectsUser.where(:user_id => current_user.id)
project_ids = []
#user_is_member.each do |member|
project_ids << member.project_id
end
#projects = Project.where(:id => project_ids)
If you want all the projects for the current_user, you can just do:
#projects = current_user.projects
I am assuming you have something like:
class User < ActiveRecord::Base
has_many :project_users
has_many :projects, :through => :project_users
end
class ProjectUser < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
class Project < ActiveRecord::Base
has_many :project_users
has_many :users, :through => :project_users
end
The problem is that you have no project_id for some user which is somehow deleted.
Check your database that which project_id is nil by putting puts like statements in a loop.
Alternatively, you can use :dependent => :destroy on your relationship to get rid of such inconsistent data. It usually happens while you are testing and somehow delete any record but forget to delete the foreign key association relation for other table.
This can be accomplished like following:
#user_is_member = ProjectsUser.where(:user_id => current_user.id)
#user_is_member.each do |member|
puts "Checking the project id existence for each user member"+member.project_id.to_s
end
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
First I'm using Rails 3.1 from the 3-1-stable branch updated an hour ago.
I'm developing an application where I have 3 essential models User, Company and Job, Here's the relevant part of the models:
class User < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :companies, :through => :companies_users, :source => :company
end
class Company < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :employees, :through => :companies_users, :source => :user
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
belongs_to :company, :counter_cache => true
end
class CompaniesUsers < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
The code works just fine, but I have been wondering if it's possible to:
I want to link a job with an employer, so think of this scenario: A user John who's an employee at Example, he posted the job Rails Developer, so I want to access #job.employer and it should get me back the user John, in other words:
#user = User.find_by_name('john')
#job = Job.find(1)
#job.employer == #user #=> true
So I thought of two possible solutions
First solution
class Job
has_one :employer, :through => :employers
end
class User
has_many :jobs, :through => :employers
end
class Employer
belongs_to :job
belongs_to :user
end
Second solution
class Job
has_one :employer, :class_name => "User"
end
class User
belongs_to :job
end
Which route should I go? Is my code right ?
I have another question, how to get rid of the class_name => "CompaniesUsers" option passed to has_many, should the class be Singular or Plural ? Should I rename it to something like Employees ?
P.S: I posted the same question to Ruby on Rails: Talk
Unless I'm missing something, I'd suggest simply doing
class Job
belongs_to :employer, :class_name => "User"
end
class User
has_many :jobs
end
This would give you methods like
user = User.first
user.jobs.create(params)
user.jobs # array
job = user.jobs.first
job.employer == user # true
You'll need an employer_id integer field in your Jobs table for this to work.
Typically you want to name your pass through model:
company_user
Then you don't need this:
class_name: "CompaniesUsers"
Just make sure the name of your database table is:
company_users
What you have works for you, so that's great. I just find when I don't follow convention I
run in to trouble down the road.
In my form for member_profile, I would like to have role checkboxes that are visible for admins. I would like to used some nested form_for, but can't make it work, so I've resorted to manually creating the check_box_tags (see below), and then manually adding them to member_profile.member.
Note that the Member model is Devise, and I don't want to mix those fields in with my MemberProfile data, in case I change auth systems in the future.
class Member < ActiveRecord::Base
has_one :member_profile
has_many :member_roles
has_many :roles, :through => :member_roles
end
class MemberProfile < ActiveRecord::Base
belongs_to :member
has_many :member_roles, :through => :member
#has_many :roles, :through => :member_roles #can't make this work work
end
class Role < ActiveRecord::Base
has_many :member_roles
validates_presence_of :name
end
class MemberRole < ActiveRecord::Base
belongs_to :member
belongs_to :role
end
Form (haml)
= form_section do
- Role.all.each do |x|
=check_box_tag 'member[role_ids][]',
x.id,
begin #resource.member.role_ids.include?(x.id) rescue nil end
=x.name
member_profiles_controller.rb
def update
if #resource.update_attributes params[:member_profile]
#resource.member.role_ids = params[:member][:role_ids]
redirect_to(#resource, :notice => 'Member profile was successfully updated.')
else
render :action => "edit"
end
end
I've decided it only makes sense to do a nested has_many :through on Update, since the join model is what is being 'gone through' to get to the has_many :through model. Before the hmt is created, there is obviously no record in the join model.