Rails ActiveRecord find through has_many - ruby-on-rails

iOS developer learning Rails here. Trying to query active record for records based on a has_many relation's property. Apologies if this is simple but I just can't figure it out. I've read about and have been trying to use scope, .where, .joins, but there are so many contradicting posts and blogs online I'm unsure which to use and what's correct...
On to the problem:
I have two ActiveRecord models:
class User < ActiveRecord::Base
has_many :items
end
and
class Item < ActiveRecord::Base
belongs_to :user
end
An item has a property title, I am trying to find all of the Users that have an item with a title that is similar to some search parameter in string format.
I have managed to do such using a search for items and then .map like this:
users_owning_item_in_search_parameter = Item.where{ (title =~ my{#search_param + "%"}) }.map! { |i| i.user }
(that syntax comes from the squeel gem.)
But that command returns an Array when I want an ActiveRecord::Relation, because I need to do some further filtering that requires this instance type.
Any help much appreciated.

I think you're looking for something like this:
User.joins(:items).where('items.title LIKE ?', "#{#search_param}%")
You'll have to modify it a bit if you want to take advantage of squeel.

Related

Rails - get polymorphic children from collection as ActiveRecord::Relation

I need to get all children from a parent as an ActiveRecord::Relation. Thing is, this children are stored in a polymorphic relation. In my case I need it to paginate some search results obtained with pg_search gem.
I've tried the following:
results = PgSearch.multisearch('query').map(&:searchable)
# Horrible solution, N + 1 and returns an array
docs = PgSearch.multisearch('query').includes(:searchable)
results = docs.map(&:searchable)
# Still getting an array
Also thought of things like select or pluck, but they are not intended for retrieving objects, only column data. I could try to search ids for each children type like so
Post.where(id: PgSearch.multisearch('query').where(searchable_type: "Post").select(:searchable_id)
Profile.where(id: PgSearch.multisearch('query').where(searchable_type: "Profile").select(:searchable_id)
But it doesn't scale, since I would need to do this for every object I want to obtain from a search result.
Any help would be appreciated.
EDIT
Here's some basic pseudocode demonstrating the issue:
class Profile < ApplicationRecord
has_one :search_document, :as => :searchable
end
class Post < ApplicationRecord
has_one :search_document, :as => :searchable
end
class Profile < ApplicationRecord
has_one :search_document, :as => :searchable
end
class SearchDocument < ApplicationRecord
belongs_to :searchable, plymorphic: true
end
I want to obtain all the searchable items as an ActiveRecord::Relation, so that I can dynamically filter them, in this specific case, using limit(x).offset(y)
SearchDocument.all.joins(:searchable).limit(10).offset(10)
Generates an error: cannot eagerly load searchable cause of polymorphic relation
SearchDocument.all.includes(:searchable).limit(10).offset(10)
This one does load the searchable items into memory, but does not return them in the query, instead it applies the filters to the SearchDocument items, as expected. This might be a temporary solution, to filter the search documents and then get the searchable items from them, but collides with pagination on the views.
The question here is: Is there a way I can get all searchable items as ActiveRecord::Relation to further filter them?
I'm unfamiliar with this library. However, looking at your code, I'd guess that any attempt to take a CollectionProxy and map some function over it will trigger evaluating the CollectionProxy and return an array.
Having had a quick look at the library GitHub docs, perhaps something like this might work:
post_docs = PgSearch.multisearch('query').where(searchable_type: "Post")
posts = Post.where(pg_search_document: post_docs)

ActiveRecord includes reverse direction

So I have a query that looks like this:
merge_groups_with_mems_and_fps = PersonMergeGroup.joins(:members)
.where('merge_group_members.member_id IN (?)', member_ids)
.includes(:members, :field_preferences)
And the class of the association:
class FieldPreference < ActiveRecord::Base
belongs_to :merge_group
end
Now, when I say something like
merge_groups_with_mems_and_fps.first.field_preferences[0].merge_group
The result is a new query.
What's a good way to make sure when I call on a field_preference, and want its merge_group, I only look in merge_groups_with_mems_and_fps and don't make a new query? Thank you very much!
I think you need to hint to Rails about this association:
class FieldPreference < ActiveRecord::Base
belongs_to :merge_group, inverse_of: :field_preference
end
Check out the docs. Also, related SO question.
You're trying to call the merge_group association without including it in your original query. Unless you add it to the include section, you'll generate a new query every time you reference merge_group on any element in the field_preferences collection. You probably want something that looks like this:
merge_groups_with_mems_and_fps = PersonMergeGroup.joins(:members)
.where(merge_group_members: member_ids)
.includes(:members, field_preferences: { :merge_group })
PS: ActiveRecord will generate IN queries for you, so you can specify those conditions as hash parameters for your where query.

rails, how to combine two ActiveRecord query results

I have following association
class Location < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :location
end
Suppose I have some instances of Location, I what to query all items belong to those locations. Currently I managed to get the result as an array
items =[]
Location.near(latitude,longitude,distance).find_each do |location|
location.items.find_each do |item|
items << item
end
end
However, is there any way I can get the results as ActiveRecord::Relation. Because I want to further query the results by using "where" with ActiveRecord::Relation.
P.S. The "near" method is from geocoder gem, it returns a ActiveRecord::Relation.
---------------------Edit----------------------------
Thank you for replies I nearly find the solution
locations = Location.near(latitude,longitude,distance)
Item.where(location_id: locations.pluck(:id))
Is it the right way to do it? to me it is a bit unintuitive.
----------------------Edit again ---------------------------
Just a small comment: I say it is unintuitive because I am switching from DataMapper. If it is Datamapper, it would be quite simple, like
Location.near(blabla).items
It is very simply to make queries through associations. Compared to Datamapper, can not understand why ActiveRecord association is so useless?
Edit to use one query with mapping...
What billy said above, but another option that might be faster:
locations = Location.near(1, 2, 3)
items = Item.where(:location_id => locations.map(&:ids)

Finding nil has_one associations in where query

This may be a simple question, but I seem to be pulling my hair out to find an elegant solution here. I have two ActiveRecord model classes, with a has_one and belongs_to association between them:
class Item < ActiveRecord::Base
has_one :purchase
end
class Purchase < ActiveRecord::Base
belongs_to :item
end
I'm looking for an elegant way to find all Item objects, that have no purchase object associated with them, ideally without resorting to having a boolean is_purchased or similar attribute on the Item.
Right now I have:
purchases = Purchase.all
Item.where('id not in (?)', purchases.map(&:item_id))
Which works, but seems inefficient to me, as it's performing two queries (and purchases could be a massive record set).
Running Rails 3.1.0
It's quite common task, SQL OUTER JOIN usually works fine for it. Take a look here, for example.
In you case try to use something like
not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null")
Found two other railsey ways of doing this:
Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL")
Item.includes(:purchase).where(purchases: { id: nil })
Technically the first example works without the 'references' clause but Rails 4 spits deprecation warnings without it.
A more concise version of #dimuch solution is to use the left_outer_joins method introduced in Rails 5:
Item.left_outer_joins(:purchase).where(purchases: {id: nil})
Note that in the left_outer_joins call :purchase is singular (it is the name of the method created by the has_one declaration), and in the where clause :purchases is plural (here it is the name of the table that the id field belongs to.)
Rails 6.1 has added a query method called missing in the ActiveRecord::QueryMethods::WhereChain class.
It returns a new relation with a left outer join and where clause between the parent and child models to identify missing relations.
Example:
Item.where.missing(:purchase)

Model associations

I have two models Library and Book. In my Library model, I have an array - book_ids. The primary key of Book model is ID.
How do I create a has_many :books relation in my library model?
This is a legacy database we are using with rails.
Thanks.
Your database schema doesn't really conform with the prescribed Rails conventions so you will probably have a hard time making the default has_many association work. Have you tried fiddling with the custom SQL options with it thought?
If you can't get the built in has_many association to work, you'll have to roll your own. I would define the books and books= methods on your Library model, and inside them set a virtual attribute, which you then save as an array in the database. Perhaps something like this:
class Book > ActiveRecord::Base; end
class Library > ActiveRecord::Base
before_save :serialize_books
def books
#books || nil
end
def books=(new_books)
#books = new_books
end
private
def serialize_books
#attributes['books'] = "[" + #books.collect {|b| b.id }.join(',') + "]"
end
end
That up there wouldn't pull out the dataIf you wanted to go even more gung ho and support single query find operations, you could use some custom SQL in a scope or override find and add it to the default options. Comment if you want help with any of this!
If you want to use has_many you could use the options :counter_sql and :finder_sql using the MySQL LIKE or REGEX syntax. But its probably better to first load the Libary model, then parse the book_ids column and load the books, or directly build a query with that string.
Consider using :serialize method with ActiveRecord:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002284
it might do what you want

Resources