I have a two tables joined with a join table - this is just pseudo code:
Library
Book
LibraryBooks
What I need to do is if i have the id of a library, i want to get all the libraries that all the books that this library has are in.
So if i have Library 1, and Library 1 has books A and B in them, and books A and B are in Libraries 1, 2, and 3, is there an elegant (one line) way todo this in rails?
I was thinking:
l = Library.find(1)
allLibraries = l.books.libraries
But that doesn't seem to work. Suggestions?
l = Library.find(:all, :include => :books)
l.books.map { |b| b.library_ids }.flatten.uniq
Note that map(&:library_ids) is slower than map { |b| b.library_ids } in Ruby 1.8.6, and faster in 1.9.0.
I should also mention that if you used :joins instead of include there, it would find the library and related books all in the same query speeding up the database time. :joins will only work however if a library has books.
Perhaps:
l.books.map {|b| b.libraries}
or
l.books.map {|b| b.libraries}.flatten.uniq
if you want it all in a flat array.
Of course, you should really define this as a method on Library, so as to uphold the noble cause of encapsulation.
If you want a one-dimensional array of libraries returned, with duplicates removed.
l.books.map{|b| b.libraries}.flatten.uniq
One problem with
l.books.map{|b| b.libraries}.flatten.uniq
is that it will generate one SQL call for each book in l. A better approach (assuming I understand your schema) might be:
LibraryBook.find(:all, :conditions => ['book_id IN (?)', l.book_ids]).map(&:library_id).uniq
Related
I have a complex DB schema in my Rails 2.3 app. At some point, I need to make a query to retrieve data from several columns by joining multiple tables. There is a table where I always need to go through to retrieve the data I need.
The problem comes when I try to use Rails syntax to make the joins. I found no way to make rails skip joining the same table again and again.
Let's say I have this table relationships:
A=>B=>C=>D=>E=>F
A=>B=>F=>G
A=>B=>F=>H
A=>I
As you can see, B is a table that I "join" several times to get to the tables I need.
My query looks something like this:
A.all( :select=>"SOME DATA FROM F, G, H AND I",
:joins=>[{{{{:B=>:C}=>:D}=>:E}=>:F}, {{:B=>:F}=>:G}, {{:B=>:F}=>:H}, :I],
:conditions=>{"SOME CONDITIONS"
}
)
You can see that I use the hash syntax to specify the joins.
The problem is that when I look at the join that rails create, I see that it joins B 3 times. I would expect that it would be smart enough to do it just just once and go from there but I guess that there maybe cases where you want to join several times.
My question is, how can I, using rails 2.3.8 syntax, make so that the resulting query just joins B once. Adding extra relationships in the model of type :belongs_to :throug is not an option for me
try
A.all(:joins => [:I, { :B => [{ :F => [:G, :H] }, {:C => { :D => { :E => :F }}}]}])
I'm wondering if there's a way to fetch objects from the DB via ActiveRecord, without having Rails build the whole objects (just a few fields).
For example,
I sometimes need to check whether a certain object contains a certain field.
Let's say I have a Student object referencing a Bag object (each student has one bag).
I need to check if a female student exists that her bag has more than 4 pencils.
In ActiveRecord, I would have to do something like this:
exists = Student.female.find(:all, conditions => 'bags.pencil_count > 4', :include => :bag).size > 0
The problem is that if there are a 1000 students complying with this condition,
a 1000 objects would be built by AR including their 1000 bags.
This reduces me to using plain SQL for this query (for performance reasons), which breaks the AR.
I won't be using the named scopes, and I would have to remember to update them all around the code,
if something in the named scope changes.
This is an example, but there are many more cases that for performance reasons,
I would have to use SQL instead of letting AR build many objects,
and this breaks encapsulation.
Is there any way to tell AR not to build the objects, or just build a certain field (also in associations)?
If you're only testing for the existence of a matching record, just use Model.count from ActiveRecord::Calculations, e.g.:
exists = Student.female.count( :conditions => 'bags.pencil_count > 4',
:joins => :bag
) > 0
count simply (as the name of the class implies), does the calculation and doesn't build any objects.
(And for future reference it's good to know the difference between :include and :joins. The former eager-loads the associated model, whereas the latter does not, but still lets you use those fields in your :conditions.)
Jordan gave the best answer here - especially re: using joins instead of include (because join won't actually create the bag objects)
I'll just add to it by saying that if you do actually still need the "Student" objects (just with the small amount of info on it) you can also use the :select keyword - which works just like in mysql and means the db I/O will be reduced to just the info you put in the select - and you can also add derived fields form the other tables eg:
students = Student.female.all(
:select => 'students.id, students.name, bags.pencil_count AS pencil_count',
:conditions => 'students.gender = 'F' AND bags.pencil_count > 4',
:joins => :bag
)
students.each do |student|
p "#{student.name} has #{student.pencil_count} pencils in her bag"
end
would give eg:
Jenny has 5 pencils in her bag
Samantha has 14 pencils in her bag
Jill has 8 pencils in her bag
(though note that a derived field (eg pencil_count) will be a string - you may need to cast eg with student.pencil_count.to_i )
I know this removes duplicates :
#email.distributions.map(&:zip_code) & CardSignup.all.map(&:zip_code)
But I want to do the same thing, where I find anything that matches, but it also shows me duplicates.
Any ideas?
I am trying to find the amount of people who signed up for a card that have a matching zip code to a zip code preference I placed.
Array#reject to the rescue, again! Like Array#map, it accepts blocks, allowing you to do something like this:
zip_codes = CardSignup.all.map(&:zip_code)
#email.distributions.reject{|o| !zip_codes.include?(o.zip_code)}
Oh, but of course, if you like finding more elegant ways, always consider the operators like you already did. & will return a new array with objects that are in both, | will join and remove duplicates.
ruby-1.9.2-p0 > [1,2] | [2,3]
=> [1, 2, 3]
ruby-1.9.2-p0 > [1,2] & [2,3]
=> [2]
Edit: as Tokland said in the comments, since this is applied on a Rails Model, you may want to consider doing it as a select. Like this -
zip_codes = CardSignup.all.map(&:zip_code)
#email.distributions.where('zip_code IN (?)', zip_codes)
Or, do it with an INNER JOIN. Doesn't look as pretty though.
#email.distributions.joins('INNER JOIN card_signups ON card_signups.zip_code = email_distributions.zip_code').all
(If the table for #email.distributions is email_distributions..)
I'm having trouble crafting a fairly simple query with Doctrine...
I have two arrays ($countries, $cities) and I need to check whether database record values would match any inside either. I'm looking for something like:
->whereIn('country', 'city', $countries, $cities)
... with 'country' being a WHERE IN for $countries and 'city' being a WHERE IN for $city.
I could separate the two out but the needed query has lots of other conditions so that's not possible. The resulting SQL I'm after would be:
SELECT ...
WHERE ...
AND ...
AND ...
AND ('country' IN (1,2,3) OR 'city' IN (7,8,9))
AND ...
AND ...;
One could therefore think of it also as a bracketing issue only. Anyone know if this is possible with Doctrine DQL? I've looked through the documentation but can't find any direction.
Thanks
After an hour of experimenting on this nonsense, here's the syntax to make it work.
$q->andWhere('country IN ? OR city IN ?', array(array(1, 2, 3), array(7, 8, 9)));
Why not use something like?
$countryIds=[1,2,3];
$cityIds=[7,8,9];
$q->whereIn('country',$countryIds)->andWhereIn('city',$cityIds);
Also, chain them together for context (most Doctrine methods return $this).
see http://www.symfony-project.org/doctrine/1_2/en/06-Working-With-Data
So I have two separate queries:
tagged_items = Item.tagged_with(params[:s], :on => :tags)
searched_items = Item.find(:all, :conditions => ["MATCH(title) AGAINST (? IN BOOLEAN MODE)", "*#{params[:s]}*"])
The first tagged_items is using the acts_as_taggable_on plugin to find all the items tagged with XYZ.
The second, searched_items, is used to search the items table for the search term.
So, how could I combine (and avoid duplicates) the results of these two?
Check out named_scope. The second query can be converted to named_scope easily, I'm not sure about the first one, but if you can rewrite it using find, you're home.
http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html
items = (tagged_items + searched_items).unique
But it would be much better if you could fetch them with single query.
This approach...
#items = tagged_items | searched_items
...would make more sense if you're looking to use the results of these queries in a View, instead of working with an Array, and accomplishes the de-duplication as well.