How to define named scope/class query in rails - ruby-on-rails

I'm having difficulty understanding how to define a scope/class method in my Rails application.
Basically, here is the query that I want it to do:
self.visible(current_user) -> {
#requests.where.not(visible==group && (request.user.group !=
current_user.group))}
Basically, it's a site where users can make posts/requests, but I wanted the poster to be able to limit the visibility of the post (ie, so that if they selected the option where visible = "group", then other users outside of that group cannot see it (only users that share the same 'group' can see that post).
Please help me with the syntax, I couldn't figure out how to use the AND condition in the WHERE statement.
Right now, I have:
scope :visible, -> (group) { joins(:user).where.not(visible: 'group',
users:{group: group}).references(:users)}
which returns the opposite of what I want.
However, when I remove the not, it doesn't give me anything.
thanks.

I think what you're looking for is this.
scope :visible, -> (group) { joins(:user).where("visible <> 'group' OR (visible = 'group' AND users.group = ?)", group) }

Related

How to speed up a rails controller queries?

My app seams to be getting bogged down. Can someone can help me optimize this controller code to run faster? Or point me in the right direction.
I'm trying to display a list of customers which are defined by active is true and a list of potential customers which active is false. Archived customers are archived true.
Thank you.
if current_user.manager?
get_customers = Customer.where(:archived => false)
#cc = get_customers.where(:active => true)
#current_customers = #cc.where(:user_id => current_user.id)
#count_current = #current_customers.count
#pc = get_customers.where(:active => false)
#potential_customers = #pc.where(:user_id => current_user.id)
#count_potential = #potential_customers.count
end
How does this look for improving speed?
model
scope :not_archived, -> { where(:archived => false) }
scope :current_customers, -> { where(:active => true).not_archived }
scope :potential_customers, -> { where(:active => false).not_archived }
scope :archived_customers, -> { where(:archived => true) }
Controller
#current_customers = Customer.current_customers.includes(:contacts,:contracts)
View
link_to "Current Clients #{#count_current.size}"
You may find help here
As #Gabbar pointed out and I will add to it, your app right now is eager-loading (opposite of lazy-loading) which means that you are loading more from the database than needed. What we need to do is optimize but that totally depends on your use-case.
Whatever the use-case, you can do a few common things to make things better:
You can implement pagination (there are gems for it and you can do it yourself too) or infinite scrolling. In this case, you will be loading a set amount of records from db at first but as soon as user wants more, either they will scroll down or click 'next' button and your action will be called again but with an increment in the page number which means get the next set of records.
Implementing based on scroll involves JS and the view-height etc. but pagination is much simpler.
Gems:
kaminari gem
infinite-pages
Using includes
One more thing you must do is, use include in query if your records are related. Using include is tricky but very very helpful in time-saving. It will fetch the related needed record together in one go from database unlike your code going to and fro database multiple times. Fetching from database takes a lot of time as compared to fetching from RAM.
#users = User.all.includes(:comments) #comments for all users brought along with users but saved in RAM for future access.
#comments = #users.map(&:comments) # no need to go to db again, just RAM.
Using scopes in models:
Creating scopes in models helps too. In your case, you should create scopes like this:
scope :archived_customers, -> { where('archived IS false') }
scope :potential_customers, -> { where('active IS false') }
**OR**
scope :archived_customers, -> { where(:archived => false) }
scope :potential_customers, -> { where(:active => false) }
Loading all the available records in a single query can be very costly. Moreover, a user may be interested only in a couple of the most recent records (i.e., the latest posts in a blog) and does not want to wait for all records to load and render.
There are couples of ways to sort out this problem
example#1 implementation of Load More
example#2 implementation of Infinite Scrolling
example#3 implementation of pagination

Active Record & Rails: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list

I've looked up other S.O questions but I don't quite understand how to fix the issue.
Trailers belong_to Movie and Movie has_many Releases. My scope on Trailer:
:released scope intention is to get trailers whose movies have at least one release and are more recent than 25 years:
scope :released, -> {
joins(movie: :releases).where("movies.release_date > ?", 25.years.ago)
}
Then I have another scope which orders Trailers by their parent Movie imdb_rating:
scope :top_rated, -> {
where("movies.imdb_rating IS NOT NULL").order("movies.imdb_rating desc")
Now if I call Trailer.released.top_rated, the issue is that the same Trailer shows up multiple times if it's movie has multiple releases. I've tried adding uniq in pretty much every place I can think of, but I just get the error:
ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
How can I show each Trailer only once?
Hey if you have used mysql then try this way add one more scope like
scope :uniq_trailers, -> {
group("releases.movie_id")
}
And try to select this way
Trailer.released.top_rated.uniq_trailers
You may modify your scopes a bit like this:
Movie
scope :released, -> { where("release_date > ?", 25.years.ago) }
scope :top_rated, -> { where.not(imdb_rating: nil).order(imdb_rating: :desc) }
Trailer
scope :released_top_related, -> { joins(:movie).merge(Movie.released.top_related) }

Rails - why can't I use activerecord exists in my scope?

I'm trying to utilize .exists?() to return true or false, pretty simple. It would be nice to do the one-liner scope like so:
scope :any_alternates, lambda{|apikey| Track.exists?(:track_id => apikey)}
Or even using this scope syntax:
scope :any_alternates, ->(apikey) {Track.exists?(:track_id => apikey)}
But for some reason, the above scopes will return all rows in my db table when there's not a match. It works how it should when it finds a match however, but breaks if none...
I'm forced to create a method, which (to my knowledge) should be doing the same thing in the above scope:
def self.any_alternates(apikey)
return Track.exists?(:track_id => apikey)
end
Any idea why .exists?() isn't working inside of my scope?
After some testing...
If there is no match, then the scope will return all rows in the DB... (I updated above to mention that). I checked the generated query on both the scope and method to see if there's a difference, but they're the same:
SELECT 1 AS one FROM `tracks` WHERE `tracks`.`track_id` = '_btbd_uUmQT8hYUK3SrJ9Q' LIMIT 1
Update:
even though I'm searching on a column called track_id, this column is not setup as a relationship to another model. I know this is confusing, but that's how this table got setup (for good reason, beyond this issue so not worth touching on here)
Are you passing in nil? That would cause all records to be returned. You can drop the Track in the scope, like this:
scope :any_alternates, lambda{|apikey| exists?(:track_id => apikey)}
Here is what happens when you pass in nil:
> Track.any_alternates(nil).count
Track Exists (1.7ms) SELECT 1 AS one FROM "track" WHERE "tracks"."track_id" IS NULL LIMIT 1
instead of passing in a value:
> Track.any_alternates('X4DBA36gbtqgWl4F1')
Track Exists (0.4ms) SELECT 1 AS one FROM "tracks" WHERE "tracks"."track_id" = $1 LIMIT 1 [["track_id", "X4DBA36gbtqgWl4F1"]]
Are you sure that you have the right search? A Track having a track_id implies that there is a relation between Track and another track model which would be interesting.
Also the scope syntax is off. A scope is just a query of the table, so traditionally it is only things associated with the current model.
scope :any_alternates, ->(api_key) { |api_key| where(track_id: api_key }

Select all records that are not associated with other

I have two tables pages and menu_items, pages HAS MANY menu_items and menu_items belongs_to pages. In Rail 4 how can I select only those pages that are not linked with menu_items?
Thanks in advance!
Each of the following should work:
Page.includes(:menu_items).where( menu_items: { page_id: nil} )
or
Page.find(:all, conditions: { :menu_items.count: 0 } )
or
Page.where('id NOT IN (SELECT DISTINCT(page_id) FROM menu_items)')
Perhaps Page.where is what you're looking for. Effectively the where method calls a search through the Page database for all objects of the specified type. By calling it with the parameter of the menu's id equal to nil, you are searching for all pages where there is no menu id.
Ideally you would like to call Page.where(page.menu_items.empty?), however, of course, this isn't allowed.
Looking around this question is more or less exactly the same as yours. They solve it with:
Page.includes(:menu_items).where( :menu_items => {:page_id=>nil} )

Remove a 'where' clause from an ActiveRecord::Relation

I have a class method on User, that returns applies a complicated select / join / order / limit to User, and returns the relation. It also applies a where(:admin => true) clause. Is it possible to remove this one particular where statement, if I have that relation object with me?
Something like
User.complex_stuff.without_where(:admin => true)
I know this is an old question, but since rails 4 now you can do this
User.complex_stuff.unscope(where: :admin)
This will remove the where admin part of the query, if you want to unscope the whole where part unconditinoally
User.complex_stuff.unscope(:where)
ps: thanks to #Samuel for pointing out my mistake
I haven't found a way to do this. The best solution is probably to restructure your existing complex_stuff method.
First, create a new method complex_stuff_without_admin that does everything complex_stuff does except for adding the where(:admin => true). Then rewrite the complex_stuff method to call User.complex_stuff_without_admin.where(:admin => true).
Basically, just approach it from the opposite side. Add where needed, rather than taking away where not needed.
This is an old question and this doesn't answer the question per say but rewhere is a thing that exists.
From the documentation:
Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
So something like:
Person.where(name: "John Smith", status: "live").rewhere(name: "DickieBoy")
Will output:
SELECT `people`.* FROM `people` WHERE `people`.`name` = 'DickieBoy' AND `people`.`status` = 'live';
The key point being that the name column has been overwritten, but the status column has stayed.
You could do something like this (where_values holds each where query; you'd have to tweak the SQL to match the exact output of :admin => true on your system). Keep in mind this will only work if you haven't actually executed the query yet (i.e. you haven't called .all on it, or used its results in a view):
#users = User.complex_stuff
#users.where_values.delete_if { |query| query.to_sql == "\"users\".\"admin\" = 't'" }
However, I'd strongly recommend using Emily's answer of restructuring the complex_stuff method instead.
I needed to do this (Remove a 'where' clause from an ActiveRecord::Relation which was being created by a scope) while joining two scopes, and did it like this: self.scope(from,to).values[:joins].
I wanted to join values from the two scopes that made up the 'joined_scope' without the 'where' clauses, so that I could add altered 'where' clauses separately (altered to use 'OR' instead of 'AND').
For me, this went in the joined scope, like so:
scope :joined_scope, -> (from, to) {
joins(self.first_scope(from,to).values[:joins])
.joins(self.other_scope(from,to).values[:joins])
.where(first_scope(from,to).ast.cores.last.wheres.inject{|ws, w| (ws &&= ws.and(w)) || w}
.or(other_scope(from,to).ast.cores.last.wheres.last))
}
Hope that helps someone

Resources