How do I create a scope for an array? - ruby-on-rails

The following code gives an empty scope. Category_ids is an array of categories.
scope :art, ->{ where(:category_ids => '1') }
How do I check to see if one of the categories exist in the array?

If you use Postgres you can use this approach: https://www.viget.com/articles/searching-serialized-fields-in-rails-using-postgres-arrays

get the categories
correct your where query
Example:
has_many :categories
scope :art, -> { required = [Category.first]; where(categories: required) }
I assume that in your model, you have categories association. In this case, you can just use categories: required in your where query. required should be set to an array of categories which you wanted

You say that category_ids is an array of categories(I'm assuming category id's). Are you trying to return all records with a category ID that is in that array? If so you're looking for:
scope :art, -> { where (:category_id => category_ids) }
Or with the new ruby syntax:
scope :art, -> { where(category_id: category_ids) }
If I've misunderstood and you're looking for any record with a category ID of 1, then you're looking for:
scope :art, -> { where(category_id: '1') }

Related

RAILS - SCOPE WITH 2 TABLES

RAILS - SCOPE FILTER THROUGH 2 TABLES
Hi, I'm new to Rails and I'm using Translate to post here.
I have the scenario below:
Ata_Public -> Ata_Object -> Products
How can I set up a filter to select all Ata_publics through the category_id inside Products?
scope :filter_category, -> (params) {
params[:category_id].present? ?
joins(:ata_objects)
.joins(:products)
.where("ata_objects.products.category_id = ?", params[:category_id])
:
all
}
I'm doing it this way, but I don't have the expected result.
What should my scope look like?
Relationships are right.
clarifying, inside the Ata_Public model, I'm creating this scope: filter_category, to pass the ID and list only the minutes that have the product with this category_id.
https://i.stack.imgur.com/V0I0s.png
This should work:
scope :filter_category, -> (params) {
joins(ata_objects: :products).where(
products: { category_id: params[:category_id] }
) if params[:category_id].present?
}

Rails Model scope chaining based on dynamic scope name list

Let's say I have some model
class MyModel < ApplicationRecord
scope :opened, -> { where(status: 'open') }
scope :closed, -> { where(status: 'closed') }
scope :colored, -> { where.not(color: nil) }
# etc
end
I can call scope chains like
MyModel.opened.colored
MyModel.send('opened').send('colored')
But how can I make scope chaining based on dynamic scope token list? I mean
scopes = ['opened', 'colored', ...]
The list may be very long and I need some general solution to do it as simple as possible, like MyModel.send_list(scopes).
More as result of scope, you can add like,
scope :send_list, -> (*scopes) { scopes.inject(self) { |out, scope| out.send(scope) } }
send this like YourModel.send_list(*scopes)

Using rewhere on joined conditions

I have this code:
class Meeting
has_many :attendees, class_name: 'MeetingsUsers', autosave: true
scope :x, -> { Meeting.where(x:13) }
scope :y, -> { Meeting.where('x':13) }
scope :z, -> { Meeting.joins(:attendees).where('meetings_users.user_id': 123)}
end
Rewhere works in these situations:
Meeting.x.to_sql
Meeting.x.rewhere(x: 1234).to_sql
Meeting.y.to_sql
Meeting.y.rewhere('x': 1234).to_sql
But in this case it doesn't:
Meeting.z.to_sql
The resulting SQL is this one:
"SELECT \"meetings\".* FROM \"meetings\" INNER JOIN \"meetings_users\" ON \"meetings_users\".\"meeting_id\" = \"meetings\".\"id\" AND \"meetings_users\".\"deleted_at\" IS NULL WHERE \"meetings\".\"deleted_at\" IS NULL AND \"meetings_users\".\"user_id\" = 123"
With rewhere:
Meeting.z.rewhere('meetings_users.user_id': 1234).to_sql
The resulting SQL is this one:
"SELECT \"meetings\".* FROM \"meetings\" INNER JOIN \"meetings_users\" ON \"meetings_users\".\"meeting_id\" = \"meetings\".\"id\" AND \"meetings_users\".\"deleted_at\" IS NULL WHERE \"meetings\".\"deleted_at\" IS NULL AND \"meetings_users\".\"user_id\" = 123 AND \"meetings_users\".\"user_id\" = 1234"
As you can see there are two conditional on the same field:
"meetings_users\".\"user_id\" = 123 AND \"meetings_users\".\"user_id\" = 1234"
So rewhere didn't had effect by some mysterious reason...
This is really an ActiveRecord bug, you can see on:
https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/relation/query_methods.rb#L650
That rewhere just send the keys of the first hash object, that latter on are consider as 'columns to be removed from from where caluse'.
You can check the end of the process here:
https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/relation/where_clause.rb#L135
They have changed this last method in rails 5.0.2, but since the other ones haven't been changed, the behavior of rewhere is still the same.
You are only able to work this around doing:
class Meeting
has_many :attendees, class_name: 'MeetingsUsers', autosave: true
scope :x, -> { Meeting.where(x:13) }
scope :y, -> { Meeting.where('x':13) }
scope :z, -> { Meeting.joins(:attendees).where(meetings_users: {user_id: 123})}
end
query = Meeting.z
query.where_clause = query.where_clause.except('user_id')
query.where(meetings_users: {user_id: 1234})
Notice that this only works if you are using the where with hash, because using as string ll cause an ActiveRecord::ImmutableRelation error.

Drying up multiples scopes with simialar queries

I am noticing a trend with my scopes and trying to figure out how to make it dry
scope :newest, -> { order('created_at DESC') }
scope :top_sold, -> { order('qty_sold DESC') }
scope :most_viewed, -> { order('qty_viewed DESC') }
scope :most_downloaded, -> { order('qty_download DESC') }
scope :most_favorited, -> { order('qty_favorited DESC') }
I would like to pass in the column I want sorted so that I can call it on Photo. I tried this, but running into problems
scope :sort_photos, -> type { order('type DESC') }
Photo.sort_photos('qty_download')
Am I on the right path or is there a smarter way to accomplish this?
Pass type as a scope parameter and use that in order clause with string interpolation:
scope :sort_photos,->(type) { order("#{type} DESC") }
Then do:
Photo.sort_photos('qty_download')
The order method takes a String or a Hash. So instead of order('created_at DESC') you can do order(created_at: :desc), for example. So, to accomplish what you want, it's as simple as changing the key to your type variable:
scope :sort_photos, -> type { order(type => :desc) }
I would also recommend using a sentinel for your order scopes such as by_. So that the scope by_sort_photos doesn't get overridden by definition of a sort_photos method or assoication later.
Finally, it's good to have a public interface full of methods, as opposed to requiring knowledge of the class attributes and passing those attribute names into a public interface method. So I'd keep the many different scopes that you have, but perhaps have them all refer to the one, general scope as we've defined here. So:
scope :newest, -> { by_most_recent_type('created_at') }
scope :top_sold, -> { by_most_recent_type('qty_sold') }
scope :by_most_recent_type, -> type { order(type => :desc) }

Using another class scope in existing scope Activerecord

I want to use the scope of another class in the scope of the first class
so instead of
scope :active, -> {includes(:b).where(b: {column: 'ACTIVE'}).where(a: {column2: 'ACTIVE'})}
I want to be able to use a scope of b
scope :active, -> {includes(b.active).where(a: {column2: 'Active'})}
You can do this using merge:
scope :active, -> { includes(:b).merge(B.active)
.where(a: {column2: 'Active'}) }
Note: I used B to represent the model class for the b column or object.
Or, assuming you're in a's model already:
scope :active, -> { includes(:b).merge(B.active)
.where(column2: 'Active') }
Also, if you WANT eager loading then using includes is great. Otherwise, it's faster and less overhead to use joins, like this:
scope :active, -> { joins(:b).merge(B.active)
.where(column2: 'Active') }
I recommend to use scope on model, if it's admin specific, then can separate it to concern
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
module AdminUserScopes
extend ActiveSupport::Concern
included do
scope :admin_scope1, -> { includes(:b).where(b: {column: 'ACTIVE'}).where(a: {column2: 'ACTIVE'}) }
scope :admin_scope2, -> { admin_scope1.where(a: {column2: 'Active'}) }
end
end
# in your model
include AdminUserScopes
# in active_admin
scope :active, -> { admin_scope1 }
scope :active2, -> { admin_scope2 }
Upd:
If you want to use one condition to other model then can use merge
Dog.all.merge(User.males) # => select * from dogs where sex = 1;
If you want to use in association filtering, then:
Post.where(user: User.males) # => select * from posts where user_id in (select users.id from users where sex = 1)
In your case I guess you have A and B, and you want to get active A-records what connected to active B-records
# in A
scope :active, -> { where(column: 'ACTIVE') }
# in B
scope :active, -> { where(column2: 'ACTIVE', a: A.active) }
# in somewhere else
scope :active, -> { where(a: A.active) } # => have active A which have active B
p.s. it's much easier with more informative names, "A's" and "B's" are hard :)

Resources