I have this :
model listing:
has_many: restrictions
model user:
has_many: restrictions
model restriction:
belongs_to: :user
belongs_to: :listing
By default all users are allowed to see all listings because there is no relation between listings and users, but I want to restrict users per listing if there is a record on restriction table.
For example:
If the record restrictions.user_id: 2 and restrictions.listing_id: 5 exits, the user with id 2 will see all listings except the listing with id 5
How can I do/describe that with rails?
What comes to mind is having a scope in Listing model which would exclude the restricted listing:
scope :accessible_listings, -> { where('id != ?',Restriction.where(user_id: current_user.id).map(&:listing_id)) }
I don't this a has_many association, maybe you can define a method to do what you want
class User
def listings
Listing.where.not(id: ((self.restrictions.map &:listing).uniq.map &:id))
end
end
As #nesiseka answered, with a little changes, using rails where.not
scope :accessible_listings, ->(user) { where.not(id: Restriction.where(user_id: user.id).map(&:listing_id) ) }
Related
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?
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.
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) ?
I am trying to learn how to write scopes in Rails.
I have models for user, organisation and organisation request. The associations are:
User
has_one :organisation
Organisation
belongs_to :owner, class_name: 'User'
has_many :organisation_requests
Organisation_request
belongs_to :organisation
In my organisation request model, I'm trying to write a scope to pick out all the organisation requests that belong to the organisation's owner.
scope :same_org, where(:organisation_id => owner.organisation_id)
Then in my organisation_requests controller, I have:
def index
#organisation_requests = OrganisationRequest.same_org
end
I tried the above. Owner is an alias for user. In each organisation, one user is nominated as that organisation's owner. I want that user to see an index of the organisation requests that come in for that owner's organisation.
Can anyone see what I've done wrong here? I'm not understanding something about how to write scopes.
In your model, try this:
scope :same_org, -> {where(:organisation_id => owner.organisation_id) }
The upvoted answer is wrong (nothing personal, #Hasmukh Rathod) - there is no organisation_id column in users table (it's actually vice versa - there is a user_id column in organisations table).
I would suggest the following solution:
scope :same_org, ->(owner_id) { joins(organisation: :user).where(users: { id: owner_id }) }
Having the above scope, what you'll need to do, is to pass the owner_id as an argument. I'm sure there is a way to get it working without passing an argument to scope, but I'm not yet sure how (I've just woke up :D).
So example:
owner_id = User.find(1).id
OrganisationRequest.same_org(owner_id) # would give you the expected collection of requests
I have 2 models. line_item and account.
line_item belongs to an account.
account has a column is_active.
I'm looking for a way to write a Rails scope to find all line_items where their account is_active = true
Something like
LineItem.should_display
EDIT
class LineItem < ActiveRecord::Base
scope :should_display, -> { joins(:account).where(accounts: {is_active: true}) }
end
This produces the same result as adding the following class method in your LineItem model.
def self.should_display
joins(:account).where(accounts: {is_active: true})
end
I think you can find more information in the Rails guides for Active Record Querying: http://guides.rubyonrails.org/active_record_querying.html
A User has_many Books. I want to add a scope to Books that only returns the most recent (created_at) book for each user_id. Something that groups the books by user_id and only gets the most recent for each user_id ?
To clarify, I could technically do this:
User.all.each { |user| user.books.first }
But I'd like to do it with a clean Active Record query.I'm having a lot of trouble figuring it out, but I imagine the solution isn't too complicated.
The easiest way to do this is to create a scope on books that, using a subquery, only selects books that are the latest ones owned by their user:
class Book < ActiveRecord::Base
belongs_to :user
scope :most_recent, -> {
where('NOT EXISTS(SELECT 1 FROM books b2 WHERE b2.user_id = books.user_id AND b2.created_at > books.created_at)')
}
end
This is just one of many options, but it's likely the most re-usable as you can join this relation onto others without worrying about your aggregations conflicting.
I am starting to create my sites in Ruby on Rails these days instead of PHP.
I have picked up the language easily but still not 100% confident with associations :)
I have this situation:
User Model
has_and_belongs_to_many :roles
Roles Model
has_and_belongs_to_many :users
Journal Model
has_and_belongs_to_many :roles
So I have a roles_users table and a journals_roles table
I can access the user roles like so:
user = User.find(1)
User.roles
This gives me the roles assigned to the user, I can then access the journal model like so:
journals = user.roles.first.journals
This gets me the journals associated with the user based on the roles. I want to be able to access the journals like so user.journals
In my user model I have tried this:
def journals
self.roles.collect { |role| role.journals }.flatten
end
This gets me the journals in a flatten array but unfortunately I am unable to access anything associated with journals in this case, e.g in the journals model it has:
has_many :items
When I try to access user.journals.items it does not work as it is a flatten array which I am trying to access the has_many association.
Is it possible to get the user.journals another way other than the way I have shown above with the collect method?
Hope you guys understand what I mean, if not let me know and ill try to explain it better.
Cheers
Eef
If you want to have user.journals you should write query by hand. As far as I know Rails does has_many :through associations (habtm is a kind of has_many :through) one level deep. You can use has_many with finder_sql.
user.journals.items in your example doesn't work, becouse journals is an array and it doesn't have items method associated. So, you need to select one journal and then call items:
user.journals.first.items
I would also modify your journals method:
def journals
self.roles(:include => :journals).collect { |role| role.journals }.flatten.uniq
end
uniq removes duplicates and :inlcude => :journals should improve sql queries.
Similar question https://stackoverflow.com/questions/2802539/ruby-on-rails-join-table-associations
You can use Journal.scoped to create scope with conditions you need. As you have many-to-many association for journals-roles, you need to access joining table either with separate query or with inner select:
def journals
Journal.scoped(:conditions => ["journals.id in (Select journal_id from journals_roles where role_id in (?))", role_ids])
end
Then you can use user.journals.all(:include => :items) etc