Permalink
Browse files

[ACTIVEMODEL] Implemented all Tire features for models via a `MyModel…

….tire` and `MyModel#tire` proxies for better isolation [#8, #9]

Brings the Tire methods into the model class only when not trampling on someone other's foot (`settings`, `update_index`, etc.)

These three calls are thus equivalent:

    class Article
      # ...
      mapping do
        indexes :id, :type => 'string', :index => :not_analyzed
      end
    end

    class Article
      # ...
      tire.mapping do
        indexes :id, :type => 'string', :index => :not_analyzed
      end
    end

    class Article
      # ...
      tire do
        mapping do
          indexes :id, :type => 'string', :index => :not_analyzed
        end
      end
    end
  • Loading branch information...
1 parent 86b3c2f commit dd5d156c02fdf9e5ede6b2a2f89b6956a7d6eb59 @karmi committed Aug 27, 2011
View
@@ -437,6 +437,46 @@ It may well be reasonable to wrap the index creation logic declared with `Tire.i
in a class method of your model, in a module method, etc, so have better control on index creation when bootstrapping your application with Rake tasks or when setting up the test suite.
_Tire_ will not hold that against you.
+You may have just stopped wondering: what if I have my own `settings` class method defined? Or what if some other gem defines `settings`, or some other _Tire_ method, such as `update_index`?
+Things will break, right? No, they won't.
+
+In fact, all this time you've been using only _proxies_ to the real _Tire_ methods, which live in the `tire`
+class and instance methods of your model. Only when not trampling on someone's foot — which is the majority
+of cases —, will _Tire_ bring its methods to the namespace of your class.
+
+So, instead of writing `Article.search`, you could write `Article.tire.search`, and instead of `@article.update_index` you could write `@article.tire.update_index`, to be on the safe side.
+Let's have a look on an example with the `mapping` method:
+
+```ruby
+ class Article < ActiveRecord::Base
+ include Tire::Model::Search
+ include Tire::Model::Callbacks
+
+ tire.mapping do
+ indexes :id, :type => 'string', :index => :not_analyzed
+ # ...
+ end
+ end
+```
+
+Of course, you could also use the block form:
+
+```ruby
+ class Article < ActiveRecord::Base
+ include Tire::Model::Search
+ include Tire::Model::Callbacks
+
+ tire do
+ mapping do
+ indexes :id, :type => 'string', :index => :not_analyzed
+ # ...
+ end
+ end
+ end
+```
+
+Internally, _Tire_ uses these proxy methods exclusively. When you run into issues, use the proxied method, eg. `Article.tire.mapping`, directly.
+
When you want a tight grip on how the attributes are added to the index, just
implement the `to_indexed_json` method in your model.
@@ -529,7 +569,7 @@ so you can pass all the usual parameters to the `search` method in the controlle
OK. Chances are, you have lots of records stored in your database. How will you get them to _ElasticSearch_? Easy:
```ruby
- Article.elasticsearch_index.import Article.all
+ Article.index.import Article.all
```
This way, however, all your records are loaded into memory, serialized into JSON,
View
@@ -21,9 +21,9 @@
require 'tire/model/naming'
require 'tire/model/callbacks'
require 'tire/model/percolate'
-require 'tire/model/search'
require 'tire/model/indexing'
require 'tire/model/import'
+require 'tire/model/search'
require 'tire/model/persistence/finders'
require 'tire/model/persistence/attributes'
require 'tire/model/persistence/storage'
@@ -5,8 +5,8 @@ module Callbacks
def self.included(base)
if base.respond_to?(:after_save) && base.respond_to?(:after_destroy)
- base.send :after_save, :update_elastic_search_index
- base.send :after_destroy, :update_elastic_search_index
+ base.send :after_save, lambda { tire.update_index }
+ base.send :after_destroy, lambda { tire.update_index }
end
if base.respond_to?(:before_destroy) && !base.instance_methods.map(&:to_sym).include?(:destroyed?)
@@ -17,7 +17,7 @@ def destroyed?; !!@destroyed; end
end
base.class_eval do
- define_model_callbacks(:update_elastic_search_index, :only => [:after, :before])
+ define_model_callbacks(:update_elasticsearch_index, :only => [:after, :before])
end if base.respond_to?(:define_model_callbacks)
end
View
@@ -7,7 +7,7 @@ module ClassMethods
def import options={}, &block
method = options.delete(:method) || 'paginate'
- self.elasticsearch_index.import self, method, options, &block
+ index.import klass, method, options, &block
end
end
@@ -45,8 +45,8 @@ def store_mapping?
end
def create_elasticsearch_index
- unless elasticsearch_index.exists?
- elasticsearch_index.create :mappings => mapping_to_hash, :settings => settings
+ unless index.exists?
+ index.create :mappings => mapping_to_hash, :settings => settings
end
end
View
@@ -6,21 +6,21 @@ module Naming
module ClassMethods
def index_name name=nil
@index_name = name if name
- @index_name || model_name.plural
+ @index_name || klass.model_name.plural
end
def document_type
- model_name.singular
+ klass.model_name.singular
end
end
module InstanceMethods
def index_name
- self.class.index_name
+ instance.class.tire.index_name
end
def document_type
- self.class.document_type
+ instance.class.tire.document_type
end
end
@@ -11,7 +11,7 @@ def percolate!(pattern=true)
def on_percolate(pattern=true,&block)
percolate!(pattern)
- after_update_elastic_search_index(block)
+ klass.after_update_elasticsearch_index(block)
end
def percolator
@@ -22,15 +22,15 @@ def percolator
module InstanceMethods
def percolate(&block)
- index.percolate self, block
+ index.percolate instance, block
end
def percolate=(pattern)
@_percolator = pattern
end
def percolator
- @_percolator || self.class.percolator || nil
+ @_percolator || instance.class.tire.percolator || nil
end
end
@@ -46,7 +46,7 @@ def update_attributes(attributes={})
def save
return false unless valid?
run_callbacks :save do
- # Document#id is set in the +update_elastic_search_index+ method,
+ # Document#id is set in the +update_elasticsearch_index+ method,
# where we have access to the JSON response
end
self
View
@@ -3,35 +3,6 @@ module Model
module Search
- def self.included(base)
- base.class_eval do
- extend Tire::Model::Naming::ClassMethods
- include Tire::Model::Naming::InstanceMethods
-
- extend Tire::Model::Indexing::ClassMethods
- extend Tire::Model::Import::ClassMethods
-
- extend Tire::Model::Percolate::ClassMethods
- include Tire::Model::Percolate::InstanceMethods
-
- extend ClassMethods
- include InstanceMethods
-
- ['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
- # TODO: Find a sane way to add attributes like _score for ActiveRecord -
- # `define_attribute_methods [attr]` does not work in AR.
- define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
- define_method("#{attr}") { @attributes[attr] }
- end
-
- def to_hash
- self.serializable_hash
- end unless instance_methods.map(&:to_sym).include?(:to_hash)
- end
-
- Results::Item.send :include, Loader
- end
-
module ClassMethods
# Returns search results for a given query.
@@ -60,7 +31,7 @@ module ClassMethods
#
#
def search(*args, &block)
- default_options = {:type => document_type, :index => elasticsearch_index.name}
+ default_options = {:type => document_type, :index => index.name}
if block_given?
options = args.shift || {}
@@ -91,15 +62,9 @@ def search(*args, &block)
s.perform.results
end
- # Wrapper for the ES index for this class
- #
- # TODO: Implement some "forwardable" object named +tire+ for Tire mixins,
- # and proxy everything via this object. If we're not stepping on
- # other libs toes, extend/include also to the top level.
- #
- # The original culprit is Mongoid here, see https://github.com/karmi/tire/issues/7
+ # Wraps an Index instance for this class
#
- def elasticsearch_index
+ def index
@index = Index.new(index_name)
end
@@ -108,32 +73,33 @@ def elasticsearch_index
module InstanceMethods
def index
- self.class.elasticsearch_index
+ instance.class.tire.index
end
- def update_elastic_search_index
- _run_update_elastic_search_index_callbacks do
- if destroyed?
- index.remove self
+ def update_index
+ instance.send :_run_update_elasticsearch_index_callbacks do
+ if instance.destroyed?
+ index.remove instance
else
- response = index.store( self, {:percolate => self.percolator} )
- self.id ||= response['_id'] if self.respond_to?(:id=)
- self._index = response['_index']
- self._type = response['_type']
- self._version = response['_version']
- self.matches = response['matches']
+ response = index.store( instance, {:percolate => percolator} )
+ instance.id ||= response['_id'] if instance.respond_to?(:id=)
+ instance._index = response['_index'] if instance.respond_to?(:_index=)
+ instance._type = response['_type'] if instance.respond_to?(:_type=)
+ instance._version = response['_version'] if instance.respond_to?(:_version=)
+ instance.matches = response['matches'] if instance.respond_to?(:matches=)
self
end
end
end
- alias :update_elasticsearch_index :update_elastic_search_index
+ alias :update_elasticsearch_index :update_index
+ alias :update_elastic_search_index :update_index
def to_indexed_json
- if self.class.mapping.empty?
- to_hash.to_json
+ if instance.class.tire.mapping.empty?
+ instance.to_hash.to_json
else
- to_hash.
- reject { |key, value| ! self.class.mapping.keys.map(&:to_s).include?(key.to_s) }.
+ instance.to_hash.
+ reject { |key, value| ! instance.class.tire.mapping.keys.map(&:to_s).include?(key.to_s) }.
to_json
end
end
@@ -150,7 +116,85 @@ def load(options=nil)
end
- extend ClassMethods
+ class ClassMethodsProxy
+ include Tire::Model::Naming::ClassMethods
+ include Tire::Model::Import::ClassMethods
+ include Tire::Model::Indexing::ClassMethods
+ include Tire::Model::Percolate::ClassMethods
+ include ClassMethods
+
+ INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
+
+ attr_reader :klass
+ def initialize(klass)
+ @klass = klass
+ end
+
+ end
+
+ class InstanceMethodsProxy
+ include Tire::Model::Naming::InstanceMethods
+ include Tire::Model::Percolate::InstanceMethods
+ include InstanceMethods
+
+ ['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
+ # TODO: Find a sane way to add attributes like _score for ActiveRecord -
+ # `define_attribute_methods [attr]` does not work in AR.
+ define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
+ define_method("#{attr}") { @attributes[attr] }
+ end
+
+ INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
+
+ attr_reader :instance
+ def initialize(instance)
+ @instance = instance
+ end
+ end
+
+ def self.included(base)
+ base.class_eval do
+ def self.tire &block
+ @__tire__ ||= ClassMethodsProxy.new(self)
+
+ @__tire__.instance_eval(&block) if block_given?
+ @__tire__
+ end
+
+ def tire &block
+ @__tire__ ||= InstanceMethodsProxy.new(self)
+
+ @__tire__.instance_eval(&block) if block_given?
+ @__tire__
+ end
+
+ def to_hash
+ self.serializable_hash
+ end unless instance_methods.map(&:to_sym).include?(:to_hash)
+
+ end
+
+ ClassMethodsProxy::INTERFACE.each do |method|
+ # puts "I AM SO NOT DEFINING #{method}!!!!" if base.public_methods.map(&:to_sym).include?(method.to_sym)
+ base.class_eval <<-"end;", __FILE__, __LINE__ unless base.public_methods.map(&:to_sym).include?(method.to_sym)
+ def self.#{method}(*args, &block)
+ tire.__send__(#{method.inspect}, *args, &block)
+ end
+ end;
+ end
+
+ InstanceMethodsProxy::INTERFACE.each do |method|
+ # puts "I AM SO NOT DEFINING #{method}!!!!" if base.instance_methods.map(&:to_sym).include?(method.to_sym)
+ base.class_eval <<-"end;", __FILE__, __LINE__ unless base.instance_methods.map(&:to_sym).include?(method.to_sym)
+ def #{method}(*args, &block)
+ tire.__send__(#{method.inspect}, *args, &block)
+ end
+ end;
+ end
+
+ Results::Item.send :include, Loader
+ end
+
end
end
View
@@ -44,18 +44,18 @@ def elapsed_to_human(elapsed)
klass = eval(ENV['CLASS'].to_s)
params = eval(ENV['PARAMS'].to_s) || {}
- index = Tire::Index.new( ENV['INDEX'] || klass.elasticsearch_index.name )
+ index = Tire::Index.new( ENV['INDEX'] || klass.tire.index.name )
if ENV['FORCE']
puts "[IMPORT] Deleting index '#{index.name}'"
index.delete
end
unless index.exists?
- mapping = defined?(Yajl) ? Yajl::Encoder.encode(klass.mapping_to_hash, :pretty => true) :
- MultiJson.encode(klass.mapping_to_hash)
+ mapping = defined?(Yajl) ? Yajl::Encoder.encode(klass.tire.mapping_to_hash, :pretty => true) :
+ MultiJson.encode(klass.tire.mapping_to_hash)
puts "[IMPORT] Creating index '#{index.name}' with mapping:", mapping
- index.create :mappings => klass.mapping_to_hash
+ index.create :mappings => klass.tire.mapping_to_hash
end
STDOUT.sync = true
Oops, something went wrong.

1 comment on commit dd5d156

Contributor

pacoguzman commented on dd5d156 Aug 29, 2011

Great Karel

Please sign in to comment.