Tire (Elastic Search) Nil Comparison - ruby-on-rails

For Tire (ElasticSearch wrapper gem), how do you query and filter out indexed records that has nil/null value for a certain attribute. For example, if I have this relationship
class Article < ActiveRecord::Base
belongs_to :topic
end
I have article indexed but I want to avoid pulling back records with topic_id = nil. I tried this code blow but it didn't work.
class Article
belongs_to :topic
def search(q)
tire.search do
...
filter :missing, :field => :topic_id, { :existence => false, :null_value => false }
...
### filter :range, :topic_id => { :gt => 0 } # ==> I tried this as well but didn't work
...
end
end
end

I think you should use exists filter if you want to filter only documents with existing topic_id value.
class Article
belongs_to :topic
def search(q)
tire.search do
...
filter :exists, :field => :topic_id
...
end
end
end
Or you can use query string with _exists_:topic_id query.

Please try:
class Article
belongs_to :topic
def search(q)
tire.search do
...
filter :not, :missing => {:field => :topic_id, { :null_value => true } }
...
end
end
end
This should work. existence is required if you want to check if the field exists or not. I guess you just need the null_value check.

#articles = Article.where('topic_id is not ?' , nil)

Related

Ordering elasticsearch results, elasticsearch-rails gem, Rails

Im started to use Elasticsearh in my project, and have problem with result ordering.
In fact I need to sort my records by hstore record in connected (belongs_to) model.
More details:
So, I have a Model that I want to be searchable. This model have connections with another models, here the code:
class PropertyObject < ActiveRecord::Base
belongs_to :country, :counter_cache => true
belongs_to :region, :counter_cache => true
belongs_to :city, :counter_cache => true
belongs_to :property_object_type, :counter_cache => true
belongs_to :property_object_state, :counter_cache => true
has_one :property_object_parameter_pack, dependent: :destroy
has_one :property_object_feature_pack, dependent: :destroy
has_one :property_object_description, dependent: :destroy
has_one :property_object_seo_field, dependent: :destroy
end
I want to include to my search results next fields:
Model PropertyObject:
:code :string
Model Country
:title_translations :hstore
Model Region
:title_translations :hstore
Model City
:title_translations :hstore
Model PropertyObjectDescription
:title_translations :hstore
:main_text_translations :hstore
Model PropertyObjectParameterPack
:price :hstore (example: {min => 10, max=>100})
To make this work I had create concern Searchable and add it to my model PropertyObject.
Here the code of it:
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
mapping do
indexes :property_object_parameter_pack, type: 'nested' do
indexes :price do
indexes :min, type: :integer
end
end
end
# Customize the JSON serialization for Elasticsearch
def as_indexed_json(options={})
self.as_json(
include: {
country: {only: :title_translations},
region: {only: :title_translations},
city: {only: :title_translations},
property_object_description: {only: [:title_translations, :main_text_translations]},
property_object_parameter_pack: {only: [:price, :area, :rooms]}
})
end
end
end
Controller part where search is calling
def search
pagen = params[:page] || 1
#property_objects = PropertyObject.search(params[:q]).page(pagen).records
end
So now searching working and all seems good. But I need sort results of search by min price.
I had try order method that works in my another orders - but no luck.
As I understand I need to use Elasticsearch sorting , to get result already sorted - but spend a lot of hours trying to implement this and fail.
What you can suggest me?
UPDATE
Had try this code:
pagen = params[:page] || 1
query = params[:q]
params[:order] ||= 'asc'
property_objects = PropertyObject.search(query) do |s|
s.query do |q|
q.string query
end
s.sort { by :property_object_parameter_pack.price.min, params[:sort]}
end
#property_objects = property_objects.page(pagen).records
With different variants
s.sort by
by :price
by :price.min
by :price[:min]
by :property_object_parameter_pack.price.min
by :property_object_parameter_pack.price[:min]
and no luck with ordering.
In the end I decide to understand how Elasticsearch works, and start to read Elasticsearch: The Definitive Guide - where the answer was founded.
First off all I recommend to read this guide and install Marvel
. After this all becomes much more clearer then using CURL. In fact I discover my index structure with Marvel, and just implement it to search query of elasticsearch-rails gem.
Next thing that I did - I had rebuild price from hstore to separate integer columns : like price_min and price_max.
So in short the answer code is:
sq = {
"query": {
"multi_match": {
"query": "Prague",
"fields": [ "country.title_translations.en",
"region.title_translations.en",
"city.title_translations.en",
"property_object_description.main_text_translations.en",
"property_object_description.title_translations.en"
]
}
},
"track_scores": true,
"sort": {
"property_object_parameter_pack.price_min":
{ "order": "desc",
"missing" : "_last"
}
}} PropertyObject.search (sq)
In fact Im sure that it will work with hstore. Because I store translations in hstore and it indexing fine - just need to point right field (in this task Marvel helps with autocomplete).
Did you try this?
def search
options = { :page => (params[:page] || 1) }
#property_objects = PropertyObject.search, options do |f|
f.query { string params[:q] }
f.sort { by :price, 'desc' }
end
#property_objects.records
end

Using sunspot_solr search array of values

Hi, I built a Ruby on Rails application with Sunspot Solr for searching.
#search = Answer.search do
with(:question_id, #question_ids)
paginate :page => 1, :per_page => Answer.count
end
return question_id
Here i want to search this Answer model using array of question_ids (ex: [1,2,3,4,5]).
How to do that? Kindly help me.
if your question and answer has association like
class Question < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
end
then you can add searchable to your questions model like this
class Question < ActiveRecord::Base
searchable do
text :title, :body
text :answers do
answers.map { |answer| answer.body }
end
integer :questions_ids, :multiple => true
end
// your another methods
end
And in your index action
#search = Answer.search do
with(:questions_ids, #question_ids)
paginate :page => 1, :per_page => Answer.count
end
return question_id
I think it will help you.

In Rails, what is the inverse of update_attributes?

In Rails, what is the inverse of update_attributes! ?
In other words, what maps a record to an attributes hash that would recreate that record and all of it children records?
The answer is not ActiveRecord.attributes as that will not recurse into child object.
To clarify if you have the following:
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
end
Then you can pass an hash like
{"name" => "a foo", "bars_attributes" => [{"name" => "a bar} ...]}
to update_attributes. But it's not clear how to easily generate such a hash programatically for this purpose.
EDIT:
As I have mentioned in a comment, I can do something like:
foo.as_json(:include => :bars)
but I wanted a solution that uses the accepts_nested_attributes_for :bars declaration to avoid having to explicitly include associations.
Not sure how that would be the "inverse", but while Rails might not "have the answer" per-see, there is nothing stopping you from traversing through an object and creating this VERY efficiently.
Something to get you started:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
You'll notice in the accepts_nested_attributes_for method, rails sets a hash for all models nested, in nested_attributes_options. So we can use that to get these nested associations, to populate this new hash.
def to_nested_hash
nested_hash = self.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym } # And any other attributes you don't want
associations = self.nested_attributes_options.keys
associations.each do |association|
key = "#{association}_attributes"
nested_hash[key] = []
self.send(association).find_each do |child|
nested_hash[key] << child.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym }
end
end
return nested_hash
end
OR just thought of this:
Using your example above:
foo.as_json(:include => foo.nested_attributes_options.keys)
One thing to note, this won't give you the bars_attributes where my first suggestions will. (neither will serializable_hash)
You can use the following method to include nested options in hash
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
def to_nested_hash(options = nil)
options ||= {}
if options[:except]
incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
else
incl = self.nested_attributes_options.keys
end
options = { :include => incl }.merge(options)
self.serializable_hash(options)
end
end
If for some situations you don't want bars, you can pass options
foo.to_nested_hash(:except => :bars)
Edit: Another option if you want same behaviour in as_json, to_json and to_xml
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
def serializable_hash(options = nil)
options ||= {}
if options[:except]
incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
else
incl = self.nested_attributes_options.keys
end
options = { :include => incl }.merge(options)
super(options)
end
def to_nested_hash(options = nil)
self.serializable_hash(options)
end
end

select specific attributes from array rails

I have post model
class Post < ActiveRecord::Base
acts_as_voteable
end
and Vote model
class Vote < ActiveRecord::Base
scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) }
scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) }
scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) }
scope :descending, order("created_at DESC")
belongs_to :voteable, :counter_cache=>true,:polymorphic => true,:touch=>true
belongs_to :voter, :polymorphic => true
attr_accessible :vote, :voter, :voteable
# Comment out the line below to allow multiple votes per user.
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
end
when I get the post voters with these method
<% #post.voters_who_voted.each do |voter|%>
<%= voter.name %>
<% end %>
I load my database
how can I select only the user name and user id from these array?
update I changed my code I am using thumbs_up gem I pasted less code first to simplify the question
What do you mean by "load database"? If you want to select only id and name columns, then use #post.users.select([:id, :name]).each ...
Or is it about this problem (according to code that you provided)?
UPD.
voters_who_voted loads all voters and returns array https://github.com/bouchard/thumbs_up/blob/master/lib/acts_as_voteable.rb#L113. You have to add own association to Post model:
has_many :voters, :through => :votes, :source => :voter, :source_type => 'User'
It's just example, perhaps voters will clash with already existing method, if any.
Then use it here instead of voters_who_voted
did you try collect method ??
names = #post.users.collect(&:name)
ids = #post.user.collect(&:id)
If you want it to be related you can make a HASH with it. Id's mapped to the names.

Model Relationship Problem

I am trying to calculate the average (mean) rating for all entries within a category based on the following model associations ...
class Entry < ActiveRecord::Base
acts_as_rateable
belongs_to :category
...
end
class Category < ActiveRecord::Base
has_many :entry
...
end
class Rating < ActiveRecord::Base
belongs_to :rateable, :polymorphic => true
...
end
The rating model is handled by the acts as rateable plugin, so the rateable model looks like this ...
module Rateable #:nodoc:
...
module ClassMethods
def acts_as_rateable
has_many :ratings, :as => :rateable, :dependent => :destroy
...
end
end
...
end
How can I perform the average calculation? Can this be accomplished through the rails model associations or do I have to resort to a SQL query?
The average method is probably what you're looking for. Here's how to use it in your situation:
#category.entries.average('ratings.rating', :joins => :ratings)
Could you use a named_scope or custom method on the model. Either way it would still require some SQL since, if I understand the question, your are calculating a value.
In a traditional database application this would be a view on the data tables.
So in this context you might do something like... (note not tested or sure it is 100% complete)
class Category
has_many :entry do
def avg_rating()
#entries = find :all
#entres.each do |en|
#value += en.rating
end
return #value / entries.count
end
end
Edit - Check out EmFi's revised answer.
I make no promises but try this
class Category
def average_rating
Rating.average :rating,
:conditions => [ "type = ? AND entries.category_id = ?", "Entry", id ],
:join => "JOIN entries ON rateable_id = entries.id"
end
end

Resources