Implementing elasticsearch index update in ruby on rails application - ruby-on-rails

I am creating a rails application with integration to elasticsearch for fulltext search. I have the search working for a model, however when another part of the app does an attribute_update on it, I get below error:
Elasticsearch::Transport::Transport::Errors::BadRequest ([400] {"error":"no handler found for uri [/cards/_doc/123/_update] and method [POST]"}):
I tried to add a call to update_document in the model, however, I still get the same error as above.
class Card < ApplicationRecord
include Searchable
validates :attr1, uniqueness: true
after_update :my_es_update
def my_es_update
self.__elasticsearch__.update_document
end
def self.es_full_search
response = Card.__elasticsearch__.search(
query: {
match_all: {}
},
size: 150
).results
#cards = response.results
end
Update call:
def pin
#card = Card.find(params[:id])
#card.update_attribute("pinned", true)
redirect_back(fallback_location: root_path)
end
What would be the right way to update the index in ES upon update to persistence layer?
Edit:
I have the Elasticsearch::Model::Callbacks module included in my model, per the documentation it should take care of updates...I am not sure what part of the setup I am missing here.
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
mapping do
indexes :name, type: :text
indexes :type, type: :text
indexes :size, type: :text
end
end
end
Update2:
I am using ES vsn 8.5.3 and the 2 below gems in my Gemfile for communicating with it:
gem 'elasticsearch'
gem 'elasticsearch-model', '~> 7.2.1'

Related

Rails Fast Json API includes scope

In my Rails 6/Grape API app I've got a serializer where I want to include only active journeys (by active I mean journey.is_deleted: false). Current endpoint looks like:
helpers do
def query
current_user.journey_progresses.joins(:journey).where('is_deleted', false)
end
end
get :journeys do
::Journeys::EnrolledJourneysSerializer.new(
query,
include: [:journey],
class: { Journey: ::Journeys::JourneyListSerializer },
)
end
It includes all journeys no matter if they have is_deleted: true or is_deleted: false. I want to include only journey with is_deleted: false to not show deleted journeys in the serialized response.
EnrolledJourneysSerializer
module Journeys
class EnrolledJourneysSerializer
include FastJsonapi::ObjectSerializer
belongs_to :journey, serializer: JourneyListSerializer
set_type :percent_progress
attributes :percent_progress, :started_at
end
end
JourneyListSerializer
module Journeys
class JourneyListSerializer
include FastJsonapi::ObjectSerializer
attribute :content do |object|
object.content.dig('attributes')
end
end
end
Is there any way different than default_scope on a Journey model?
This line is wrong and needs to be changed to...
current_user
.journey_progresses
.joins(:journey)
.where(journeys: { is_deleted: false })

How to create index of an instance method in elasticsearch-rails?

What I want to do
https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model
Using this gem, I wanna create indexes of User model, including output of method named full_name. User has columns of id, first_name, last_name.
class User
...
def full_name
first_name + last_name
end
...
end
What I did
module UserSearchable
extend ActiveSupport::Concern
included do
include Searchable
settings index: {
...
}
mappings do
indexes :id, type: :integer
indexes :first_name, type: text
indexes :last_name, type: text
indexes :full_name, type: text, as: 'full_name'
end
def as_indexed_json(options={})
as_json(
methods: [:full_name]
)
end
end
end
I referred to this question.
Index the results of a method in ElasticSearch (Tire + ActiveRecord)
But this doesn't work, ending up with not containing full_name index in the response.
I'm new to elasticsearch and elasticsearch-rails.
How can I fix this?
Sorry, I just forgot to reload changes in the code!
And It works without as: 'full_name' in current version of the gem.

Validating nested models with ActiveModel::Validations

My application uses plain Ruby classes with ActiveModel::Validations, without implementing ActiveRecord:
class Car
include ::ActiveModel::Validations
attr_accessor :engine
end
class Engine
include ::ActiveModel::Validations
attr_accessor :cylinders
validates_presence_of :cylinders
end
I would like Car to check the nested attributes that are of ActiveModel::Validations, in this case engine.
car = Car.new
car.engine = Engine.new
car.engine.valid? # => false
car.valid? # => true
# It should return 'false',
# because 'engine.cylinders' is 'nil'
What's the easiest way to get this behavior?
One option is creating your own validation method, something like
class Car
include ::ActiveModel::Validations
attr_accessor :engine
validate :engine_must_be_valid
def engine_must_be_valid
errors.add(:base, "Engine is not valid") unless engine.valid?
end
end
I use Gem active_type in almost all my projects for similar requirements. Project statement - Make any Ruby object quack like ActiveRecord. Project github page provides good documentation as well.
in your Gemfile, add:
gem 'active_type'
Then,
class Car < ActiveType::Object
nests_one :engine
validates :check_engine
def check_engine
return true if self.engine.valid?
false
end
end
class Engine < ActiveType::Object
attribute :cylinders, :string
validates :cylinders, presence: true
end
Now,
car = Car.new
car.engine = Engine.new
car.engine.valid? # => false
car.valid? # => false

rails 4 gem 'mongoid_slug' unable to search by id

I just installed the gem 'mongoid_slug', here is the model:
class Book
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Slug
field :_id, type: String, slug_id_strategy: lambda {|id| id.start_with?('....')}
field :name, type: String
slug :name,
...
end
In the controller I have a function call get_book that I call before edit, show etc
Of course it is not working, I also tried find_by_slug.
Error Document not found for class Book with attributes {:id=>"try-new-book"}.
Request info
Request parameters
{"action"=>"show", "controller"=>"startups", "id"=>"try-new-book"}
def get_book
#book = Book.find_by(id: params[:id])
end
Thank you
According to docs it should works just:
Book.find params[:id]
Updated
I answered the same question here. So in short: change id.start_with?('....') to something like id =~ /^[[:alnum:]]+$/

ActiveRecord - replace model validation error with warning

I want to be able to replace a field error with a warning when saving/updating a model in rails. Basically I want to just write a wrapper around the validation methods that'll generate the error, save the model and perhaps be available in a warnings hash (which works just like the errors hash):
class Person < ActiveRecord::Base
# normal validation
validates_presence_of :name
# validation with warning
validates_numericality_of :age,
:only_integer => true,
:warning => true # <-- only warn
end
>>> p = Person.new(:name => 'john', :age => 2.2)
>>> p.save
=> true # <-- able to save to db
>>> p.warnings.map { |field, message| "#{field} - #{message}" }
["age - is not a number"] # <-- have access to warning content
Any idea how I could implement this? I was able to add :warning => false default value to ActiveRecord::Validations::ClassMethods::DEFAULT_VALIDATION_OPTIONS
By extending the module, but I'm looking for some insight on how to implement the rest. Thanks.
The validation_scopes gem uses some nice metaprogramming magic to give you all of the usual functionality of validations and ActiveRecord::Errors objects in contexts other than object.errors.
For example, you can say:
validation_scope :warnings do |s|
s.validates_presence_of :some_attr
end
The above validation will be triggered as usual on object.valid?, but won't block saves to the database on object.save if some_attr is not present. Any associated ActiveRecord::Errors objects will be found in object.warnings.
Validations specified in the usual manner without a scope will still behave as expected, blocking database saves and assigning error objects to object.errors.
The author has a brief description of the gem's development on his blog.
I don't know if it's ready for Rails 3, but this plugin does what you are looking for:
http://softvalidations.rubyforge.org/
Edited to add:
To update the basic functionality of this with ActiveModel I came up with the following:
#/config/initializer/soft_validate.rb:
module ActiveRecord
class Base
def warnings
#warnings ||= ActiveModel::Errors.new(self)
end
def complete?
warnings.clear
valid?
warnings.empty?
end
end
end
#/lib/soft_validate_validator.rb
class SoftValidateValidator < ActiveModel::EachValidator
def validate(record)
record.warnings.add_on_blank(attributes, options)
end
end
It adds a new Errors like object called warnings and a helper method complete?, and you can add it to a model like so:
class FollowupReport < ActiveRecord::Base
validates :suggestions, :soft_validate => true
end
I made my own gem to solve the problem for Rails 4.1+: https://github.com/s12chung/active_warnings
class BasicModel
include ActiveWarnings
attr_accessor :name
def initialize(name); #name = name; end
warnings do
validates :name, absence: true
end
end
model = BasicModel.new("some_name")
model.safe? # .invalid? equivalent, but for warnings
model.warnings # .errors equivalent

Resources