Automatically update index with elasticsearch-rails - ruby-on-rails

I'm implementing the completion suggester using the elasticsearch-rails gem. Everything works except update or delete.
For example when I update the title of an article and try to research again, the same title still exist.
I have included Elasticsearch::Model::Callbacks
Model:
require 'elasticsearch/model'
class Article < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
def self.suggest(query)
Article.__elasticsearch__.client.suggest(:index => Article.index_name, :body => {
:suggestions => {
:text => query,
:completion => {
:field => 'suggest'
}
}
})
end
settings :index => { :number_of_shards => 1 } do
mappings :dynamic => 'false' do
indexes :title, :type => 'string', :analyzer => 'english'
indexes :suggest, :type => 'completion', :index_analyzer => 'simple', :search_analyzer => 'simple', :payloads => true
end
end
def as_indexed_json(options={})
{
:name => self.title,
:suggest => {
:input => [self.title, self.content],
:output => self.title,
:payload => {
:id => self.id,
:content => self.content
}
}
}
end
end
Controller:
class ArticlesController < ApplicationController
def update
#article = Article.find(params[:id])
if #article.update_attributes(article_params)
render :json => #article
else
render :json => #article.errors
end
end
# ...
end

we had that same problem.
The only way to change the autocompletion data is to call the optimize API.
Optimize will cause a segment merge.
Completion suggesters are stored in their own datastructure calles FST. They are not part of the regular index, so just refreshing would not work.
The datastructure used to store completion suggestions is only created at index time, when a new segment is written. All the old data will be available until a full cleanup happens, which is only guaranteed when segments are merged.
So call optimize:
http://localhost:9200/indexName/_optimize?max_num_segments=number_of_segments
http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/merge-process.html#optimize-api
The smaller the number of segments the more performant completion will be.
And then check again. For me it worked!
Optimizing is slow and I/O heavy, so you cannot run it all the time, but maybe once a day or so...
Good luck!
https://groups.google.com/forum/?fromgroups#!searchin/elasticsearch/completion$20suggester$20delete/elasticsearch/8Rfg4kGV0ps/YG0V9jM7JhcJ
http://www.elasticsearch.org/blog/you-complete-me/

Related

Highcharts gem not working

I'm trying to create a line graph of weights for a user off of the lazy high charts gem.
I currently have in my users_controller
def show
#user = User.find(params[:id])
#weights = Weight.where(user_id: #user.id)
#weight_hash = #weights.to_json
#chart = LazyHighCharts::HighChart.new('graph') do |f|
f.title(:text => "Historical Weights")
f.xAxis(:type => 'datetime', :title => {:text =>'Date'})
f.yAxis(:title => {:text => "pounds"})
f.series(:name => 'Weight', :data => #weight_hash)
f.chart({defaultSeriesType => 'line'})
end
end
Within my weight model I have:
class Weight < ActiveRecord::Base
belongs_to :user
def as_json(*args)
{
:weight => self.weight,
:date => self.date
}
end
end
Then in my users/show.html.erb I have
<%= high_chart("Weight", #chart) %>
but i'm getting the error
undefined local variable or method `defaultSeriesType' for
#
I'm not sure how this method should be declared as it is part of the gem. Could anyone please explain what is going on?
In this line:
f.chart({defaultSeriesType => 'line'})
It looks like you forgot to add a colon to defaultSeriesType to make it a symbol, so Ruby thinks it's a variable/method. Try changing it to:
f.chart({:defaultSeriesType => 'line'})
...like the other hashes.

Ruby on Rails 4 render :json with conditions

I have this render expression in a Ruby on Rails 4 controller:
render :json => #account_preferences, :include => {:payment_methods => {:include => {:payment_type => {:only => [:id, :name]}}}}
Can I filter the payment_methods somehow in this expression to get only the payment_methods with state = 'Confirmed'?
I don't believe you can do this conditionally in the render :json.
But something like this would work:
class AccountPreference < ActiveRecord::Base
has_many :confirmed_payment_methods,-> { where state: 'Confirmed' }, class_name: 'PaymentMethod'
end
and call that association in your to_json
render :json => #account_preferences, :include => {:confirmed_payment_methods => {:include => {:payment_type => {:only => [:id, :name]}}}}
If you do a lot of includes and conditionals I'd recommend using jbuilder to do this kind of stuff.
Did you take a look at that?
https://github.com/rails/jbuilder
It's included in your Gemfile by default in a rails 4 project.

Why is Routes.rb not loading the IPs from cache?

I am testing this in local. My ip is 127.0.0.1. The ip_permissions table, is empty. When I browse the site, everything works as expected.
Now, I want to simulate browsing the site with a banned IP. So I add the IP into the ip_permissions table via:
IpPermission.create!(:ip => '127.0.0.1', :note => 'foobar', :category => 'blacklist')
In Rails console, I clear the cache via; Rails.cache.clear. I browse the site. I don't get sent to pages#blacklist.
If I restart the server. And browse the site, then I get sent to pages#blacklist. Why do I need to restart the server every time the ip_permissions table is updated? Shouldn't it fetch it based on cache?
Routes look like:
class BlacklistConstraint
def initialize
#blacklist = IpPermission.blacklist
end
def matches?(request)
#blacklist.map { |b| b.ip }.include? request.remote_ip
end
end
Foobar::Application.routes.draw do
match '/(*path)' => 'pages#blacklist', :constraints => BlacklistConstraint.new
....
end
My model looks like:
class IpPermission < ActiveRecord::Base
validates_presence_of :ip, :note, :category
validates_uniqueness_of :ip, :scope => [:category]
validates :category, :inclusion => { :in => ['whitelist', 'blacklist'] }
def self.whitelist
Rails.cache.fetch('whitelist', :expires_in => 1.month) { self.where(:category => 'whitelist').all }
end
def self.blacklist
Rails.cache.fetch('blacklist', :expires_in => 1.month) { self.where(:category => 'blacklist').all }
end
end
You are initializing BlacklistConstraint in your routes file which is loaded only once at the start. There you call IpPermission.blacklist and store it in an instance variable. Initialize is not called anymore and therefore you check against the same records.
You should load the records on each request if you want them to be updated:
class BlacklistConstraint
def blacklist
IpPermission.blacklist
end
def matches?(request)
blacklist.map { |b| b.ip }.include? request.remote_ip
end
end

problem using 'as_json' in my model and 'render :json' => in my controller (rails)

I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.

Ruby on Rails calling ActiveRecord model from within ActionController plugin module

I've a got a method in ActiveRecord::User:
def create_user_from_json(user)
#user=User.new(user)
if #user.save!
#user.activate!
end
end
And I'm trying to call it in a plugin's module method. The plugin is json-rpc-1-1. Here is the relevant code:
class ServiceController < ApplicationController
json_rpc_service :name => 'cme', # required
:id => 'urn:uuid:28e54ac0-f1be-11df-889f-0002a5d5c51b', # required
:logger => RAILS_DEFAULT_LOGGER # optional
json_rpc_procedure :name => 'userRegister',
:proc => lambda { |u| ActiveRecord::User.create_user_from_json(u) },
:summary => 'Adds a user to the database',
:params => [{:name => 'newUser', :type => 'object'}],
:return => {:type => 'num'}
end
The problem is the code in the proc. No matter whether I call ActiveRecord::User.create_user_from_json(u) or ::User.create_user_from_json(u) or User.create_user_from_json(u) I just get undefined method 'create_user_from_json'.
What is the proper way to call a User method from the proc?
I think this needs to be a class method instead of an instance method, declare it like this:
def self.create_user_from_json(user)
...
end

Resources