简体   繁体   中英

Rails has_many and has_one with argument

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 ie 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

That is correct, if you have a model User , an argument which is passed to closure is the self for called class instance, ie 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM