Select all records that are not associated with other - ruby-on-rails

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} )

Related

How to define named scope/class query in 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) }

How can I get all the records in a table where any value of an associated table is in a given array?

I think if I had a better idea how to word this question, I would have been able to find an answer already... Anyways, I have a table called Vendors that has a many-to-many relationship with a table called Basins. I would like to be able to retrieve all the vendors that have at least one basin in an array of basins that is passed in as input.
So if I had three vendors like:
vendor1.basins = [Basin.first, Basin.second]
vendor2.basins = [Basin.second, Basin.third]
vendor3.basins = [Basin.third, Basin.fourth]
And I wanted to get all the vendors containing anything from [Basin.first, Basin.fourth], I would get both vendor1 and vendor3. If the array was [Basin.first, Basin.second], I would get both vendor1 and vendor2. I thought select might be the way to go here, but everything I've tried has been flagrantly wrong.
Thanks in advance.
I am assuming you are using Rails >= v4.0.0.
You can get vendors like:
Vendor.joins(:basins).where(basins: { id: [Basin.first.id, Basin.fourth.id] })
I hope this will help you.
You can add scope to Vendor model:
scope :foo, ->(basins_arr){ joins(:basins).
where(basins: { id: basins_arr.collect(&:id)})

Rails update database field according to two other fields

So I have 2 fields in my Articles table.
- :vote_up
- :vote_down
I have methods in my app of updating the :vote_up and :vote_down fields that work fine. What I want to do is order my articles by total votes (:vote_up minus :vote_down).
What is the best way to do this. Can I do this directly in the controller with a certain method? Or must I create a :vote_total field that updates automatically according to the values of the other two fields (if so how do you do this).
Many thanks!
Don't do this in your controller. This is meant to be done in your model. Controllers should just use the model.
You can do this in 2 ways:
Solution 1
Try this in your console (rails c)
Article
.unscoped
.select(%q(articles.*, (articles.vote_up - articles.vote_down) AS vote_total))
.order(%q(vote_total DESC))
and the implement it as a scope in your Article class
scope :order_by_total_votes, -> {
select(%q(articles.*, (articles.vote_up - articles.vote_down) AS vote_total))
.order(%q(vote_total DESC))
}
Solution 2
Create a field vote_total for your Article and update it every time one of the vote fields gets updated (use a before_save callback). Then you can do the same as in solution 1, but without the select part.
Suggestion
I would go with solution 2, because amongst others it is faster in queries
Hope this helps.
Thanks #Hiasinho for your direction. I ended up just creating a :vote_total to my Article database and updated it on both my upvote and downvote methods like so
#article.update_attributes(vote_total: #article.vote_total + 1)
Obviously it was a -1 for downvotes.

Rails 4: how to use named scope with has_many associations

In my Rails 4 app Project (model) has_many Videos (model). I have a named scope in the videos model:
scope :live, where( is_deleted: 0, sent_to_api: 1 )
In one of my project views, I do this (project is an instance of Project):
project.videos.live.size
What I expect to get is the number of projects in that specific project but instead I get the number of videos in any project. It's as if .live is not returning a subset from .videos but rather replacing it.
I see it explained here that chaining named scopes with one another should be combined with logical AND but when applied to an "association method" [<--not sure the proper terminology for .videos in this context] that doesn't seem to be happening.
What's the right way to do this?
I believe it should read like this in Rails 4:
scope :live, -> { where(is_deleted: 0, sent_to_api: 1) }
The rails 4 docs and all examples in it show you passing in a callable object to the scope to ensure it gets called each time. If it doesn't work like this try implementing it as a class method and see how that works out for you.
http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html
I would just go for class methods and leave scopes behind. The syntax is much simpler because it's just like any other class method, including passing parameters to it.
Try:
def self.live
where( is_deleted: 0, sent_to_api: 1 )
end
And then:
project.videos.live.size
and see if it helps.
For more info, read here.

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