Skip to content

Commit 0f273f6

Browse files
committed
[MODEL] Extracted the model integration into a proxy, to prevent model namespace pollution
To prevent the "pollution" of the including model by adding class and instance methods to it, the integration is, by default, proxied through class and instance level gateway: `__elasticsearch__`. These two methods includes all the `Elasticsearch::Model` logic, and serve as a transparent proxy between the model and the library. The proxy is set up automatically by including `Elasticsearch::Model`: class Article < ActiveRecord::Base include Elasticsearch::Model end Of course, users may still include/extend the corresponding modules separately, without using the proxy: MyModel.__send__ :extend, Elasticsearch::Model::Client::ClassMethods MyModel.__send__ :include, Elasticsearch::Model::Client::InstanceMethods MyModel.__send__ :extend, Elasticsearch::Model::Searching::ClassMethods MyModel.__send__ :extend, Elasticsearch::Model::Naming::ClassMethods MyModel.__send__ :include, Elasticsearch::Model::Naming::InstanceMethods MyModel.__send__ :extend, Elasticsearch::Model::Indexing::ClassMethods MyModel.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods MyModel.__send__ :include, Elasticsearch::Model::Serializing::InstanceMethods
1 parent 44da6c0 commit 0f273f6

File tree

2 files changed

+179
-14
lines changed

2 files changed

+179
-14
lines changed

elasticsearch-model/lib/elasticsearch/model.rb

+50-14
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,71 @@
1616
require 'elasticsearch/model/indexing'
1717
require 'elasticsearch/model/naming'
1818
require 'elasticsearch/model/serializing'
19+
require 'elasticsearch/model/searching'
20+
21+
require 'elasticsearch/model/proxy'
1922

2023
require 'elasticsearch/model/response'
2124
require 'elasticsearch/model/response/base'
2225
require 'elasticsearch/model/response/result'
2326
require 'elasticsearch/model/response/results'
2427
require 'elasticsearch/model/response/records'
25-
require 'elasticsearch/model/searching'
2628

2729
require 'elasticsearch/model/version'
2830

2931
module Elasticsearch
32+
33+
# Elasticsearch integration for Ruby models
34+
# =========================================
35+
#
36+
# TODO: Description
37+
#
3038
module Model
3139

32-
# Add the Elasticsearch::Model functionality the including class/module
40+
# Adds the `Elasticsearch::Model` functionality to the including class.
41+
#
42+
# * Creates the `__elasticsearch__` class and instance methods, pointing to the proxy object
43+
# * Includes the necessary modules in the proxy classes
44+
# * Sets up delegation for crucial methods such as `search`, etc.
45+
#
46+
# @example Include the {Elasticsearch::Model} module in the `Article` model definition
47+
#
48+
# class Article < ActiveRecord::Base
49+
# include Elasticsearch::Model
50+
# end
51+
#
52+
# @example Inject the {Elasticsearch::Model} module into the `Article` model
53+
#
54+
# Article.__send__ :include, Elasticsearch::Model
55+
#
56+
# It is possible to include/extend the model with the corresponding
57+
# modules directly, without using the proxy, if this is desired:
58+
#
59+
# MyModel.__send__ :extend, Elasticsearch::Model::Client::ClassMethods
60+
# MyModel.__send__ :include, Elasticsearch::Model::Client::InstanceMethods
61+
# MyModel.__send__ :extend, Elasticsearch::Model::Searching::ClassMethods
62+
# # ...
3363
#
3464
def self.included(base)
3565
base.class_eval do
36-
extend Elasticsearch::Model::Client::ClassMethods
37-
include Elasticsearch::Model::Client::InstanceMethods
38-
39-
extend Elasticsearch::Model::Naming::ClassMethods
40-
include Elasticsearch::Model::Naming::InstanceMethods
41-
42-
extend Elasticsearch::Model::Indexing::ClassMethods
43-
include Elasticsearch::Model::Indexing::InstanceMethods
44-
45-
include Elasticsearch::Model::Serializing::InstanceMethods
46-
47-
extend Elasticsearch::Model::Searching::ClassMethods
66+
include Elasticsearch::Model::Proxy
67+
68+
Elasticsearch::Model::Proxy::ClassMethodsProxy.class_eval do
69+
include Elasticsearch::Model::Client::ClassMethods
70+
include Elasticsearch::Model::Naming::ClassMethods
71+
include Elasticsearch::Model::Indexing::ClassMethods
72+
include Elasticsearch::Model::Searching::ClassMethods
73+
end
74+
75+
Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval do
76+
include Elasticsearch::Model::Client::InstanceMethods
77+
include Elasticsearch::Model::Naming::InstanceMethods
78+
include Elasticsearch::Model::Indexing::InstanceMethods
79+
include Elasticsearch::Model::Serializing::InstanceMethods
80+
end
81+
82+
extend Support::Forwardable
83+
forward :'self.__elasticsearch__', :search unless respond_to?(:search)
4884
end
4985
end
5086

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
module Elasticsearch
2+
module Model
3+
4+
# This module provides a proxy interfacing between the including class and
5+
# Elasticsearch::Model, preventing polluting the including class namespace.
6+
#
7+
# The only "gateway" between the model and Elasticsearch::Model is the
8+
# `__elasticsearch__` class and instance method.
9+
#
10+
# The including class must be compatible with
11+
# [ActiveModel](https://github.com/rails/rails/tree/master/activemodel).
12+
#
13+
# @example Include the {Elasticsearch::Model} module into an `Article` model
14+
#
15+
# class Article < ActiveRecord::Base
16+
# include Elasticsearch::Model
17+
# end
18+
#
19+
# Article.__elasticsearch__.respond_to?(:search)
20+
# # => true
21+
#
22+
# article = Article.first
23+
#
24+
# article.respond_to? :as_indexed_json
25+
# # => false
26+
#
27+
# article.__elasticsearch__.respond_to?(:as_indexed_json)
28+
# # => true
29+
#
30+
module Proxy
31+
32+
# Define the `__elasticsearch__` class and instance methods
33+
# in including class.
34+
#
35+
def self.included(base)
36+
base.class_eval do
37+
# {ClassMethodsProxy} instance, accessed as `MyModel.__elasticsearch__`
38+
#
39+
def self.__elasticsearch__ &block
40+
@__elasticsearch__ ||= ClassMethodsProxy.new(self)
41+
@__elasticsearch__.instance_eval(&block) if block_given?
42+
@__elasticsearch__
43+
end
44+
45+
# {InstanceMethodsProxy}, accessed as `@mymodel.__elasticsearch__`
46+
#
47+
def __elasticsearch__ &block
48+
@__elasticsearch__ ||= InstanceMethodsProxy.new(self)
49+
@__elasticsearch__.instance_eval(&block) if block_given?
50+
@__elasticsearch__
51+
end
52+
end
53+
end
54+
55+
# A proxy interfacing between Elasticsearch::Model class methods and model class methods
56+
#
57+
# TODO: Inherit from BasicObject and make Pry's `ls` command behave
58+
#
59+
class ClassMethodsProxy
60+
attr_reader :klass
61+
62+
def initialize(klass)
63+
@klass = klass
64+
end
65+
66+
# Delegate methods to `@klass`
67+
#
68+
def method_missing(method_name, *arguments, &block)
69+
klass.respond_to?(method_name) ? klass.__send__(method_name, *arguments, &block) : super
70+
end
71+
72+
# Respond to methods from `@klass`
73+
#
74+
def respond_to?(method_name, include_private = false)
75+
klass.respond_to?(method_name) || super
76+
end
77+
78+
def inspect
79+
"[PROXY] #{klass.inspect}"
80+
end
81+
end
82+
83+
# A proxy interfacing between Elasticsearch::Model instance methods and model instance methods
84+
#
85+
# TODO: Inherit from BasicObject and make Pry's `ls` command behave
86+
#
87+
class InstanceMethodsProxy
88+
attr_reader :instance
89+
90+
def initialize(instance)
91+
@instance = instance
92+
end
93+
94+
# Return the class of the target (instance class object)
95+
#
96+
def klass
97+
instance.class
98+
end
99+
100+
# Return the `ClassMethodsProxy` instance (instance class' `__elasticsearch__` object)
101+
#
102+
def class
103+
klass.__elasticsearch__
104+
end
105+
106+
def inspect
107+
"[PROXY] #{instance.inspect}"
108+
end
109+
110+
def as_json(options={})
111+
instance.as_json(options)
112+
end
113+
114+
# Delegate methods to `@instance`
115+
#
116+
def method_missing(method_name, *arguments, &block)
117+
instance.respond_to?(method_name) ? instance.__send__(method_name, *arguments, &block) : super
118+
end
119+
120+
# Respond to methods from `@instance`
121+
#
122+
def respond_to?(method_name, include_private = false)
123+
instance.respond_to?(method_name) || super
124+
end
125+
end
126+
127+
end
128+
end
129+
end

0 commit comments

Comments
 (0)