Elastic Search equivalent of "Scoping (Scalar Fields)" in sunspot/solr - solr

I'm exploring various options for a search engine for our rails-app/data. I was able to make sunspot/solr work and am currently exploring ElasticSearch as an alternative but couldn't get the same thing(scoping/filtering) to work under ES.
I would like to filter/scope objects as described in the "Scoping" section in 'https://github.com/sunspot/sunspot'.
I have an active record class 'Product'.
class Product < ActiveRecord::Base
...
include Tire::Model::Search
include Tire::Model::Callbacks
tire.mapping do
indexes :name, :type => :string
indexes :categories do
indexes :id, :type => :integer
end
end
end
Once I imported products into ES,
the following query works and gives results
Product.tire.search { query { string '*', default_operator: "AND", default_field: "name" } }.results
How do I get products of a particular category given a category id ?
Product.tire.search { query { string '*', default_operator: "AND", default_field: "name" }; filter(:term, 'categories.id' => 38) }.results
I'm basically looking for the tire equivalent for the following sunspot call:
Product.search do
with(:category_ids, 38)
end
with the following in the Product class
searchable :auto_index => true, :auto_remove => true do
text :name
integer :category_ids, :multiple => true do
self.categories.map &:id
end
end

Related

Mongoid query with where clause on an association of an embeded document

I have a Rails app using Mongoid with the classes User, ContractAgreement and ContractVersions.
The relationships are the following: User embeds ContractAgreement, and ContractAgreement belong to ContractVersions, like so:
User:
class User
include Mongoid::Document
include Mongoid::Stateful
include Mongoid::Timestamps
[...]
embeds_many :contract_agreements
ContractAgreement:
class ContractAgreement
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :user
belongs_to :contract_version
field :date_of_agreement, type: DateTime
ContractVersion
class ContractVersion
include Mongoid::Document
include Mongoid::Timestamps
field :version, type: String
field :description, type: String
field :status, type: String
has_one :post, autosave: true
has_one :post, autosave: true
#has_many :contract_agreements
As you can see, #has_many :contract_agreements is commented out as Mongoid didn't like the association to an embeded document.
Is there a way of getting the list of Users that have agreed to a specified contract?
I've tried both doing $elemMatch all the way:
[64] pry(main)> reload!;
u = User.all.select{|u| u.contract_agreements.count > 0}.first ;
cv = u.contract_agreements.first.contract_version ;
User.where(contract_agreements: {'$elemMatch' => {contract_version: {'$elemMatch' => {_id: cv._id}}}}).count
Reloading...
=> 0
As well as the compressed single query notation:
[64] pry(main)> reload!;
u = User.all.select{|u| u.contract_agreements.count > 0}.first ;
cv = u.contract_agreements.first.contract_version ;
User.where("contract_agreements.contract_version._id" => cv._id).count
Reloading...
=> 0
I know the contract version id I am asking for exists since I explicitly select a user that has one.
Is such a query even possible or do I need to de-embed ContractAgreement? If it is possible, what am I doing wrong?
Referring to the foreign key worked:
[73] pry(main)> reload!;
u = User.all.select{|u| u.contract_agreements.count > 0}.first ;
cv = u.contract_agreements.first.contract_version ;
User.where("contract_agreements.contract_version_id" => cv._id).count
Reloading...
=> 1

How to return an array of arrays that contains hashes

This is my method that is called by the test function:
def movies_with_directors_set(source)
director = []
director1 = []
hash = {}
while outer_index < source.length do
inner_index = 0
while inner_index < source[outer_index][:movies].length do
hash[:title] = []
hash[:title] = source[outer_index][:movies][inner_index][:title]
hash[:director_name] = []
hash[:director_name] = source[outer_index][:name]
director1 << hash.dup
inner_index +=1
end
director << director1.dup
outer_index += 1
end
return director
end
This is the test code:
describe 'movies_with_directors_set' do
describe 'when given a Hash with keys :name and :movies,' do
describe 'returns an Array of Hashes that represent movies' do
describe 'and each Hash has a :director_name key set with the value that was in :name' do
# This lets "sample_data" be used in the two "it" statements below
let (:test_data) {
[
{ :name => "Byron Poodle", :movies => [
{ :title => "At the park" },
{ :title => "On the couch" },
]
},
{ :name => "Nancy Drew", :movies => [
{ :title => "Biting" },
]
}
]
}
it 'correctly "distributes" Byron Poodle as :director_name of the first film' do
# { :name => "A", :movies => [{ :title => "Test" }] }
# becomes... [[{:title => "Test", :director_name => "A"}], ...[], ... []]
results = movies_with_directors_set(test_data)
expect(results.first.first[:director_name]).to eq("Byron Poodle"),
"The first element of the AoA should have 'Byron Poodle' as :director_name"
end
it 'correctly "distributes" Nancy Drew as :director_name of the last film' do
results = movies_with_directors_set(test_data)
expect(results.last.first[:director_name]).to eq("Nancy Drew"),
"The last element of the AoA should have 'Nancy Drew' as :director_name"
end
end
end
end
end
My method returns an array of hashes but for some reason it does not want to pass the test, as it tells me the second part of the test fails.
It could be that it requires arrays within an array due to the way the test is worded.
source is the database that gets passed into the method. This specific database is shown in the test code.
If you want to transform the structure from a nested director-name/titles into director-name/title pairs, there's a much easier way of going about that. Ruby's strength is in the Enumerable library which makes data transformation really fast, efficient, and easy to express. Here's an approach worth using:
def movies_with_directors(source)
# flat_map will join together the inner arrays into a single contiguous array
source.flat_map do |set|
set[:movies].map do |movie|
{
director_name: set[:name],
title: movie[:title]
}
end
end
end
This produces a very flat, easy to navigate structure like this:
# => [{:director_name=>"Byron Poodle", :title=>"At the park"}, {:director_name=>"Byron Poodle", :title=>"On the couch"}, {:director_name=>"Nancy Drew", :title=>"Biting"}]
Where you can iterate over that and assert more easily without having to do .first.first and such.
When using Ruby always try and think in terms of data transformation, not in terms of loops. There's a multitude of tools in the Enumerable library that can perform complicated operations with a single line of code. In your case you needed a combination of flat_map and map, and you're done.
Your test data and test expectations seem to be out of sync.
You have a :name key, and then expect :director_name to be present in your test hash. Change :director_name to be :name and it should pass:
{ :name => "Nancy Drew", :movies => [
and
expect(results.last.first[:director_name]).to eq("Nancy Drew")
So your failing test is probably saying something like expected nil to be "Nancy Drew", right?

Have foreign_key on mongoid

Well I'm have a PerformerSource and MonthlyEarning documents which has a field called performer_id
class PerformerSource
....
field :performer_id,:type => Integer
....
....
end
class MonthlyEarning
....
field :performer_id,:type => Integer
....
....
end
Now all I want to set a has_many relationships between the two documents with performer_id in mind i.e
performer_source has_many monthly_earnings
monthly_earning belongs_to performer_source
I think the following isn't allowed in Mongoid because apparently when I set the relationships
it just does not return anything
But if it does then please let me know
Apparently this work so the idea was to have relations through performer_id field present in both document so all that is need is
to set this
class PerformerSource
....
field :performer_id,:type => Integer
....
....
has_many :earnings ,:class_name => "MonthlyEarning",:primary_key => :performer_id,:foreign_key => :performer_id
end
class MonthlyEarning
....
field :performer_id,:type => Integer
....
....
belongs_to :performer,:class_name => "PerformerSource",:primary_key => :performer_id,:foreign_key => :performer_id
end
The way it works over here is the :primary_key i.e(performer_id) when firing the request through association
This is exactly what I want

How do you define an array field when using create in mongoid?

I'm trying to create a seed file to populate my mongo database. The following syntax creates a nil value when I try to define the array field value. I'm using mongoid v3.0.9. What am I doing wrong?
These following examples do not work when I put them in the seed file:
User.create(name:'name', test_array_field:'[123,123]')
User.create(name:'name', test_array_field:[123,123])
User.create(name:'name', test_array_field:[123,123].to_a)
I've defined the field in my class like so:
field :test_array_field, type: Array
Your second syntax works for me.
class User
field :roles, type: Array, default: []
end
u = User.create roles: ['superadmin']
u.new_record? # => false
u.roles # => ["superadmin"]
Try this:
class Foo
include Mongoid::Document
field :bar, :type => Array, :default => []
field :baz, :type => Hash, :default => {}
end

mongoid, embedy_many, simple_form

i m looking for a way to manage multiple embedded objects in a form.
found a solution for formtastic by bowsersenior
Formtastic with Mongoid embedded_in relations
but i wasnt able to do the same for simple_form
formtastic:
= semantic_form_for #team do |form|
= #team.players.each do |player|
= form.inputs :for => [:players, player] do |player_form|
= player_form.input :name
best regards
sample
class Team
include Mongoid::Document
field :name, :type => String
embeds_many :players
end
class Player
include Mongoid::Document
embedded_in :team, :inverse_of => :players
field :name, :type => String
field :active, :type=> Boolean # checkboxes
end
Not sure if this would work but you might want to try something like this:
= simple_form_for #team do |form|
= f.input :name
= f.simple_fields_for #team.players do |player_form|
= player_form.input :name
Just keep in mind that you will have to create a new player in the team before the form will show up.
In your controller(controller):
def new
#team = Team.new
8.times { #team.players.new } #for 8 players
end

Resources