Rails 5 - using a scope in an edit action to find

2019-07-18 11:21发布

问题:

I am trying to learn how to use scopes in my Rails 5 app.

I have asked a background question here.

have models in my Rails 5 app for User, Proposal and Potential.

Users create Proposals which they themselves and others can then create comments on.

The associations between models are:

User

  has_many :proposals, dependent: :destroy
  has_many :potentials

Proposal

belongs_to :user
has_many :potentials, inverse_of: :proposal
    accepts_nested_attributes_for :potentials, reject_if: :all_blank, allow_destroy: true

Potential

belongs_to :proposal, inverse_of: :potentials
  belongs_to :user

In my routes file, I have two resources for potentials. I'm not sure if I've gone off piste with this bit- I cant find an example of how to do this otherwise. I have both:

resources :potentials

as well as:

resources :proposals do

    resources :potentials

Objective:

When the user who made the proposal tries to edit it, I only want that user to be able to edit the potentials that they created themselves.

The reason I have two routes set up for potentials is that the nested resource has a nested form fields inside the proposal form, so that the proposal creator can make a potential in that way. Any other user that sees the proposal and makes a potential does it via a separate form.

Any user (including the proposal creator, can edit the potential via that separate form), and the proposal creator can also edit any of its own proposals by the nested form in the proposal form.

At the moment, whenever I edit the proposal form (even when I don't edit the potential nested fields), all of the potentials are updated to insert the proposal creator's user id overriding the actual potential creator's user id.

Solution

I am trying to limit the edit action in the proposals controller, so that it only allows the proposal /potentials to be edited if they have the user_id == the proposal.user_id.

For this purpose, I have written scopes in my proposal.rb

scope :owner_potentials, ->{ where(user_id: potential.user_id ) }
scope :third_party_potentials, ->{ where(user_id: != potential.user_id) }

The solution in the post i liked above was to try using a scope. Since scopes are meant to work on the class, rather than an instance, I'm stuck in trying to figure out how to adapt them so that I can use the scope to search for all the compliant potentials (i.e. potentials where potential.user_id == proposal.user_id). That means Im not searching the Proposal class, Im searching the specific proposal.

This post suggested defining Event.all inside the relevant controller action, but then how would I limit that so it only applied to the specific potentials edit line? I have other lines in my edit action which should not be tested on the Proposal table, but just the instance. If this were able to work, I imagine I would then need to rewrite my scope to try to exclude all the other proposals.

Is there a way to use an edit action in a controller with a scope, on a specific instance?

回答1:

I would suggest scopes like this:

scope :owner_potentials, -> (user_id) { where(user_id: user_id) }
scope :third_party_potentials, -> (user_id) { where.not(user_id: user_id) }

When calling these scopes you just need to pass current user's id.



回答2:

Scopes define queries for the AR class they are defined in. You say you have written owner_potentials and third_party_potentials scopes in proposal.rb. But if these scopes are meant to return a collection of potentials, then these should be defined in the Potential class. If you need to access these scopes from a proposal record, you can chain scopes to associations, e.g.

class Potential
  scope :owner_potentials, -> (user) { where(user: user) }
  scope :third_party_potentials, -> (user) { where.not(user: user) }
end

...

class ProposalsController # Proposals::PotentialsController..? imo Proposals::PotentialsController#edit sounds like an endpoint for editing exactly one potential record and nothing else, which doesn't sound like what you want. Your call on how to structure the controller/routes though.
  def edit
    @proposal = ... # Whatever your logic is to find the proposal

    @proposal.potentials.owner_potentials(current_user) # do something with the user's potentials
    @proposal.potentials.third_party_potentials(current_user) # do something with the potentials the user doesn't own
  end
end

You can see here how you chain an association (.potentials) to a scope (.owner_potentials).

Also, if you have an association, you can treat that association as a field in a where method, a la where(user: user) instead of where(user_id: user.id).

Last thing to note is that you probably want to change the name of the scopes with this refactor. potentials.owner_potentials(user) is a bit redundant. Maybe something like potentials.owned_by(user) ?