mobility 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|