How to merge multiple 'having' clause relation in rails 3 - ruby-on-rails

Facing problem for merging multiple having clause ... As in my model i had two scopes written
1.)
scope :vacant_list, lambda {|daterange|
where("vacancies.vacancy_date" => daterange , "vacancies.availability" => ["Y" ,"Q"]).joins(:vacancies).group("vacancies.property_id").having("count(vacancies.vacancy_date) >= #{daterange.count}") unless daterange.blank?
}
2.)
scope :amenity_type, lambda {|term|
where("amenities.name" => term).joins(:amenities).group("amenities.property_id").having("count(amenities.name) >= #{term.size}") unless term.blank?
}
I need to do something like this
#model = Model.vacant_list(daterange).amenity_type(term)
But i always get wrong number of arguments (2 for 1)
/home/vivek/.rvm/gems/ruby-1.9.2-p180/gems/arel-2.0.10/lib/arel/select_manager.rb:100:in `having'
/home/vivek/.rvm/gems/ruby-1.9.2-p180/gems/activerecord-3.0.6/lib/active_record/relation/query_methods.rb:180:in `build_arel'
/home/vivek/.rvm/gems/ruby-1.9.2-p180/gems/activerecord-3.0.6/lib/active_record/relation/query_methods.rb:149:in `arel'.
If i remove any one of scopes having clause then the above action works perfectly .
Is there any way to like-
#model = Model.vacant_list(daterange) and then remove the active record relation and then apply #model.amenity_type(term).
I tried lots of things but didnt find any solution for this.....

I think you're doing this wrong - it took me quite a while to dig out what the actual intent of the 'having' clauses above was. From what I can tell, it looks like the idea is that you pass in an array of dates or amenities and want to find properties that match all of them.
The underlying issue is that (AFAIK) code like this will NOT do the right thing:
# NOTE: WILL NOT WORK
scope :vacant_on, lambda { |date| where('vacancies.vacancy_date' => date, "vacancies.availability" => ["Y" ,"Q"]).joins(:vacancies) }
scope :vacant_list, lambda {|daterange|
daterange.inject(self) { |rel, date| rel.vacant_on(date) }
}
Unless this has changed in Arel (haven't poked it much) then this fails because you end up with exactly one join to the vacancies table, but multiple where clauses that specify incompatible values. The solution is to do multiple joins and alias them individually. Here's how I used to do it in Rails 2:
named_scope :vacant_on, lambda { |date|
n = self.connection.quote_table_name("vacancies_vacant_on_#{date}")
{ :joins => "INNER JOIN vacancies AS #{n} ON #{n}.property_id = properties.id",
:conditions => ["#{n}.vacancy_date = ? AND #{n}.availability IN (?)", date, ["Y","Q"]] }
}
Explicitly specifying an 'AS' here lets multiple versions of this scope coexist in one query, and you get the results you'd expect.
Here's a rough translation of this to modern Arel syntax:
scope :vacant_on, lambda { |date|
our_vacancies = Vacancy.arel_table.alias
joins(our_vacancies).on(our_vacancies[:property_id].eq(Property.arel_table[:id])).
where(our_vacancies[:vacancy_date].eq(date),
our_vacancies[:availability].in(["Y" ,"Q"]))
}
Haven't tried it, but this post:
http://www.ruby-forum.com/topic/828516
and the documentation seem to imply it would do the right thing...

Related

How do I combine ActiveRecord results from multiple has_many :through queries?

Basically, I have an app with a tagging system and when someone searches for tag 'badger', I want it to return records tagged "badger", "Badger" and "Badgers".
With a single tag I can do this to get the records:
#notes = Tag.find_by_name(params[:tag_name]).notes.order("created_at DESC")
and it works fine. However if I get multiple tags (this is just for upper and lower case - I haven't figured out the 's' bit either yet):
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger'])
I can't use .notes.order("created_at DESC") because there are multiple results.
So, the question is.... 1) Am I going about this the right way? 2) If so, how do I get all my records back in order?
Any help much appreciated!
One implementation would be to do:
#notes = []
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger']).each do |tag|
#notes << tag.notes
end
#notes.sort_by {|note| note.created_at}
However you should be aware that this is what is known as an N + 1 query, in that it makes one query in the outer section, and then one query per result. This can be optimized by changing the first query to be:
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger'], :includes => :notes).each do |tag|
If you are using Rails 3 or above, it can be re-written slightly:
Tag.where("lower(name) = ?", "badger").includes(:notes) do |tag|
Edited
First, get an array of all possible tag names, plural, singular, lower, and upper
tag_name = params[:tag_name].to_s.downcase
possible_tag_names = [tag_name, tag_name.pluralize, tag_name.singularize].uniq
# It's probably faster to search for both lower and capitalized tags than to use the db's `lower` function
possible_tag_names += possible_tag_names.map(&:capitalize)
Are you using a tagging library? I know that some provide a method for querying multiple tags. If you aren't using one of those, you'll need to do some manual SQL joins in your query (assuming you're using a relational db like MySQL, Postgres or SQLite). I'd be happy to assist with that, but I don't know your schema.

How can I compose/chain queries in Rails 2?

I have one query that gets the total count of rows with one condition, and a second query that gets the total count of rows with the same condition plus another condition. Ideally, I wouldn't repeat myself in the code and could instead just chain/compose the extra condition onto the first query.
I'm thinking of something like this.
query1 = Table.find(:all, :conditions => "condition1")
query2 = query1.find(:all, :conditions => "condition2")
It'd also be nice to find out what this looks like for the Table.count use case, since that's what I'm actually trying to do at the moment.
I'm guessing that the ActiveRecord::Base has some method that will return the query object as opposed to executing it, but I haven't found that in the docs.
Although Rails 3 makes this significantly easier, you can always do it in Rails 2 with a little hack that emulates it:
# config/initializers/rails2_where_scope.rb
class ActiveRecord::Base
named_scope :where, lambda { |conditions| {
:conditions => conditions
}}
end
This way you can chain together multiple conditions in a manner that's forward-compatible with Rails 3:
query2 = Table.where(condition1).where(condition2).all
Rails 3 uses AREL to do most of the SQL computations so that's why it's much more flexible than Rails 2.

Combine two ActiveRecord Query results

I currently have two active record queries that I would like to combine together
joins("join relationships ON user_id = followed_id").
where("follower_id = #{user.id}")
and
where(:user_id => user.id)
Basically I want the results of the second one to appear with the first similar to a UNION statement in SQL. Can it be done in ActiveRecord in this way?
I would prefer to use a union rather that have to join all the followed_ids in a string and use the IN clause in sql.
Any ideas?
-----Edit------
I am looking for a way to get this to work with lazy loading
This was useful for me:
Model.where(...) | Model.where(...)
Use relation & relation:
Model.joins("join relationships ON user_id = followed_id").where("follower_id = {user.id}") & Model.where(:user_id => user.id)
This was useful for me:
An updated version of Rails/ActiveRecord may support this syntax natively. It would look similar to:
Model.where(foo: 'bar').or.where(bar: 'bar')
Or you can, simply sticking with the following works great:
Model.where('foo= ? OR bar= ?', 'bar', 'bar')
referred to this link

rails - activerecord ... grab first result

I want to grab the most recent entry from a table. If I was just using sql, you could do
Select top 1 * from table ORDER BY EntryDate DESC
I'd like to know if there is a good active record way of doing this.
I could do something like:
table.find(:order => 'EntryDate DESC').first
But it seems like that would grab the entire result set, and then use ruby to select the first result. I'd like ActiveRecord to create sql that only brings across one result.
You need something like:
Model.first(:order => 'EntryDate DESC')
which is shorthand for
Model.find(:first, :order => 'EntryDate DESC')
Take a look at the documentation for first and find for details.
The Rails documentation seems to be pretty subjective in this instance. Note that .first is the same as find(:first, blah...)
From:http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263
"Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args)."
Digging into the ActiveRecord code, at line 1533 of base.rb (as of 9/5/2009), we find:
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
This calls find_every which has the following definition:
def find_every(options)
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
records = find_by_sql(construct_finder_sql(options))
if include_associations.any?
preload_associations(records, include_associations)
end
end
records.each { |record| record.readonly! } if options[:readonly]
records
end
Since it's doing a records.each, I'm not sure if the :limit is just limiting how many records it's returning after the query is run, but it sure looks that way (without digging any further on my own). Seems you should probably just use raw SQL if you're worried about the performance hit on this.
Could just use find_by_sql http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002267
table.find_by_sql "Select top 1 * from table ORDER BY EntryDate DESC"

Variable field name in named_scope?

In a Rails model I am trying to acheive a named_scope that filters on a start_date and end_date. This is easy. But I am going to have to do it on lots of different fields on many occasions.
Is this asking for trouble? If so why (SQL injection?) and is there another way to acheive this.
named_scope :between, lambda {|start_date, end_date, field|
{ :conditions => ["#{field} >= ? AND #{field} <= ?", start_date, end_date] }
}
EDIT: Solution Used
Using Eggdrop's line of thinking I went with:
##valid_fields = %w(fields in here)
named_scope :between, lambda{ |start_date, end_date, field_name|
field = (##valid_fields.include?(field_name)) ? (field_name) : raise (ActiveRecord::StatementInvalid)
{ :conditions => ["#{field} >= ? AND #{field} <= ?", start_date, end_date]}
}
Now I can reuse my named_scope for fields I wish to filter on date range without rewriting essentially the same scope over and over and whitelist the field names to avoid any mucking about with my column names and tricky SQL injection should the code ever get exposed to user input in the future.
Maybe you could write a method in your model to validate 'field':
If table x, then 'field' must be a particular existing date field in that table.
In other words you don't allow external input into 'field' directly - the external input has to map to known attributes and defined conditions specified in your validate method.
In general, though, this overall direction would not seem to be recommended.

Resources