Mongoid: select embedded objects that fits number of options - ruby-on-rails

I have got this structure
class House
include Mongoid::Document
embeds_many :inhabitants
end
class Inhabitant
include Mongoid::Document
embedded_in :house
field :name
field :gender
field :age
end
I can get all houses where females live:
houses = House.where("inhabitants.gender" => "female")
But how can I get all houses where females under age 50 live? How can I specify more than one condition for embedded object?

To apply multiple conditions to each entry in an array, you should use the $elemMatch operator. I'm not familiar with Mongoid, but here's the MongoDB shell syntax for your query modified to use $elemMatch:
> db.house.find({inhabitants: {$elemMatch: {gender: "female", age: {$lt: 50}}}})

Try this:
houses = House.where("inhabitants.gender" => "female", "inhabitants.age" => {"$lt" => 50})
Combining conditions:
MongoDB query:
db.houses.find({'inhabitants.age' : {$in: {$lt: 50}}})
Mongoid:
houses = House.where('inhabitants.age' => {'$in' => {'$lt' => 50}})

Related

Rails 5, thinking sphinx, indexing and searching has meny through relationships

I have a Rails App in which I want to use Thinking Sphinx for search. I have a has many though relationship between the following models, Product has many Types through ProductType.
# Product.rb
has_many :product_types
has_many :types, through: :product_types
# Type.rb
has_many :product_types
has_many :products, through: :product_types
# ProductType.rb
belongs_to :product
belongs_to :type
In my ProductsController index action I want to be able to filter which products are shown in the view based on given Variant ids.
My relevant indexes currently looks like this (note, I haven't used ThinkingSphinx in a long time):
# product_index.rb
ThinkingSphinx::Index.define :product, :with => :active_record do
indexes name, :sortable => true
indexes description
indexes brand.name, as: :brand, sortable: true
indexes product_types.type.id, as: :product_types
has created_at, updated_at
end
# type_index.rb
ThinkingSphinx::Index.define :type, :with => :active_record do
indexes name, :sortable => true
end
# product_type_index.rb
ThinkingSphinx::Index.define :product_type, :with => :active_record do
has product_id, type: :integer
has type_id, type: :integer
end
I currently pass an array of :product_types ids in a link_to, like this (let me know if there is a better way to do it):
= link_to "Web shop", products_path(product_types: Type.all.map(&:id), brand: Brand.all.map(&:id)), class: "nav-link"
In my ProductsController I try to filter the result based on the given Type ids like this:
product_types = params[:product_types]
#products = Product.search with_all: { product_types: product_types.collect(&:to_i) }
When I run rake ts:rebuild I get the following error:
indexing index 'product_type_core'...
ERROR: index 'product_type_core': No fields in schema - will not index
And when I tries to view the view in the browser I get the following error:
index product_core: no such filter attribute 'product_types'
- SELECT * FROM `product_core` WHERE `sphinx_deleted` = 0 AND
`product_types` = 1 AND `product_types` = 2 AND `product_types` = 3
LIMIT 0, 20; SHOW META
Any ideas in how to properly set up my indexes (and query) for this case?
There's a few issues to note here:
Firstly, the error you're seeing during rake ts:rebuild is pointing out that you've not set any fields in your ProductType Sphinx index - no indexes calls for text data you wish to search on. Are you actually searching on ProductType at all? If so, what text are you expecting people to match by?
If you're not searching on that model, there's no need to have a Sphinx index for it.
Secondly, the issue with your search - you're filtering on product_types with integers, which makes sense. However, in your index, you've defined product_types as a field (using indexes) rather than an attribute (using has). Given it's integer values and you're likely not expecting someone to type in an ID into a search input, you'll almost certainly want this to be an attribute instead - so change the indexes to a has for that line in your Product index definition, and run ts:rebuild.

Mongoid Search by Array of Association Id's

I have a Rails 4.2, Mongoid 4 project with these models:
class Customer #aka Company
include Mongoid::Document
has_many :branches
end
class Branch
include Mongoid::Document
field :name, type: String, default: ""
belongs_to :customer
end
I want to find all the Customers (aka Companies) that have a branch with name "New York". I would think that this code would work:
branches = Branch.where(name: "New York").map(&:_id)
=> [BSON::ObjectId('54f76cef6272790316390100')]
Customer.where(:branch_ids => branches).entries
However, it always returns an empty array, no matter what I try. In place of branch_ids, I've also tried branches, branch, branches_id, and others, but to no avail. I've also tried to convert the BSON::ObjectID to plain string, but that doesn't work either.
So, basically, how can I search a model based on an array of association ids? Thanks.
If the relations are
Customer has_many :branches and
Branch belongs_to :customer,
Then branches collection will have a customer_id column and not the reverse. So you can do
cust_ids = Branch.where(name: "New York").map(&:customer_id)
Customer.find(cust_ids)
Since you need only the customer ids from the first query, it is advisable to use pluck
cust_ids = Branch.where(name: "New York").pluck(:customer_id)
You can use Symbol#elem_match like this:
Customer.where(:branches.elem_match => { name: "New York" })
And Queryable#elem_match like this:
Customer.elem_match(branches: { name: "New York" })
Both query will give return you Customers of 'New York' branches.

How do you combine/merge two resultsets using mongoid?

How do I get a single resultset for a Student that also includes the classroom information - description and the name of the student? Is this possible?
Classroom has a 1-to-N relationship with Student in my current example
Classrooms.first currently returns
Classroom {
:_id => "5222da075d39f3e4e802000a",
:description => "Learn english",
:student_id => "50f9d5bc5d39f30ebb010004"
}
Classroom.first.student.first currently only returns:
Student {
:_id => "50f9d5bc5d39f30ebb010004",
:name => "Michael",
:classroom_id => "5222da075d39f3e4e802000a"
}
here is one solution:-
Classroom.first.attributes.merge(Classroom.first.student.first.attributes)
It will return a hash with both the model values

Ruby on Rails multiple belongs_to associations saving

There is something i don't quite understand in Rails's belongs_to concept. Documentation states:
Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object
Let's say i have an Employee entity:
class Employee < ActiveRecord::Base
belongs_to :department
belongs_to :city
belongs_to :pay_grade
end
Will the following code fire three updates and if so is there a better way to do it? :
e = Employee.create("John Smith")
Department.find(1) << e
City.find(42) << e
Pay_Grade.find("P-78") << e
You can simply assign it:
e = Employee.new(:name => "John Smith")
e.department = Department.find(1)
e.city = City.find(42)
e.pay_grade = Pay_Grade.where(:name => "P-78")
e.save
I changed the create to new to construct the object before saving it. The constructor takes a hash, not different values. find takes only the id and not a string, use where on a field instead.
You can also use the following:
Employee.create(:name => "John Smith",
:department => Department.find(1),
:city => City.find(42),
:pay_grade => PayGrade.where(:name => "P-78").first
Also note that model names should be camel case: PayGrade instead of Pay_Grade.

Indexing fields + custom text in with Thinking Sphinx

I've got indexes on a few different models, and sometimes the user might search for a value which exists in multiple models. Now, if the user is really only interested in data from one of the models I'd like the user to be able to pre/postfix the query with something to limit the scope.
For instance, if I only want to find a match in my Municipality model, I've set up an index in that model so that the user now can query "xyz municipality" (in quotes):
define_index do
indexes :name, :sortable => true
indexes "name || ' municipality' name", :as => :extended_name, :type => :string
end
This works just fine. Now I also have a Person model, with a relation to Municipality. I'd like, when searching only on the Person model, to have the same functionality available, so that I can say Person.search("xyz municipality") and get all people connected to that municipality. This is my current definition in the Person model:
has_many :municipalities, :through => :people_municipalities
define_index do
indexes [lastname, firstname], :as => :name, :sortable => true
indexes municipalities.name, :as => :municipality_name, :sortable => true
end
But is there any way I can create an index on this model, referencing municipalities, like the one I have on the Municipality model itself?
If you look at the generated SQL in the sql_query setting of config/development.sphinx.conf for source person_core_0, you'll see how municipalities.name is being concatenated together (I'd post an example, but it depends on your database - MySQL and PostgreSQL handle this completely differently).
I would recommend duplicating the field, and insert something like this (SQL is pseudo-code):
indexes "GROUP_CONCAT(' municipality ' + municipalities.name)",
:as => :extended_municipality_names
Also: there's not much point adding :sortable true to either this nor the original field from the association - are you going to sort by all of the municipality names concat'd together? I'm guessing not :)

Resources