mobility 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +3 -2
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +39 -1
- data/Gemfile.lock +65 -10
- data/README.md +63 -27
- data/lib/mobility.rb +16 -31
- data/lib/mobility/active_record.rb +2 -12
- data/lib/mobility/active_record/uniqueness_validator.rb +9 -8
- data/lib/mobility/arel.rb +20 -0
- data/lib/mobility/arel/nodes.rb +16 -0
- data/lib/mobility/arel/nodes/pg_ops.rb +136 -0
- data/lib/mobility/arel/visitor.rb +61 -0
- data/lib/mobility/attributes.rb +82 -19
- data/lib/mobility/backend.rb +53 -8
- data/lib/mobility/backend_resetter.rb +2 -1
- data/lib/mobility/backends/active_record.rb +31 -11
- data/lib/mobility/backends/active_record/column.rb +7 -3
- data/lib/mobility/backends/active_record/container.rb +23 -21
- data/lib/mobility/backends/active_record/hstore.rb +11 -6
- data/lib/mobility/backends/active_record/json.rb +22 -16
- data/lib/mobility/backends/active_record/jsonb.rb +22 -16
- data/lib/mobility/backends/active_record/key_value.rb +123 -15
- data/lib/mobility/backends/active_record/pg_hash.rb +1 -2
- data/lib/mobility/backends/active_record/serialized.rb +7 -6
- data/lib/mobility/backends/active_record/table.rb +145 -24
- data/lib/mobility/backends/hash_valued.rb +15 -10
- data/lib/mobility/backends/key_value.rb +12 -12
- data/lib/mobility/backends/sequel/container.rb +3 -9
- data/lib/mobility/backends/sequel/hstore.rb +2 -2
- data/lib/mobility/backends/sequel/json.rb +15 -15
- data/lib/mobility/backends/sequel/jsonb.rb +14 -14
- data/lib/mobility/backends/sequel/key_value.rb +0 -11
- data/lib/mobility/backends/sequel/pg_hash.rb +2 -3
- data/lib/mobility/backends/sequel/pg_query_methods.rb +1 -1
- data/lib/mobility/backends/sequel/query_methods.rb +3 -3
- data/lib/mobility/backends/sequel/serialized.rb +2 -2
- data/lib/mobility/backends/sequel/table.rb +10 -11
- data/lib/mobility/backends/table.rb +17 -8
- data/lib/mobility/configuration.rb +4 -1
- data/lib/mobility/interface.rb +0 -0
- data/lib/mobility/plugins.rb +1 -0
- data/lib/mobility/plugins/active_record/query.rb +192 -0
- data/lib/mobility/plugins/cache.rb +1 -2
- data/lib/mobility/plugins/default.rb +28 -14
- data/lib/mobility/plugins/fallbacks.rb +1 -1
- data/lib/mobility/plugins/locale_accessors.rb +13 -9
- data/lib/mobility/plugins/presence.rb +15 -7
- data/lib/mobility/plugins/query.rb +28 -0
- data/lib/mobility/translates.rb +9 -9
- data/lib/mobility/version.rb +1 -1
- data/lib/rails/generators/mobility/templates/initializer.rb +1 -0
- metadata +10 -15
- metadata.gz.sig +0 -0
- data/lib/mobility/accumulator.rb +0 -33
- data/lib/mobility/adapter.rb +0 -20
- data/lib/mobility/backends/active_record/column/query_methods.rb +0 -42
- data/lib/mobility/backends/active_record/container/json_query_methods.rb +0 -36
- data/lib/mobility/backends/active_record/container/jsonb_query_methods.rb +0 -33
- data/lib/mobility/backends/active_record/hstore/query_methods.rb +0 -25
- data/lib/mobility/backends/active_record/json/query_methods.rb +0 -30
- data/lib/mobility/backends/active_record/jsonb/query_methods.rb +0 -26
- data/lib/mobility/backends/active_record/key_value/query_methods.rb +0 -76
- data/lib/mobility/backends/active_record/pg_query_methods.rb +0 -154
- data/lib/mobility/backends/active_record/serialized/query_methods.rb +0 -34
- data/lib/mobility/backends/active_record/table/query_methods.rb +0 -105
@@ -25,13 +25,12 @@ To use the validator, you must +extend Mobility+ before calling +validates+
|
|
25
25
|
def validate_each(record, attribute, value)
|
26
26
|
klass = record.class
|
27
27
|
|
28
|
-
if (([*options[:scope]] + [attribute]).map(&:to_s) & klass.
|
29
|
-
warn %{
|
30
|
-
WARNING: The Mobility uniqueness validator for translated attributes does not
|
31
|
-
support case-insensitive validation. This option will be ignored for: #{attribute}
|
32
|
-
} if options[:case_sensitive] == false
|
28
|
+
if (([*options[:scope]] + [attribute]).map(&:to_s) & klass.mobility_attributes).present?
|
33
29
|
return unless value.present?
|
34
|
-
relation = klass.
|
30
|
+
relation = klass.__mobility_query_scope__ do |m|
|
31
|
+
node = m.__send__(attribute)
|
32
|
+
options[:case_sensitive] == false ? node.lower.eq(value.downcase) : node.eq(value)
|
33
|
+
end
|
35
34
|
relation = relation.where.not(klass.primary_key => record.id) if record.persisted?
|
36
35
|
relation = mobility_scope_relation(record, relation)
|
37
36
|
relation = relation.merge(options[:conditions]) if options[:conditions]
|
@@ -50,8 +49,10 @@ support case-insensitive validation. This option will be ignored for: #{attribut
|
|
50
49
|
private
|
51
50
|
|
52
51
|
def mobility_scope_relation(record, relation)
|
53
|
-
[*options[:scope]].inject(relation) do |scoped_relation, scope_item|
|
54
|
-
scoped_relation.
|
52
|
+
[*options[:scope]].inject(relation.unscoped) do |scoped_relation, scope_item|
|
53
|
+
scoped_relation.__mobility_query_scope__ do |m|
|
54
|
+
m.__send__(scope_item).eq(record.send(scope_item))
|
55
|
+
end
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
require "mobility/arel/nodes"
|
3
|
+
require "mobility/arel/visitor"
|
4
|
+
|
5
|
+
module Mobility
|
6
|
+
module Arel
|
7
|
+
class Attribute < ::Arel::Attributes::Attribute
|
8
|
+
attr_reader :backend_class
|
9
|
+
attr_reader :locale
|
10
|
+
attr_reader :attribute_name
|
11
|
+
|
12
|
+
def initialize(relation, column_name, locale, backend_class, attribute_name: nil)
|
13
|
+
@backend_class = backend_class
|
14
|
+
@locale = locale
|
15
|
+
@attribute_name = attribute_name || column_name
|
16
|
+
super(relation, column_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
module Mobility
|
3
|
+
module Arel
|
4
|
+
module Nodes
|
5
|
+
class Unary < ::Arel::Nodes::Unary; end
|
6
|
+
class Binary < ::Arel::Nodes::Binary; end
|
7
|
+
class Grouping < ::Arel::Nodes::Grouping; end
|
8
|
+
class Equality < ::Arel::Nodes::Equality; end
|
9
|
+
|
10
|
+
::Arel::Visitors::ToSql.class_eval do
|
11
|
+
alias :visit_Mobility_Arel_Nodes_Equality :visit_Arel_Nodes_Equality
|
12
|
+
alias :visit_Mobility_Arel_Nodes_Grouping :visit_Arel_Nodes_Grouping
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
require "mobility/arel"
|
3
|
+
|
4
|
+
module Mobility
|
5
|
+
module Arel
|
6
|
+
module Nodes
|
7
|
+
%w[
|
8
|
+
JsonDashArrow
|
9
|
+
JsonDashDoubleArrow
|
10
|
+
JsonbDashArrow
|
11
|
+
JsonbDashDoubleArrow
|
12
|
+
JsonbQuestion
|
13
|
+
HstoreDashArrow
|
14
|
+
HstoreQuestion
|
15
|
+
].each do |name|
|
16
|
+
const_set name, (Class.new(Binary) do
|
17
|
+
include ::Arel::Expressions
|
18
|
+
include ::Arel::Predications
|
19
|
+
include ::Arel::OrderPredications
|
20
|
+
include ::Arel::AliasPredication
|
21
|
+
|
22
|
+
def eq other
|
23
|
+
Equality.new self, quoted_node(other)
|
24
|
+
end
|
25
|
+
|
26
|
+
def lower
|
27
|
+
super self
|
28
|
+
end
|
29
|
+
end)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Needed for AR 4.2, can be removed when support is deprecated
|
33
|
+
if ::ActiveRecord::VERSION::STRING < '5.0'
|
34
|
+
[JsonbDashDoubleArrow, HstoreDashArrow].each do |klass|
|
35
|
+
klass.class_eval do
|
36
|
+
def quoted_node other
|
37
|
+
other && super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Jsonb < JsonbDashDoubleArrow
|
44
|
+
def to_dash_arrow
|
45
|
+
JsonbDashArrow.new left, right
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_question
|
49
|
+
JsonbQuestion.new left, right
|
50
|
+
end
|
51
|
+
|
52
|
+
def eq other
|
53
|
+
case other
|
54
|
+
when NilClass
|
55
|
+
to_question.not
|
56
|
+
when Integer, Array, Hash
|
57
|
+
to_dash_arrow.eq other.to_json
|
58
|
+
when Jsonb
|
59
|
+
to_dash_arrow.eq other.to_dash_arrow
|
60
|
+
when JsonbDashArrow
|
61
|
+
to_dash_arrow.eq other
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Hstore < HstoreDashArrow
|
69
|
+
def to_question
|
70
|
+
HstoreQuestion.new left, right
|
71
|
+
end
|
72
|
+
|
73
|
+
def eq other
|
74
|
+
other.nil? ? to_question.not : super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Json < JsonDashDoubleArrow; end
|
79
|
+
|
80
|
+
class JsonContainer < Json
|
81
|
+
def initialize column, locale, attr
|
82
|
+
super(Arel::Nodes::JsonDashArrow.new(column, locale), attr)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class JsonbContainer < Jsonb
|
87
|
+
def initialize column, locale, attr
|
88
|
+
@column, @locale = column, locale
|
89
|
+
super(JsonbDashArrow.new(column, locale), attr)
|
90
|
+
end
|
91
|
+
|
92
|
+
def eq other
|
93
|
+
other.nil? ? super.or(JsonbQuestion.new(@column, @locale).not) : super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module Visitors
|
99
|
+
def visit_Mobility_Arel_Nodes_JsonDashArrow o, a
|
100
|
+
json_infix o, a, '->'
|
101
|
+
end
|
102
|
+
|
103
|
+
def visit_Mobility_Arel_Nodes_JsonDashDoubleArrow o, a
|
104
|
+
json_infix o, a, '->>'
|
105
|
+
end
|
106
|
+
|
107
|
+
def visit_Mobility_Arel_Nodes_JsonbDashArrow o, a
|
108
|
+
json_infix o, a, '->'
|
109
|
+
end
|
110
|
+
|
111
|
+
def visit_Mobility_Arel_Nodes_JsonbDashDoubleArrow o, a
|
112
|
+
json_infix o, a, '->>'
|
113
|
+
end
|
114
|
+
|
115
|
+
def visit_Mobility_Arel_Nodes_JsonbQuestion o, a
|
116
|
+
json_infix o, a, '?'
|
117
|
+
end
|
118
|
+
|
119
|
+
def visit_Mobility_Arel_Nodes_HstoreDashArrow o, a
|
120
|
+
json_infix o, a, '->'
|
121
|
+
end
|
122
|
+
|
123
|
+
def visit_Mobility_Arel_Nodes_HstoreQuestion o, a
|
124
|
+
json_infix o, a, '?'
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def json_infix o, a, opr
|
130
|
+
visit(Nodes::Grouping.new(::Arel::Nodes::InfixOperation.new(opr, o.left, o.right)), a)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
::Arel::Visitors::PostgreSQL.include Visitors
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
module Mobility
|
3
|
+
module Arel
|
4
|
+
class Visitor < ::Arel::Visitors::Visitor
|
5
|
+
INNER_JOIN = ::Arel::Nodes::InnerJoin
|
6
|
+
OUTER_JOIN = ::Arel::Nodes::OuterJoin
|
7
|
+
|
8
|
+
attr_reader :backend_class, :locale
|
9
|
+
|
10
|
+
def initialize(backend_class, locale)
|
11
|
+
super()
|
12
|
+
@backend_class, @locale = backend_class, locale
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def visit(object)
|
18
|
+
super
|
19
|
+
rescue TypeError
|
20
|
+
visit_default(object)
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_collection(_objects)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
alias :visit_Array :visit_collection
|
27
|
+
|
28
|
+
def visit_Arel_Nodes_Unary(object)
|
29
|
+
visit(object.expr)
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_Arel_Nodes_Binary(object)
|
33
|
+
visit_collection([object.left, object.right])
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_Arel_Nodes_Function(object)
|
37
|
+
visit_collection(object.expressions)
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_Arel_Nodes_Case(object)
|
41
|
+
visit_collection([object.case, object.conditions, object.default])
|
42
|
+
end
|
43
|
+
|
44
|
+
def visit_Arel_Nodes_And(object)
|
45
|
+
visit_Array(object.children)
|
46
|
+
end
|
47
|
+
|
48
|
+
def visit_Arel_Nodes_Node(object)
|
49
|
+
visit_default(object)
|
50
|
+
end
|
51
|
+
|
52
|
+
def visit_Arel_Attributes_Attribute(object)
|
53
|
+
visit_default(object)
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_default(_object)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/mobility/attributes.rb
CHANGED
@@ -133,20 +133,18 @@ with other backends.
|
|
133
133
|
raise ArgumentError, "method must be one of: reader, writer, accessor" unless %i[reader writer accessor].include?(method)
|
134
134
|
@method = method
|
135
135
|
@options = Mobility.default_options.to_h.merge(backend_options)
|
136
|
-
@names = attribute_names.map(&:to_s)
|
137
|
-
raise
|
136
|
+
@names = attribute_names.map(&:to_s).freeze
|
137
|
+
raise BackendRequired, "Backend option required if Mobility.config.default_backend is not set." if backend.nil?
|
138
138
|
@backend_name = backend
|
139
139
|
end
|
140
140
|
|
141
|
-
# Setup backend class, include modules into model class,
|
142
|
-
#
|
143
|
-
#
|
141
|
+
# Setup backend class, include modules into model class, include/extend
|
142
|
+
# shared modules and setup model with backend setup block (see
|
143
|
+
# {Mobility::Backend::Setup#setup_model}).
|
144
144
|
# @param klass [Class] Class of model
|
145
145
|
def included(klass)
|
146
146
|
@model_class = @options[:model_class] = klass
|
147
|
-
@backend_class =
|
148
|
-
|
149
|
-
@backend_class.configure(options) if @backend_class.respond_to?(:configure)
|
147
|
+
@backend_class = get_backend_class(backend_name).for(model_class).with_options(options)
|
150
148
|
|
151
149
|
Mobility.plugins.each do |name|
|
152
150
|
plugin = get_plugin_class(name)
|
@@ -159,8 +157,10 @@ with other backends.
|
|
159
157
|
define_writer(name) if %i[accessor writer].include?(method)
|
160
158
|
end
|
161
159
|
|
162
|
-
|
163
|
-
|
160
|
+
klass.include InstanceMethods
|
161
|
+
klass.extend ClassMethods
|
162
|
+
|
163
|
+
backend_class.setup_model(model_class, names)
|
164
164
|
end
|
165
165
|
|
166
166
|
# Yield each attribute name to block
|
@@ -169,6 +169,8 @@ with other backends.
|
|
169
169
|
names.each(&block)
|
170
170
|
end
|
171
171
|
|
172
|
+
# Show useful information about this module.
|
173
|
+
# @return [String]
|
172
174
|
def inspect
|
173
175
|
"#<Attributes (#{backend_name}) @names=#{names.join(", ")}>"
|
174
176
|
end
|
@@ -176,37 +178,35 @@ with other backends.
|
|
176
178
|
private
|
177
179
|
|
178
180
|
def define_backend(attribute)
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
@mobility_backends[attribute] ||= backend_class_.new(self, attribute, options_)
|
181
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
182
|
+
def #{Backend.method_name(attribute)}
|
183
|
+
mobility_backends[:#{attribute}]
|
183
184
|
end
|
185
|
+
EOM
|
184
186
|
end
|
185
187
|
|
186
188
|
def define_reader(attribute)
|
187
|
-
backend = Backend.method_name(attribute)
|
188
189
|
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
189
190
|
def #{attribute}(**options)
|
190
191
|
return super() if options.delete(:super)
|
191
192
|
#{set_locale_from_options_inline}
|
192
|
-
|
193
|
+
mobility_backends[:#{attribute}].read(locale, options)
|
193
194
|
end
|
194
195
|
|
195
196
|
def #{attribute}?(**options)
|
196
197
|
return super() if options.delete(:super)
|
197
198
|
#{set_locale_from_options_inline}
|
198
|
-
|
199
|
+
mobility_backends[:#{attribute}].present?(locale, options)
|
199
200
|
end
|
200
201
|
EOM
|
201
202
|
end
|
202
203
|
|
203
204
|
def define_writer(attribute)
|
204
|
-
backend = Backend.method_name(attribute)
|
205
205
|
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
206
206
|
def #{attribute}=(value, **options)
|
207
207
|
return super(value) if options.delete(:super)
|
208
208
|
#{set_locale_from_options_inline}
|
209
|
-
|
209
|
+
mobility_backends[:#{attribute}].write(locale, value, options)
|
210
210
|
end
|
211
211
|
EOM
|
212
212
|
end
|
@@ -240,5 +240,68 @@ EOL
|
|
240
240
|
klass_name = key.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
241
241
|
parent_class.const_get(klass_name)
|
242
242
|
end
|
243
|
+
|
244
|
+
module InstanceMethods
|
245
|
+
# Return a new backend for an attribute name.
|
246
|
+
# @return [Hash] Hash of attribute names and backend instances
|
247
|
+
def mobility_backends
|
248
|
+
@mobility_backends ||= Hash.new do |hash, name|
|
249
|
+
next hash[name.to_sym] if String === name
|
250
|
+
hash[name] = self.class.mobility_backend_class(name).new(self, name.to_s)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def initialize_dup(other)
|
255
|
+
@mobility_backends = nil
|
256
|
+
super
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
module ClassMethods
|
261
|
+
# Return all {Mobility::Attribute} module instances from among ancestors
|
262
|
+
# of this model.
|
263
|
+
# @return [Array<Mobility::Attributes>] Attribute modules
|
264
|
+
def mobility_modules
|
265
|
+
ancestors.select { |mod| Attributes === mod }
|
266
|
+
end
|
267
|
+
|
268
|
+
# Return translated attribute names on this model.
|
269
|
+
# @return [Array<String>] Attribute names
|
270
|
+
def mobility_attributes
|
271
|
+
mobility_modules.map(&:names).flatten
|
272
|
+
end
|
273
|
+
|
274
|
+
# @!method translated_attribute_names
|
275
|
+
# @return (see #mobility_attributes)
|
276
|
+
alias translated_attribute_names mobility_attributes
|
277
|
+
|
278
|
+
# Return backend class for a given attribute name.
|
279
|
+
# @param [Symbol,String] Name of attribute
|
280
|
+
# @return [Class] Backend class
|
281
|
+
def mobility_backend_class(name)
|
282
|
+
@backends ||= BackendsCache.new(self)
|
283
|
+
@backends[name.to_sym]
|
284
|
+
end
|
285
|
+
|
286
|
+
class BackendsCache < Hash
|
287
|
+
def initialize(klass)
|
288
|
+
# Preload backend mapping
|
289
|
+
klass.mobility_modules.each do |mod|
|
290
|
+
mod.names.each { |name| self[name.to_sym] = mod.backend_class }
|
291
|
+
end
|
292
|
+
|
293
|
+
super() do |hash, name|
|
294
|
+
if mod = klass.mobility_modules.find { |m| m.names.include? name.to_s }
|
295
|
+
hash[name] = mod.backend_class
|
296
|
+
else
|
297
|
+
raise KeyError, "No backend for: #{name}."
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
private_constant :BackendsCache
|
303
|
+
end
|
243
304
|
end
|
305
|
+
|
306
|
+
class BackendRequired < ArgumentError; end
|
244
307
|
end
|
data/lib/mobility/backend.rb
CHANGED
@@ -66,7 +66,7 @@ On top of this, a backend will normally:
|
|
66
66
|
# @!macro [new] backend_constructor
|
67
67
|
# @param model Model on which backend is defined
|
68
68
|
# @param [String] attribute Backend attribute
|
69
|
-
def initialize(model, attribute
|
69
|
+
def initialize(model, attribute)
|
70
70
|
@model = model
|
71
71
|
@attribute = attribute
|
72
72
|
end
|
@@ -106,10 +106,22 @@ On top of this, a backend will normally:
|
|
106
106
|
Util.present?(read(locale, options))
|
107
107
|
end
|
108
108
|
|
109
|
-
#
|
109
|
+
# @!method model_class
|
110
|
+
# Returns name of model in which backend is used.
|
111
|
+
# @return [Class] Model class
|
112
|
+
|
113
|
+
# @return [Hash] options
|
114
|
+
def options
|
115
|
+
self.class.options
|
116
|
+
end
|
117
|
+
|
118
|
+
# Extend included class with +setup+ method and other class methods
|
110
119
|
def self.included(base)
|
111
|
-
base.extend
|
112
|
-
base.
|
120
|
+
base.extend ClassMethods
|
121
|
+
def base.options
|
122
|
+
@options
|
123
|
+
end
|
124
|
+
base.option_reader :model_class
|
113
125
|
end
|
114
126
|
|
115
127
|
# @param [String] attribute
|
@@ -120,7 +132,7 @@ On top of this, a backend will normally:
|
|
120
132
|
end
|
121
133
|
|
122
134
|
# Defines setup hooks for backend to customize model class.
|
123
|
-
module
|
135
|
+
module ClassMethods
|
124
136
|
# Assign block to be called on model class.
|
125
137
|
# @yield [attribute_names, options]
|
126
138
|
# @note When called multiple times, setup blocks will be appended
|
@@ -139,19 +151,46 @@ On top of this, a backend will normally:
|
|
139
151
|
|
140
152
|
def inherited(subclass)
|
141
153
|
subclass.instance_variable_set(:@setup_block, @setup_block)
|
154
|
+
subclass.instance_variable_set(:@options, @options)
|
142
155
|
end
|
143
156
|
|
144
157
|
# Call setup block on a class with attributes and options.
|
145
158
|
# @param model_class Class to be setup-ed
|
146
159
|
# @param [Array<String>] attribute_names
|
147
160
|
# @param [Hash] options
|
148
|
-
def setup_model(model_class, attribute_names
|
161
|
+
def setup_model(model_class, attribute_names)
|
149
162
|
return unless setup_block = @setup_block
|
150
163
|
model_class.class_exec(attribute_names, options, &setup_block)
|
151
164
|
end
|
152
|
-
end
|
153
165
|
|
154
|
-
|
166
|
+
# Build a subclass of this backend class for a given set of options
|
167
|
+
# @note This method also freezes the options hash to prevent it from
|
168
|
+
# being changed.
|
169
|
+
# @param [Hash] options
|
170
|
+
# @return [Class] backend subclass
|
171
|
+
def with_options(options = {}, &block)
|
172
|
+
configure(options) if respond_to?(:configure)
|
173
|
+
options.freeze
|
174
|
+
Class.new(self) do
|
175
|
+
@options = options
|
176
|
+
class_eval(&block) if block_given?
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Create instance and class methods to access value on options hash
|
181
|
+
# @param [Symbol] name Name of option reader
|
182
|
+
def option_reader(name)
|
183
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
184
|
+
def self.#{name}
|
185
|
+
options[:#{name}]
|
186
|
+
end
|
187
|
+
|
188
|
+
def #{name}
|
189
|
+
self.class.options[:#{name}]
|
190
|
+
end
|
191
|
+
EOM
|
192
|
+
end
|
193
|
+
|
155
194
|
# {Attributes} uses this method to get a backend class specific to the
|
156
195
|
# model using the backend. Backend classes can override this method to
|
157
196
|
# return a class specific to the model class using the backend (e.g.
|
@@ -172,6 +211,12 @@ On top of this, a backend will normally:
|
|
172
211
|
def apply_plugin(_)
|
173
212
|
false
|
174
213
|
end
|
214
|
+
|
215
|
+
# Show useful information about this backend class, if it has no name.
|
216
|
+
# @return [String]
|
217
|
+
def inspect
|
218
|
+
name ? super : "#<#{superclass.name}>"
|
219
|
+
end
|
175
220
|
end
|
176
221
|
|
177
222
|
Translation = Struct.new(:backend, :locale) do
|