Reputation: 625
I have a setup like the below:
user.rb
class User < ActiveRecord::Base
...
has_many :projects_users
has_many :projects, through: :projects_users
...
end
projects_users.rb
class ProjectsUser < ActiveRecord::Base
belongs_to :project
belongs_to :user
has_one :projects_users_role
end
project.rb
class Project < ActiveRecord::Base
has_many :projects_users
has_many :users, through: :projects_users
end
Users are nested under Project in my routes file.
For some reason, I cannot seem to find a good way of accessing a single project for a user. Ideally I would like to create a
has_one :project_user
association and then a
has_one :project, through: :project_user
and somehow pass and id for the project to the association (which I know is not possible). Any ideas for a good approach.
I would then like to call a method similar to
@user = User.includes(:project (project_id parameter).find(:id)
in my users controller.
Any assistance is greatly appreciated.
Thank you
UPDATE
I essentially have a role that is attached per project per user in the projects_users.rb (updated above and added below)
projects_users.rb
class ProjectsUser < ActiveRecord::Base
belongs_to :project
belongs_to :user
has_one :projects_users_role
end
projects_users_role.rb
class ProjectsUsersRole < ActiveRecord::Base
belongs_to :projects_user
enum role: [...]
end
And my aim, which I should have stated previously is to be able to edit this role for the user on the given project via a route like
/projects/1/users/2/edit-roles
this would display the user, and allow me to assign the role to the projects_users_roles table, with the nested ids added i.e. the id from the project_users table. Hence I would like (I think) to have a has_one to projects with an argument so that my nested form would be simpler.
Update 2
I have stumbled across this
has_one :project_department, ->(department) { where department: department }, class_name: 'ProjectDepartment'
from here http://www.rojotek.com/blog/2014/05/16/cool-stuff-you-can-do-with-rails-has_one/ but cannot make it apply as replacing 'department' with 'project' does not use the project id in the join but the user id in my case.
user.rb update
...
has_one :project_user, -> (project) { where project: project}, class_name: 'ProjectsUser'
has_one :project, through: :project_user
...
From the console
User.first.project_user(1)
User Load (0.2ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
ProjectsUser Load (0.1ms) SELECT `projects_users`.* FROM `projects_users` WHERE `projects_users`.`user_id` = 26 AND `projects_users`.`project_id` = 26 LIMIT 1
The project_id should be 1 in this case but it is using the user.id 26. Not sure why really.
Thanks again
Upvotes: 1
Views: 5884
Reputation: 16507
That is correct, if you have a model User
, an argument which is passed to closure is the self
for called class instance, i.e. it will be an instance of User
. So in following code piece project
variable will have a user
value.
user.rb
has_one :project_user, -> (project) { where project: project}, class_name: 'ProjectsUser'
To pass into has_many/one
relation an additional argument use extending selector:
class User < ActiveRecord::Base
has_one :project_user, class_name: 'ProjectsUser' do
def for_project(project)
where(project: project)
end
end
end
customers.project_user.for_project(project)
The approach is described in Association Extension
head in Rails Association
page.
Upvotes: 7
Reputation: 5740
One of various ways to produce your result in one SQL statement, would be with a left join:
@user=User.joins(:projects).
where("users.id = ? and projects.id = ?",
params[:id],
params[:project_id]).take # use take to get one row only
UPDATE
The above statement will produce an extra SQL request if you want to get:
@user.projects.first.id
If you want no more statements for the project (of course you don't) you should combine the join with an include:
@user=User.joins(:projects).includes(:projects).
where("users.id = ? and projects.id = ?",
params[:id],
params[:project_id]).take # use take to get one row only
So now
@user.projects.first.id
will produce no extra SQL statements
Upvotes: 0