using searchlogic with habtm associations - ruby-on-rails

I have the following issue:
I have two classes in my rails app, joined by a HABTM association, like so:
#ad.rb
has_and_belongs_to_many :specs
#spec.rb
has_and_belongs_to_many :ads
joined by an ads_specs table.
I'm trying to perform a search on the Ad class using the excellent searchlogic gem. Everything went fine until I wanted to return all the ads that have ALL the selected specs, not any of them.
I tried #ads = Ad.specs_id_like_all(id1, id2, id3)but to no results, since I think it's trying to match a spec with an id of "id1, id2, id3". I also tried to .split the ids or directly type them in an array but nothing worked.
My exact search query is:
if params[:search]
#ads = Ad.search(:price_lte => params[:search][:price],
:rulaj_lte => params[:search][:rulaj],
:fabrication_date_gte => Date.new(params[:search][:"an_fabr(1i)"].to_i,"01".to_i,"01".to_i)).specs_id_like_all(2, 457, 233)
else
#ads = Ad.all
end
Do you guys have any idea how could I solve this problem? I swear it's the last time I use HABTM associations in a rails app, but it's so late to change to a polymorphic one this late in the development process :).

If i understand correctly, you're trying to perform an SQL IN() query (i.e. spec_id IN (x,y,z..) )
Searchlogic does support passing an array to a *_eq() method which will use SQL's IN() predicate:
Ad.search(:spec_id_eq => [1,2,3,4])
**Not sure if this works in older versions of Searchlogic*

Related

Rails How do I find case insensitive values that are not already associated to the user?

Newbie Rails developer here so please bare with me.
I have a table called Ingredients where it contains a title field and an association to a User. A user can have many ingredients.
I want to query the database to get the ingredients that are not already available to a User.
I tried doing something like this with Rails:
#ingredients = current_user.ingredients
#community_ingredients = Ingredient.all.excluding(#ingredients).pluck(:title, :id)
But the problem is that this still returns values that are the same & only the case is different.
How can I achieve this outcome?
Try following queries.
#community_ingredients = Ingredient.includes(:user).where("users.user_id = ?", current_user.id).where(users: { id: nil } ).pluck(:title, :id)
OR
Ingredient.includes(:user).where("users.user_id = ?", current_user.id).where(ingredients: {user_id: nil } ).pluck(:title, :id)
OR
Ingredient.includes(:user).where("users.user_id = ?", current_user.id).where(users: { ingredient_id: nil } ).pluck(:title, :id)
Choose right query based on your association and feel free to suggest me so I can remove the extra one.
Most probably the first or second query will work, I strongly feel the third might not be the case.
Let's say this one is not working for you and you want to have solution based on your architecture.
#ingredients = current_user.ingredients.pluck(:title)
#community_ingredients = Ingredient.where.not("lower(title) IN (?)", #ingredients.map(&:downcase)).pluck(:title, :id)
So basically we need to convert both column value and the matching list in same case.
So we have converted to downcase.
here is how it looks in my local system, just make sure it's working that way.

Datamapper: Sorting results through association

I'm working on a Rails 3.2 app that uses Datamapper as its ORM. I'm looking for a way to sort a result set by an attribute of the associated model. Specifically I have the following models:
class Vehicle
include DataMapper::Resource
belongs_to :user
end
class User
include DataMapper::Resource
has n, :vehicles
end
Now I want to be able to query the vehicles and sort them by the name of the driver. I tried the following but neither seems to work with Datamapper:
> Vehicle.all( :order => 'users.name' )
ArgumentError: +options[:order]+ entry "users.name" does not map to a property in Vehicle
> Vehicle.all( :order => { :users => 'name' } )
ArgumentError: +options[:order]+ entry [:users, "name"] of an unsupported object Array
Right now I'm using Ruby to sort the result set post-query but obviously that's not helping performance any, also it stops me from further chaining on other scopes.
I spent some more time digging around and finally turned up an old blog which has a solution to this problem. It involves manually building the ordering query in DataMapper.
From: http://rhnh.net/2010/12/01/ordering-by-a-field-in-a-join-model-with-datamapper
def self.ordered_by_vehicle_name direction = :asc
order = DataMapper::Query::Direction.new(vehicle.name, direction)
query = all.query
query.instance_variable_set("#order", [order])
query.instance_variable_set("#links", [relationships['vehicle'].inverse])
all(query)
end
This will let you order by association and still chain on other scopes, e.g.:
User.ordered_by_vehicle_name(:desc).all( :name => 'foo' )
It's a bit hacky but it does what I wanted it to do at least ;)
Note: I'm not familiar with DataMapper and my answer might not be within the standards and recommendations of using DataMapper, but it should hopefully give you the result you're looking for.
I've been looking through various Google searches and the DataMapper documentation and I haven't found a way to "order by assocation attribute". The only solution I have thought of is "raw" SQL.
The query would look like this.
SELECT vehicles.* FROM vehicles
LEFT JOIN users ON vehicles.user_id = users.id
ORDER BY users.name
Unfortunately, from my understanding, when you directly query the database you won't get the Vehicle object, but the data from the database.
From the documentation: http://datamapper.org/docs/find.html. It's near the bottom titled "Talking directly to your data-store"
Note that this will not return Zoo objects, rather the raw data straight from the database
Vehicle.joins(:user).order('users.name').all
or in Rails 2.3,
Vehicle.all(:joins => "inner join users on vehicles.user_id = user.id", :order => 'users.name')

rails mongoid criteria find by association

I'm trying to find a record by associated username which is included in a belongs_to relation, but it's not working.
Articles belong to Users
Users have many articles
Article.where(user_id: someid) works fine, but I'd like to use the username as reference which is stored in the Users table.
Article.includes(:user).where(:username => "erebus")
Article.includes(:user).where("user.username" => "erebus")
I also have identity_map_enabled: true
Article.includes(:user).inclusions returns the relation details
Doesn't work, what am I not understanding?
You have to keep in mind that there are no joins in mongodb. In relational dbs, includes forms a join query and you can use columns from both the tables in query. However due to absence of joins in mongodb, same is not possible.
In mongoid, includes just saves a bunch of db calls. It fetches and stores the associated records in identity map for fast retrieval, but still while querying, one query can only deal with one collection.
If you need articles based on user names, I would suggest following work around:
user_ids = User.where(username: 'erebus').only(:_id).map(&:_id)
articles = Article.where(:user_id.in => user_ids)
You can make it little shorter from what rubish suggested:
user_ids = User.where(username: 'erebus').pluck(:id)
articles = Article.where(:user_id.in => user_ids)
Or one liner:
articles = Article.where(:user_id.in => User.where(username: 'erebus').pluck(:id))

Best Rails 3.1 practice to complex query

Given:
(models):
Contract (has_many :invoices, belongs_to :user)
Invoice (belongs_to :contract)
This way, for example:
my_contracts = Contract.where(user_id: current_user.id) #=> [#<Contract id: 1, title: "1">, #<Contract id: 2, title: "2">]
In this case we have two Contracts for User. And each of Contracts have multiple number of Invoices.
Now we need to gather all Invoices for each of contracts and sort them by 'updated_at'.
Something like:
all_invoices = my_contracts.map{|i| i.invoices.sort_by(&:updated_at)}
but using ActiveRecord.
How it could be done right?
The way you are doing it is not bad, just use includes to get eager loading of the invoices instead of lazy (n+1) loading
contracts = Contract.where(:user_id => current_user.id).includes(:invoices)
# or this might do the same => current_user.contracts.includes(:invoices)
invoices = contracts.map{|i| i.invoices }
invoices.sort_by(&:updated_at).each do |invoice|
# ....
end
try this and also what David Sulc posted, view the generated sql and experiment with the result in rails console; using joins vs includes has very different behavior, depending on situation one maybe better than the other
see also
http://guides.rubyonrails.org/active_record_querying.html#joining-tables
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
It should be something like
Invoice.joins(:contracts).where(user_id: current_user.id).order(:updated_at)
Arel isn't a strong suit of mine (still need to work on it), but if this doesn't work, it should at least get you closer to the answer.

Rails 3 refuses to eager load

I'm working in Rails 3.0.7.
I have some many-to-many and some one-to-many associations that fail to eager load.
My associations are:
Person has_many :friends
Person has_many :locations through=> :location_histories
Location belongs_to :location_hour
Location_hour has_many :locations
In my controller I have the following:
#people = Person.includes([[:locations=>:location_hour],:friends]).where("(location_histories.current = true) AND (people.name LIKE ? OR friends.first_name LIKE ? OR friends.last_name LIKE ? OR (friends.first_name LIKE ? AND friends.last_name LIKE ?))").limit(10).all
Then in my view I have:
<% #people.each do |person| %>
<tr>
<td><%= link_to person.name, person %></td>
<td><%= link_to person.friends.collect {|s| [s.full_name]}.join(", "), person.friends.first %></td>
<td><%= link_to person.locations.current.first.name, person.locations.current.first %></td>
</tr>
<% end %>
locations.current is a scope defined as:
scope :current, lambda {
where("location_histories.current = ?", true)
}
This works as expected and first generates 2 database calls: one to get a list of person ids and then a big database call where everything is properly joined. THE PROBLEM is that after that there are n database calls along the lines of:
SELECT 'friends'.* from 'friends' WHERE ('friends'.person_id = 12345)
So for each iteration of the loop in the view. Needless to say this takes a while.
I thought that .all would force eager loading. Anyone have an idea what's going on here?
This spends over 3 seconds on ActiveRecord. Way too long.
I would greatly appreciate any and all suggestions.
Thanks.
OK. Finally Solved.
I needed to call both joins and includes. I've also had to remove the :locations_current association from the join. It was creating some chaos by attempting to
... LEFT OUTER JOIN `locations` ON `location_histories`.current = 1 ...
Which of course is not a valid association. It seems that the 'current' clause was being carried over into the JOINS.
So now I have the following, which works.
#people = Person.joins([[:locations=>:location_hour],:friends]).includes([[:locations=>:location_hour],:friends]).where("(location_histories.current = true) AND (people.name LIKE ? OR friends.first_name LIKE ? OR friends.last_name LIKE ? OR (friends.first_name LIKE ? AND friends.last_name LIKE ?))")
IN SUMMARY:
1) I needed to use both Joins and Includes for eager loading and proper interpretation of the .while() conditions
2) I needed to keep associations with conditions (i.e. :current_locations) out of the joins clause.
Please correct me if this seems like a glaring mistake to you. It seems to work though. This brings down the Active Record time to just under 1 sec.
Is it common to combine joins and includes?
Thanks!
I've figured out PART of the problem (though there is still some unintended behavior).
I had several scopes such as locations.current, defined as indicated above.
I have moved this logic to the association. So in my Person model I now have
has_many :current_locations, :source => :location, :through => :location_histories, :conditions => ["`location_histories`.current = ?", true]
And I call
Person.current_locations.first
instead of
Person.locations.current.first.
So now the includes do eager load as expected.
The problem is that this messed up the search. For some reason now everything seems to hang when I include a where clause. Things first get dramatically slower with each table that I add to the include and by the time I've included all the necessary tables it just hangs. No errors.
I do know this: when I add symbols to the where clause Rails does an outer join during the query (as explained here), which is what's expected. But why does this cause the whole thing to collapse?
(A minor problem here is that I need string comparisons. In order to get a proper join I call .where as
.where(:table =>{:column=> 'string'})
which is equivalent to
table.column = 'string'
in SQL but I need
table.column LIKE '%string%'
Oddly, for me, I get the following behavior:
# fails to eager load tags--it only loads one of the tags for me, instead of all of them.
Product.find(:all, :conditions => ["products_tags.tag_id IN (?)", 2], :include => [:tags])
but this succeeds
Product.find(:all, :conditions => ["products_tags.tag_id IN (?)", 2], :include => [:tags], :joins => [:tags])
It's like the query on the inner join table is messing up the eager loading somehow. So your answer may be right. But there may be something odd going on here. (Rails 2.3.8 here).

Resources