Rails: OR where condition on association - ruby-on-rails

I have a model called Facility. It has_many :addresses. The Address model has_one :city.
Now I want to run a condition:
to get all facilities that do not have associated address;
if they do have addresses check if they do not have city model associated with that addresses.
I have tried the first condition but I am unable to combine an OR for it.
This gets all facilities that do not have an address model associated to it
Facility.includes(:addresses).where( :addresses => {:facility_id => nil})
Some error tries are:
Facility.includes(:addresses).where( :addresses => ({:facility_id => nil}).or({:city_id => nil}) );
Facility.includes(:addresses).where( :addresses => ({:facility_id => nil}).or(:address => {:city_id => nil}) )

Try the following:
Facility.includes(:addresses)
.where('addresses.facility_id is null or addresses.city_id is null')
.references(:addresses)
You can also find interesting this post, concerning possible implementations of the or condition in the activerecord queries.

Try this
Facility.includes(addresses: :city).where("addresses.facility_id IS NULL OR addresses.city_id IS NULL");
Hope this would be helpful.

First, using includes when you want to do conditions on associations can have negative side effects:
You will eager load data, which, if not needed, means doing more work for nothing
The addresses that are eager loaded are only those matching the condition. So if you thn use addresses, you don't get all of them for that record (only those that matched the condition). This causes weird bug or hard to understand working code.
To avoid this and all other issues, I recommend to use a gem I made specifically for this: activerecord_where_assoc
Your problem can be simplified, I will do so after replying to your request as-is:
You seem to be using Rails 4.2 according to the tags. So you don't yet have access to the #or method yet. So this is how you could do it:
sql_no_address = Facility.assoc_not_exists_sql(:addresses)
sql_no_city = Facility.assoc_exists_sql(:addresses) { where_assoc_not_exists(:city) }
Facility.where("#{sql_no_address} OR #{sql_no_city}")
If you have Rails 5 or more:
Facility.where_assoc_not_exists(:addresses).or(Facility.where_assoc_not_exists([:addresses, :city]))
Now, a simpler solution. Notice that if a facility has no addresses, then it cannot have a city, because it must go through an address to get to a city. Really, your problem is just "I want Facilities that have no cities". This is what passing an array to the methods do, it tries to go from one to the other until the end.
Facility.where_assoc_not_exists([:addresses, :city])
Here are the introduction and examples. Read more details in the documentation.

Related

Using class methods in named_scope and rspec

I may have painted myself into a corner.
In some of my rails (2.3.18) named_scopes I've used class methods to retrieve known rows from the database - for example status values.
However, when I try to use these with rspec, I think I've got a problem because the fixtures (I'm using FactoryGirl) haven't loaded before the app gets loaded - so I get an error when its parsing the named_scopes (I think).
For example:
named_scope :active_users, :conditions => [ 'status_id = ?', UserStatus.Active.id ]
When the user model is loading it gives an error to effect
app/models/user.rb:34: Called id for nil, which would mistakenly be 4
which is the named_scope line.
user_status.rb
def self.Active
UserStatus.find_by_name('active')
end
So I think I've got two questions:
Is this an abuse of named_scope and if so what would be a better way of writing it?
Is it possible to get rspec to load some key data into the database before it loads the application?
Thanks
Your named scope is written fine. You need to check output of UserStatus.Active.id. It should return array of ids. As per the naming convention if you have written method named 'Active' in UserStatus then it is wrong. It should be in lowercase.
Also I do not understand the use of id in => UserStatus.Active.id. Can you put this method here?
UserStatus.Active must be giving you nil so
UserStatus.Active.id giving you this error. Because id of nil is 4. Make sure you are getting a record in Active method
Managed to answer my own question and am putting it here in case anyone else has the same issue.
To ensure that an attempt to access the database when the named_scope is parsed is avoided, I needed to wrap the :condition in a lamdba / proc as below
named_scope :active_users, lambda {{ :conditions => [ 'status_id = ?', UserStatus.Active.id ] }}
This now allows the application to be loaded, and then the data required for the tests to be loaded into the database ahead of the test as usual.

Query/scope not including unsaved records in association

Scenario: I have a quiz type setup where Questions have many Answers and also have a Response provided by the user (omitted). A response has a selected attribute to indicate the user's choice and a correct? method that compares selected with the correct_answer.
Code: is here in this GitHub repo, along with seed data. Quick links to:
Question.rb
Answer.rb
Response.rb
Problem: I want to return all responses for a question that are correct however, unsaved records are not included.
I've tried a couple of different ways, as you'll see in the code including scope, question.correct_responses and also inverse_of (which I've read is supposed to be automatic now) - but to no avail.
Basically, I'm expecting following code to return 1, not 0.
q=Question.first
r=q.responses.build
r.selected = q.correct_answer
q.responses.correct.size # => 0??! wtf man!?
Your assistance is greatly appreciated.
When you use a scope you're going to the database.
Since you aren't saving the response, you don't want to go to the database. Instead, you should use something like the line below, which will select all of the question's "correct" responses and then count them.
q.responses.select { |r| r.correct? }.size
EDIT: or the short syntax for select:
q.responses.select(&:correct?).size

ActiveRecord Includes n+1 issues

I am using the Bullet gem to assist me in finding my n + 1 errors for my ActiveRecord queries. I currently am passing in:
#user = User.includes(:routines => {:lifts => [:exercise, :infos]}).find(current_user.id)
To me this means I am loading the current user, his routines, those routines' lifts, and those lifts' exercise and infos (which are sets).
Is my assumption true?
The Bullet gem is giving me two errors in which it is claiming I need to:
Lift => [:routine] so it says add ".include => [:routine]"
AND
Lift => [:infos] so it says add ".include => [:infos]"
Would somebody be able to explain this to me?
Thank you!
You definitely are on the right path. I highly recommend brushing up on this via http://guides.rubyonrails.org/
Your setup supports pre-loading when you access data like this:
routines = #user.routines
lifts = #user.routines.map(&:lifts)
Can you please describe how you are attempting to access this data? It appears that you may be trying to access routine via:
lift.routine
How are you accessing lift?
You might want to make sure that you use :inverse_of when specifying your associations.
Does Routine have many :lifts?

Convert ActiveRecord habtm query to Arel

I have a pretty common habtm relationship:
Photo has_and_belongs_to_many :tags
Tag has_and_belongs_to_many :photos
In my Photo model I've got a method "with tags" that I use to find a photo that is tagged with a given set of tag_ids. This query needs to match only photos that have all of the given tags, but disregarding the presence or lack of any other tags. Here's my method:
def self.with_terms( array )
select('distinct photos.*').joins(:tags).where('tags.id' => array).group("photos." + self.column_names.join(', photos.')).having("count(*) = #{array.size}")
end
This works as expected.
Now, in order to integrate this better with some other libraries I'm using, I need to re-write this in Arel. (make it an Arel node?, not sure what you normally call this).
I've been experimenting with this, but to be honest I've never tried to use Arel before, so I'm a little lost. I've been experimenting in the console and tried:
t = Photo.arel_table
q = t.join(:tags).on(t[:tags_id].in(array))
Photo.where(q)
But, (1) I don't think q is the right query in the first place, and (2) it creates an Arel::SelectManager, which when passed to a where call raises Cannot visit Arel::SelectManager. So, obviously I'm doing this wrong.
Update: Just to be extra-specific here, I'm looking to return an Arel node, because I'm working with a gem (ransack) that expects you to pass it Arel nodes for search methods. Ransack will chain this Arel node with others in generating complex search queries.
Could an Arel guru show me how do this correctly?
It's hard to find good Arel documentation, but #Philip C has put together some useful slides, referenced in his answer to this question.
The following should be what you're looking for:
photos = Arel::Table.new(:photos)
tags = Arel::Table.new(:tags)
photo_tags = Arel::Table.new(:photo_tags)
q = photos[:id].in(
photos.project(photos[:id])
.join(photo_tags).on(photos[:id].eql(photo_tags[:photo_id]))
.join(tags).on(photo_tags[:tag_id].eql(tags[:id]))
.where(tags[:id].in(array))
.group(photos.columns)
.having(tags[:id].count.eq(array.length))
)
This results in an Arel::Nodes::In instance that you should be able to use directly as in Photo.where(q).
UPDATE:
After looking through the documentation and some of the source for ransack, there doesn't seem to be any natural way to define a custom predicate involving a subquery, which is necessary in your case (because predicates must fit into a where clause). One way to work around this might be to take advantage of the :formatter that your predicate uses as follows:
Ransack.configure do |config|
config.add_predicate 'with_tag_ids',
:arel_predicate => 'in',
:formatter => proc {|tag_ids| tags_subquery(tag_ids) },
:validator => proc {|v| v.present?},
:compounds => true
end
You can define tags_subquery(tag_ids) as a method that generates the arel node as above but replaces array with tag_ids and calls .to_sql on it before returning it (the formatter needs to return a string, not a node).
I haven't tried this, so I'll be thrilled if it works!

rails/activerecord search eager loaded associations

I have a simple find statement as such:
m = MyModel.find(1, :include => :my_children)
With m.mychildren being an Array; is there anyway to find a particular record from within the array without having to iterate over the entire thing. If I do mychildren.find(1), a new DB query is issues, which doesn't make sense, since they are all loaded already
It looks like there's a little Rails magic going on here. Where Enumerable#find is being overridden by ActiveRecord::Base#find on methods created for associations.
On the upside Enumerable#find is aliased to Enumerable#detect.
Unfortunately Enumerable#find/Enumerable#detect have significantly different syntax from ActiveRecord::Base#find.
So you can't just do mychildren.find(1), instead you've got to do mychildren.detect{|c| c.id == 1} if you want to avoid hitting the database again. You may also want to consider extending Array for a more DRY way of doing this.
class Array
def id_find id
self.detect{|element| element.id == id}
end
end
I'm not quite sure what your asking, but have you tried select:
m.mychildren.select{ |child| child == <<some_statement>> }
This won't hit the database assuming you've used the :include option as you stated in your question.
Alternatively, if you know the number of the child you want, you should be able to just use
m.mychildren[1]

Resources