right now, overriding index_name and document_type won't work properly with rails STI
consider this example:
class Animal < ActiveRecord::Base
document_type 'mammal'
index_name 'mammals'
end
class Dog < Animal
end
Animal.document_type # 'mammal'
Animal.index_name # 'mammals'
Dog.document_type # 'dog'
Dog.index_name # 'dogs'
Since document_type and index_name are redefined in base class, i would expect the same to be reflected in derived classes also, but thats not happening. So, i have to manually redefine them in all the derived classes. Is this a bug?
Is this a bug?
Depends on the perspective. I consider "single table inheritance" itself to be a bug :)
The index_name method is bound to the @index_name "class instance variable". The method would have to traverse the ancestor hierarchy, check whether they have an index_name method, and use it, or fall back to defaults.
I don't think it's worth it for STI, it might be wort when there's some other use-case.
For reference, Tire issues related to STI: https://github.com/karmi/retire/search?q=STI&type=Issues
I use STI all the time - and like to think it saves me a lot of time :)
...and like _has_paper_trail_ it would be a real treat to be able to write _has_elastic_search_
Everything works for me - except for the
(tried to dig into the elasticsearch_rails - but that is still waaaayy beyond my 'rails-fu-level' <:) )
/W
class Abstracted < ActiveRecord::Base
self.abstract_class=true
include Exceptions
include PrintEngine
include Searchable
#
# all models inheriting from this will have versions
has_paper_trail
# has_paper_trail :ignore => [:state, :cups_state]
has_many :eventables, as: :eventable
#
# return that part of records which by default will be considered pertinent
# this way we will - later - be able to add some generic views - like
# not providing the deleted records, old records, records in other systems, etc
def self.pertinent
all
end
#
# translate the key
def self.oxt(key, *args)
I18n.t(key, args[0])
end
def oxt(key, *args)
self.class.oxt(key, args[0])
end
#
# when implementing searches on models (using params)
def self.find_with_params(params=nil)
self.search( params )
end
def self.logit( log_type, msg )
Rails.logger.send(log_type, "[OXEN] #{Time.now} [#{log_type.to_s}] #{msg}")
end
def logit( log_type, msg )
Rails.logger.send(log_type, "[OXEN] #{Time.now} [#{log_type.to_s}] #{msg}")
end
# list_title
def list_title
self.respond_to?( "name") ? self.name : "please define list_title on model (#{self.class.to_s})!"
end
# implement on relevant models
def default_record_template
'/record.html.haml'
end
def self.default_list_template
'list.html.haml'
end
def self.default_label_template
'label.html.haml'
end
def print_label options={}
params = {}
params[:print_job] = {}
params[:print_job][:view_template_path] = options[:template] || default_label_template
params[:print_job][:print_driver] = options[:print_driver] || :cab
params[:print_job][:print_format] = options[:print_format] || 'label'
params[:print_job][:paper] = options[:paper] || "Pakkelabel"
params[:printer_name] = options[:printer_name]
params[:user] = options[:user]
self.class.print [self], params
end
def print_record(options={})
params = {}
params[:print_job] = {}
params[:print_job][:view_template_path] = options[:template] || default_record_template
params[:print_job][:print_driver] = options[:print_driver] || :pdf
params[:print_job][:print_format] = options[:print_format] || 'sheet'
params[:print_job][:paper] = options[:paper] || "A4"
params[:printer_name] = options[:printer_name]
params[:user] = options[:user]
self.class.print [self], params
end
def self.print_list(options={})
params = {}
params[:print_job] = {}
params[:print_job][:view_template_path] = options[:template] || self.default_list_template
params[:print_job][:print_driver] = options[:print_driver] || :pdf
params[:print_job][:print_format] = options[:print_format] || 'list'
params[:print_job][:paper] = options[:paper] || "A4"
params[:printer_name] = options[:printer_name]
params[:user] = options[:user]
self.print options[:resources], params
end
# add the child to an association of children
def attach child
case child.class.to_s
when "Event","WageEvent"
ev = Eventable.create( event: child, eventable: self)
self.eventables << ev
when "Printer"
pr = Printable.create( printer: child, printable: self)
else
children = eval child.class.to_s.underscore.pluralize
children << child
end
end
# remove the child from an association of children
def detach child
case child.class.to_s
when "Event","WageEvent"
ev = Eventable.where( event: child, eventable: self).first
ev.delete
when "Printer"
pr = Printable.where( printer: child, printable: self).first
pr.delete
else
children = eval child.class.to_s.downcase.pluralize
children.delete child
end
end
end
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
__elasticsearch__.client = Elasticsearch::Client.new log: true
__elasticsearch__.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" }
include Indexing
after_touch() { __elasticsearch__.index_document }
# mapping do
# # ...
# end
#
# def self.search(query)
# # ...
# end
end
module Indexing
# Customize the JSON serialization for Elasticsearch
# def as_indexed_json(options={})
# self.as_json(
# include: { categories: { only: :title},
# authors: { methods: [:full_name], only: [:full_name] },
# comments: { only: :text }
# })
# end
end
end
class Employee < Abstracted
end
[1] oxen40 » Employee.base_class
=> class Employee < Abstracted {
:id => :integer,
:ox_id => :integer,
:name => :string,
:born_at => :datetime,
:enter_code => :string,
:active => :boolean,
:security_code => :string,
:entrance_code => :string,
:last_came_at => :datetime,
:last_went_at => :datetime,
:hour_pay_cents => :integer,
:wage_union_agreement => :string,
:last_wage_at => :datetime,
:datetime => :datetime,
:over_time_saving => :string,
:wage_service_bureau_emp_nbr => :string,
:bank_account_number => :string,
:lock_version => :integer,
:created_at => :datetime,
:updated_at => :datetime,
:hired_at => :datetime,
:currency => :string,
:white_collar => :boolean
}
[2] oxen40 »
After mulling it over for a while, I think I'll close this one. I really don't like complicating the code with ancestry lookup etc. If there's some great demand for a feature like this, let's reconsider it!
cool. i have a workaround for this problem. will let you know if we run into some problems.
@mathieugagne It would be better to open a specific issue for your problem, since "problems with STI" is too broad. There are definitely solutions for those problems -- not only "workaround".
any new changes?
@iffyuva what was your work around?
@bcackerman as described in the issue
class Animal < ActiveRecord::Base
document_type 'mammal'
index_name 'mammals'
end
class Dog < Animal
document_type 'mammal'
index_name 'mammals'
end
class Cat < Animal
document_type 'mammal'
index_name 'mammals'
end
manually declare document_type and index_name in each derived class.
Huge +1 to you. Thanks!!
Bruce
On Apr 1, 2016, at 5:49 AM, Yuva [email protected] wrote:
@bcackerman as described in the issue
class Animal < ActiveRecord::Base
document_type 'mammal'
index_name 'mammals'
endclass Dog < Animal
document_type 'mammal'
index_name 'mammals
endclass Cat < Animal
document_type 'mammal'
index_name 'mammals
end
manually declare document_type and index_name in each derived class.—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
Copying and pasting document_type and index_name in every subclass is not a manageable solution in larger code bases.
index_name should definitely "just work" with STI, and keep in line with the Rails philosophy. Here is my work around that does not require ancestry lookups:
class Animal < ActiveRecord::Base
document_type 'mammal'
index_name 'mammals'
def self.inherited(subclass)
subclass.instance_eval do
document_type 'mammal'
index_name 'mammals'
end
super
end
end
class Dog < Animal
end
class Cat < Animal
end
This could easily be placed into the standard Elasticsearch::Model concern. Here is a robust catch all solution:
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include BackgroundIndexer
index_name { "#{Rails.env}-#{es_type_name.pluralize}" }
document_type es_type_name
delegate :update_document, :index_document, :delete_document, to: :__elasticsearch__
end
module ClassMethods
# Callback so that subclasses still have index_name assigned.
def inherited(subclass)
subclass.instance_eval do
index_name { "#{Rails.env}-#{es_type_name.pluralize}" }
document_type es_type_name
end
super
end
def es_type_name
name.full_deconstantize.underscore
# full_deconstantize string ext method -> self.split("::").first || self
end
end
end
@dimroc to build off of what you have, this also makes it so that mappings get directed back to the root ancestor
module ClassMethods
def inherited(subclass)
search_class = self.search_class
index_name = self.index_name
document_type = self.document_type
subclass.instance_eval do
@search_class = search_class
index_name(index_name)
document_type(document_type)
def self.mapping(*args, &block)
search_class.mapping(*args, &block)
end
end
super
end
def search_class
@search_class || self
end
end
Most helpful comment
@dimroc to build off of what you have, this also makes it so that mappings get directed back to the root ancestor