Advanced find in Rails - ruby-on-rails

I really suck at Rails' finders besides the most obvious. I always resort to SQL when things get more advanced than
Model.find(:all, :conditions => ['field>? and field<? and id in (select id from table)', 1,2])
I have this method:
def self.get_first_validation_answer(id)
a=find_by_sql("
select answers.*, answers_registrations.answer_text
from answers_registrations left join answers on answers_registrations.answer_id=answers.id
where
(answers_registrations.question_id in (select id from questions where validation_question=true))
and
(sale_registration_id=#{id})
limit 1
").first
a.answer_text || a.text if a
end
Can someone create a find method that gets me what I want?
Regards,
Jacob

class AnswersRegistration < ActiveRecord::Base
has_many :answers
end
id = 123
the_reg = AnswersRegistration.first(
:joins => :answers,
:conditions => '(question_id in (select id from questions where validation_question = true)) and (sale_registration_id = ?)', id)
(untested)

Just use binarylogic's Searchlogic gem if that satisfies your need.
Here you go: http://github.com/binarylogic/searchlogic

Sometimes AR will choke on complicated nested conditions, but in theory you should be able to do this:
AnswersRegistration.first(:conditions => { :question => { :validation_question => true },
:sale_registration_id => id },
:include => :answer)

Related

Rails How to use join in this specific situation?

I have three models, each connected as such: Groups which has many Students which has many Absences.
Absences has a field called created_on.
I only have a group id and would like to obtain all students with an absence of today.
I have created this method inside my Student model:
# Inside student.rb
def self.absent_today_in_group (group)
#SQLITE
find(:all, :joins => :absences, :conditions => ["STRFTIME('%d', created_on) = ? AND STRFTIME('%m', created_on) = ?", Date.today.day, Date.today.month])
#POSTGRES
#find(:all, :joins => :absences, :conditions => ["EXTRACT(DAY FROM created_on) = ? AND EXTRACT(MONTH FROM created_on) = ?", Date.today.day, Date.today.month])
end
Why would that query not return anything? And how could I then also check for group_id?
What version of rails are you using? You can do this in rails 3:
def self.absent_today_in_group(group)
joins(:absences, :group).where(
'absences.created_on' => (Time.now.beginning_of_day..Time.now.end_of_day),
'groups.id' => group.id
)
end
That would find all users which were absent for today for given group.
Shouldnt this be :where and not :conditions?

How do date comparisons work in ActiveRecord?

I am new to Rail and trying to make a query in ActiveRecord. I am trying to get all of the records with the status of 'Landed', that are over 60 days old. My query works up to the point of getting all of the projects with the status of 'Landed'. When I add in the last condition of "created_at < ? ", then I always get an empty relation. I know that I have projects that fit that description, so I am doing something wrong in my query and dont understand. I believe my error is in the date comparison, but I am not sure.
1. Projects
belongs_to :status
has_many :project_status_histories
2. Status
has_many :projects
has_many :project_status_histories
3. Project_Status_Histories
belongs_to :status
belongs_to :project
Project.find(:all, :joins => [:project_status_histories, :status], :conditions => {:projects => {:status_id => Status.where(:name => 'Landed').first.id }, :project_status_histories => {:created_at => ["created_at < ?", (Date.today - 60.days)]}})
I have tried to build the query, step by step, with the dbconsole and am not having any luck. Thanks for all the help in advance.
I don't think it's the date arithmetic. One nice way to do this would be with named scopes. Add the following to project.rb:
scope :landed, joins(:status).where('statuses.name' => 'Landed')
scope :recent, lambda \
{ joins(:project_status_histories) \
.where('project_status_histories.created_at < ?', Date.today - 60.days) }
Then you can retrieve the relevant records/objects with:
Project.landed.recent
This worked for me in my test. You should also check out the rails guide, from which I stole most of this:
http://guides.rubyonrails.org/active_record_querying.html#scopes
Your query is a little bit complicated...
I would rather do it like this:
Project.where("status.name = ? AND project_status_histories.created_at < ?", "Landed", Time.now.day - 60.days)
I think this should work better. Let me know if it doesn't, maybe I've wrote something wrong, unfortunately I can't test it right now...
[Edit]
You also might want to see what is the generated SQL, use the "explain" method for that, just add it to the end of your query and print the result, for instance with your query:
Project.find(:all, :joins => [:project_status_histories, :status], :conditions => {:projects => {:status_id => Status.where(:name => 'Landed').first.id }, :project_status_histories => {:created_at => ["created_at < ?", (Date.today - 60.days)]}}).explain

Rails 2.3: How to turn this SQL statement into a named_scope

Having a bit of difficulty figuring out how to create a named_scope from this SQL query:
select * from foo where id NOT IN (select foo_id from bar) AND foo.category = ? ORDER BY RAND() LIMIT 1;
Category should be variable to change.
What's the most efficient way the named_scope can be written for the problem above?
named_scope :scope_name, lambda { |category|
{
:conditions => ["id NOT IN (select foo_id from bar) AND foo.category = ?", category],
:order => 'RAND()',
:limit => 1
}
}
More of a comment than an answer but it won't really fit...
zed_oxff is on the ball.
To simplify things and keep them DRY, you might consider defining discrete named scopes instead of one big one, and chaining them together.
For example:
named_scope :random_order, :order => 'RAND()'
named_scope :limit, :lambda => { |limit| :limit => limit }
named_scope :whatever, ...
So you would use them as follows:
Person.random_order.limit(3).whatever

Rails find by *all* associated tags.id in

Say I have a model Taggable has_many tags, how may I find all taggables by their associated tag's taggable_id field?
Taggable.find(:all, :joins => :tags, :conditions => {:tags => {:taggable_id => [1,2,3]}})
results in this:
SELECT `taggables`.* FROM `taggables` INNER JOIN `tags` ON tags.taggable_id = taggables.id WHERE (`tag`.`taggable_id` IN (1,2,3))
The syntax is incredible but does not fit my needs in that the resulting sql returns any taggable that has any, some or all of the tags.
How can I find taggables with related tags of field taggable_id valued 1, 2 and 3?
Thanks for any advice. :)
UPDATE:
I have found a solution which I'll post for others, should they end up here. Also though for the attention of others whose suggestions for improvement I'd happily receive. :)
Taggable.find(:all, :joins => :tags, :select => "taggables.*, tags.count tag_count", :conditions => {:tags => {:taggable_id => array_of_tag_ids}}, :group => "taggables.id having tag_count = #{array_of_tag_ids.count}"))
Your question is a bit confusing ('tags' seemed to be used quite a bit :)), but I think you want the same thing I needed here:
Taggable.find([1,2,3], :include => :tags).tags.map { |t| t.taggables }
or (if you want the results to be unique, which you probably do):
Taggable.find([1,2,3], :include => :tags).tags.map { |t| t.taggables }.flatten.uniq
This gets at all the taggables that have the same tags as the taggables that have ids 1,2, and 3. Is that what you wanted?

named_scope in rails with a has_many association

I am trying to achieve what I think will be a fairly complex query using the magic of Rails without having lots of ugly looking SQL in the code.
Since my database is dealing with rather specialised biomedical models I'll translate the following to a more real world scenario.
I have a model Book that
has_many :chapters
and Chapter that
belongs_to :book
Say for instance Chapters have a name attribute and the names can be preface ,introduction and appendix, and a Book could have a Chapter named preface and a Chapter named introduction but no Chapter named appendix. In fact any combination of these
I am looking to find all Books that have both Chapters named preface and introduction.
At present I have a named_scope as follows
Book
named_scope :has_chapter?, lambda { |chapter_names|
condition_string_array = []
chapter_names.size.times{condition_string_array << "chapters.name = ?"}
condition_string = condition_string_array.join(" OR ")
{:joins => [:chapters] , :conditions => [condition_string, * chapter_names]}
}
If I call Book. has_chapter? ["preface", "introduction"] this will find me all books that have either a Chapter named preface or introduction. How could I do a similar thing that would find me isolates that both Chapters preface and introduction?
I am not that familiar with SQL so am not quite sure what kind of join would be needed and whether this could be achieved in a named scope.
Many thanks
Anthony
I am looking to find all Books that
have both Chapters named preface and
introduction.
I use mysql, not postgres, but I assume postgres has support for sub-queries? You could try something like:
class Book
named_scope :has_preface, :conditions => "books.id IN (SELECT book_id FROM chapters WHERE chapters.name = 'preface')"
named_scope :has_introduction, :conditions => "books.id IN (SELECT book_id FROM chapters WHERE chapters.name = 'introduction')"
end
and then:
Book.has_preface.has_introduction
I like the condensing of code to
condition_string = Array.new(chapter_names.size, "chapters.name=?").join(" AND ")
and this does indeed work for a join with "OR".However the AND join will not work since this means that chapters.name in a single row of a join has to be both 'preface' and 'introduction' for example.
An even neater way of doing my original "OR" join is
named_scope :has_chapter?, lambda { | chapter_names |
{:joins => [: chapters] , :conditions => { :chapters => {:name => chapter_names}}}
}
Thanks to Duplex on Rails Forum (Post link)
In order to achieve my original problem though DUPLEX suggested this
named_scope :has_chapters, lambda { |chapter_names|
{:joins => :chapters , :conditions => { :chapters => {:name => chapter_names}},
:select => "books.*, COUNT(chapters.id) AS c_count",
:group => "books.id", :having => "c_count = #{chapter_names.is_a?(Array) ? chapter_names.size : 1}" }
}
This may work in some flavour of SQL but not in PostgreSQL. I had to use the following
named_scope : has_chapter?, lambda { | chapter_names |
{:joins => :chapters , :conditions => { :chapters => {:name => chapter_names}},
:select => "books.* , COUNT(chapters.id)",
:group => Book.column_names.collect{|column_name| "books.#{column_name}"}.join(","), :having => "COUNT(chapters.id) = #{chapter_names.is_a?(Array) ? chapter_names.size : 1}" }
}
The code
Book.column_names.collect{|column_name| "books.#{column_name}"}.join(",")
is required because in PostgreSQL you can't select all columns with books.* and then GROUP BY books.* each column has to be named individually :(
I am looking to find all Books that
have both Chapters named preface and
introduction.
At present I have a named_scope as
follows
You can do this without a named_scope.
class Book < ActiveRecord::Base
has_many :chapters
def self.has_chapter?(chapter_names)
Chapter.find_all_by_name(chapter_names, :include => [:book]).map(&:book)
end
end

Resources