mobility 0.0.1 → 0.1.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 +4 -4
- data/Gemfile +19 -0
- data/Gemfile.lock +153 -0
- data/Guardfile +70 -0
- data/README.md +603 -13
- data/Rakefile +42 -0
- data/lib/generators/mobility/install_generator.rb +45 -0
- data/lib/generators/mobility/templates/create_string_translations.rb +15 -0
- data/lib/generators/mobility/templates/create_text_translations.rb +15 -0
- data/lib/mobility.rb +203 -2
- data/lib/mobility/active_model.rb +6 -0
- data/lib/mobility/active_model/attribute_methods.rb +27 -0
- data/lib/mobility/active_model/backend_resetter.rb +26 -0
- data/lib/mobility/active_record.rb +39 -0
- data/lib/mobility/active_record/backend_resetter.rb +26 -0
- data/lib/mobility/active_record/model_translation.rb +14 -0
- data/lib/mobility/active_record/string_translation.rb +7 -0
- data/lib/mobility/active_record/text_translation.rb +7 -0
- data/lib/mobility/active_record/translation.rb +14 -0
- data/lib/mobility/attributes.rb +210 -0
- data/lib/mobility/backend.rb +152 -0
- data/lib/mobility/backend/active_model.rb +7 -0
- data/lib/mobility/backend/active_model/dirty.rb +84 -0
- data/lib/mobility/backend/active_record.rb +13 -0
- data/lib/mobility/backend/active_record/column.rb +52 -0
- data/lib/mobility/backend/active_record/column/query_methods.rb +40 -0
- data/lib/mobility/backend/active_record/hash_valued.rb +58 -0
- data/lib/mobility/backend/active_record/hstore.rb +36 -0
- data/lib/mobility/backend/active_record/hstore/query_methods.rb +53 -0
- data/lib/mobility/backend/active_record/jsonb.rb +43 -0
- data/lib/mobility/backend/active_record/jsonb/query_methods.rb +53 -0
- data/lib/mobility/backend/active_record/key_value.rb +126 -0
- data/lib/mobility/backend/active_record/key_value/query_methods.rb +63 -0
- data/lib/mobility/backend/active_record/query_methods.rb +36 -0
- data/lib/mobility/backend/active_record/serialized.rb +93 -0
- data/lib/mobility/backend/active_record/serialized/query_methods.rb +32 -0
- data/lib/mobility/backend/active_record/table.rb +197 -0
- data/lib/mobility/backend/active_record/table/query_methods.rb +91 -0
- data/lib/mobility/backend/cache.rb +110 -0
- data/lib/mobility/backend/column.rb +52 -0
- data/lib/mobility/backend/dirty.rb +28 -0
- data/lib/mobility/backend/fallbacks.rb +89 -0
- data/lib/mobility/backend/hstore.rb +21 -0
- data/lib/mobility/backend/jsonb.rb +21 -0
- data/lib/mobility/backend/key_value.rb +71 -0
- data/lib/mobility/backend/null.rb +24 -0
- data/lib/mobility/backend/orm_delegator.rb +33 -0
- data/lib/mobility/backend/sequel.rb +14 -0
- data/lib/mobility/backend/sequel/column.rb +40 -0
- data/lib/mobility/backend/sequel/column/query_methods.rb +24 -0
- data/lib/mobility/backend/sequel/dirty.rb +54 -0
- data/lib/mobility/backend/sequel/hash_valued.rb +51 -0
- data/lib/mobility/backend/sequel/hstore.rb +36 -0
- data/lib/mobility/backend/sequel/hstore/query_methods.rb +42 -0
- data/lib/mobility/backend/sequel/jsonb.rb +43 -0
- data/lib/mobility/backend/sequel/jsonb/query_methods.rb +42 -0
- data/lib/mobility/backend/sequel/key_value.rb +139 -0
- data/lib/mobility/backend/sequel/key_value/query_methods.rb +48 -0
- data/lib/mobility/backend/sequel/query_methods.rb +22 -0
- data/lib/mobility/backend/sequel/serialized.rb +133 -0
- data/lib/mobility/backend/sequel/serialized/query_methods.rb +20 -0
- data/lib/mobility/backend/sequel/table.rb +149 -0
- data/lib/mobility/backend/sequel/table/query_methods.rb +48 -0
- data/lib/mobility/backend/serialized.rb +53 -0
- data/lib/mobility/backend/table.rb +93 -0
- data/lib/mobility/backend_resetter.rb +44 -0
- data/lib/mobility/configuration.rb +31 -0
- data/lib/mobility/core_ext/nil.rb +10 -0
- data/lib/mobility/core_ext/object.rb +19 -0
- data/lib/mobility/core_ext/string.rb +16 -0
- data/lib/mobility/instance_methods.rb +34 -0
- data/lib/mobility/orm.rb +4 -0
- data/lib/mobility/sequel.rb +26 -0
- data/lib/mobility/sequel/backend_resetter.rb +26 -0
- data/lib/mobility/sequel/column_changes.rb +29 -0
- data/lib/mobility/sequel/model_translation.rb +20 -0
- data/lib/mobility/sequel/string_translation.rb +7 -0
- data/lib/mobility/sequel/text_translation.rb +7 -0
- data/lib/mobility/sequel/translation.rb +53 -0
- data/lib/mobility/translates.rb +75 -0
- data/lib/mobility/wrapper.rb +31 -0
- metadata +152 -12
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.travis.yml +0 -5
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/mobility.gemspec +0 -32
@@ -0,0 +1,14 @@
|
|
1
|
+
module Mobility
|
2
|
+
module ActiveRecord
|
3
|
+
=begin
|
4
|
+
|
5
|
+
Subclassed dynamically to generate translation class in
|
6
|
+
{Backend::ActiveRecord::Table} backend.
|
7
|
+
|
8
|
+
=end
|
9
|
+
class ModelTranslation < ::ActiveRecord::Base
|
10
|
+
self.abstract_class = true
|
11
|
+
validates :locale, presence: true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Mobility
|
2
|
+
module ActiveRecord
|
3
|
+
# @abstract Subclass and set +table_name+ to implement for a particular column type.
|
4
|
+
class Translation < ::ActiveRecord::Base
|
5
|
+
self.abstract_class = true
|
6
|
+
|
7
|
+
belongs_to :translatable, polymorphic: true
|
8
|
+
|
9
|
+
validates :key, presence: true, uniqueness: { scope: [:translatable_id, :translatable_type, :locale] }
|
10
|
+
validates :translatable, presence: true
|
11
|
+
validates :locale, presence: true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module Mobility
|
2
|
+
=begin
|
3
|
+
|
4
|
+
Defines accessor methods to include on model class. Inspired by Traco's
|
5
|
+
+Traco::Attributes+ class.
|
6
|
+
|
7
|
+
Normally this class will be created through class methods defined using
|
8
|
+
{Mobility::Translates} accessor methods, and need not be created directly.
|
9
|
+
However, the class is central to how Mobility hooks into models to add
|
10
|
+
accessors and other methods, and should be useful as a reference when
|
11
|
+
understanding and designing backends.
|
12
|
+
|
13
|
+
==Including Attributes in a Class
|
14
|
+
|
15
|
+
Since {Attributes} is a subclass of +Module+, including an instance of it is
|
16
|
+
like including a module. Creating an instance like this:
|
17
|
+
|
18
|
+
Attributes.new(:accessor, ["title"], backend: :my_backend, locale_accessors: [:en, :ja], cache: true, fallbacks: true)
|
19
|
+
|
20
|
+
will generate an anonymous module looking something like this:
|
21
|
+
|
22
|
+
Module.new do
|
23
|
+
def title_backend
|
24
|
+
# Create a subclass of Mobility::Backend::MyBackend and include in it:
|
25
|
+
# - Mobility::Cache (from the cache: true option)
|
26
|
+
# - Mobility::Fallbacks (from the fallbacks: true option)
|
27
|
+
# Then instantiate the backend, memoize it, and return it.
|
28
|
+
end
|
29
|
+
|
30
|
+
def title(**options)
|
31
|
+
title_backend.read(Mobility.locale, **options).presence
|
32
|
+
end
|
33
|
+
|
34
|
+
def title?(**options)
|
35
|
+
title_backend.read(Mobility.locale, **options).present?
|
36
|
+
end
|
37
|
+
|
38
|
+
def title=(value)
|
39
|
+
title_backend.write(Mobility.locale, value.presence)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Start Locale Accessors
|
43
|
+
#
|
44
|
+
def title_en(**options)
|
45
|
+
title_backend.read(:en, **options).presence
|
46
|
+
end
|
47
|
+
|
48
|
+
def title_en?(**options)
|
49
|
+
title_backend.read(:en, **options).present?
|
50
|
+
end
|
51
|
+
|
52
|
+
def title_en=(value)
|
53
|
+
title_backend.write(:en, value.presence)
|
54
|
+
end
|
55
|
+
|
56
|
+
def title_ja(**options)
|
57
|
+
title_backend.read(:ja, **options).presence
|
58
|
+
end
|
59
|
+
|
60
|
+
def title_ja?(**options)
|
61
|
+
title_backend.read(:ja, **options).present?
|
62
|
+
end
|
63
|
+
|
64
|
+
def title_ja=(value)
|
65
|
+
title_backend.write(:ja, value.presence)
|
66
|
+
end
|
67
|
+
# End Locale Accessors
|
68
|
+
end
|
69
|
+
|
70
|
+
Including this module into a model class will then add the backend method, the
|
71
|
+
reader, writer and presence methods, and the locale accessor so the model
|
72
|
+
class.
|
73
|
+
|
74
|
+
==Setting up the Model Class
|
75
|
+
|
76
|
+
Accessor methods alone are of limited use without a hook to actually modify the
|
77
|
+
model class. This hook is provided by the {Backend::Setup#setup_model} method,
|
78
|
+
which is added to every backend class when it includes the {Backend} module.
|
79
|
+
|
80
|
+
Assuming the backend has defined a setup block by calling +setup+, this block
|
81
|
+
will be called when {Attributes} is {#included} in the model class, passed
|
82
|
+
attributes and options defined when the backend was defined on the model class.
|
83
|
+
This allows a backend to do things like (for example) define associations on a
|
84
|
+
model class required by the backend, as happens in the {Backend::KeyValue} and
|
85
|
+
{Backend::Table} backends.
|
86
|
+
|
87
|
+
The +setup+ block is also used to extend the +i18n+ scope/dataset with
|
88
|
+
backend-specific query method support.
|
89
|
+
|
90
|
+
Since setup blocks are evaluated on the model class, it is possible that
|
91
|
+
backends can conflict (for example, overwriting previously defined methods).
|
92
|
+
Care should be taken to avoid defining methods on the model class, or where
|
93
|
+
necessary, ensure that names are defined in such a way as to avoid conflicts
|
94
|
+
with other backends.
|
95
|
+
|
96
|
+
=end
|
97
|
+
class Attributes < Module
|
98
|
+
# Attributes for which accessors will be defined
|
99
|
+
# @return [Array<String>] Array of attributes
|
100
|
+
attr_reader :attributes
|
101
|
+
|
102
|
+
# Backend options
|
103
|
+
# @return [Hash] Backend options
|
104
|
+
attr_reader :options
|
105
|
+
|
106
|
+
# Backend class
|
107
|
+
# @return [Class] Backend class
|
108
|
+
attr_reader :backend_class
|
109
|
+
|
110
|
+
# Name of backend
|
111
|
+
# @return [Symbol,Class] Name of backend, or backend class
|
112
|
+
attr_reader :backend_name
|
113
|
+
|
114
|
+
# @param [Symbol] method One of: [reader, writer, accessor]
|
115
|
+
# @param [Array<String>] _attributes Attributes to define backend for
|
116
|
+
# @param [Hash] _options Backend options hash
|
117
|
+
# @option _options [Class] model_class Class of model
|
118
|
+
# @option _options [Boolean, Array<Symbol>] locale_accessors Enable locale
|
119
|
+
# accessors or specify locales for which accessors should be defined on
|
120
|
+
# this model backend. Will default to +true+ if +dirty+ option is +true+.
|
121
|
+
# @option _options [Boolean] cache (true) Enable cache for this model backend
|
122
|
+
# @option _options [Boolean, Hash] fallbacks Enable fallbacks or specify fallbacks for this model backend
|
123
|
+
# @option _options [Boolean] dirty Enable dirty tracking for this model backend
|
124
|
+
# @raise [ArgumentError] if method is not reader, writer or accessor
|
125
|
+
def initialize(method, *_attributes, **_options)
|
126
|
+
raise ArgumentError, "method must be one of: reader, writer, accessor" unless %i[reader writer accessor].include?(method)
|
127
|
+
@options = _options
|
128
|
+
@attributes = _attributes.map &:to_s
|
129
|
+
model_class = options[:model_class]
|
130
|
+
@backend_name = options.delete(:backend) || Mobility.config.default_backend
|
131
|
+
@backend_class = Class.new(get_backend_class(backend: @backend_name,
|
132
|
+
model_class: model_class))
|
133
|
+
|
134
|
+
options[:locale_accessors] ||= true if options[:dirty]
|
135
|
+
|
136
|
+
@backend_class.configure!(options) if @backend_class.respond_to?(:configure!)
|
137
|
+
|
138
|
+
@backend_class.include Backend::Cache unless options[:cache] == false
|
139
|
+
@backend_class.include Backend::Dirty.for(model_class) if options[:dirty]
|
140
|
+
@backend_class.include Backend::Fallbacks if options[:fallbacks]
|
141
|
+
@accessor_locales = options[:locale_accessors]
|
142
|
+
@accessor_locales = Mobility.config.default_accessor_locales if options[:locale_accessors] == true
|
143
|
+
|
144
|
+
attributes.each do |attribute|
|
145
|
+
define_backend(attribute)
|
146
|
+
|
147
|
+
if %i[accessor reader].include?(method)
|
148
|
+
define_method attribute do |**options|
|
149
|
+
mobility_get(attribute, options)
|
150
|
+
end
|
151
|
+
|
152
|
+
define_method "#{attribute}?" do |**options|
|
153
|
+
mobility_present?(attribute, options)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
define_method "#{attribute}=" do |value|
|
158
|
+
mobility_set(attribute, value)
|
159
|
+
end if %i[accessor writer].include?(method)
|
160
|
+
|
161
|
+
define_locale_accessors(attribute, @accessor_locales) if @accessor_locales
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Add this attributes module to shared {Mobility::Wrapper} and setup model
|
166
|
+
# with backend setup block (see {Mobility::Backend::Setup#setup_model}).
|
167
|
+
# @param model_class [Class] Class of model
|
168
|
+
def included(model_class)
|
169
|
+
model_class.mobility << self
|
170
|
+
backend_class.setup_model(model_class, attributes, options)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Yield each attribute to block
|
174
|
+
# @yield [String] Attribute
|
175
|
+
def each &block
|
176
|
+
attributes.each &block
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def define_backend(attribute)
|
182
|
+
_backend_class, _options = backend_class, options
|
183
|
+
define_method Backend.method_name(attribute) do
|
184
|
+
@mobility_backends ||= {}
|
185
|
+
@mobility_backends[attribute] ||= _backend_class.new(self, attribute, _options)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def define_locale_accessors(attribute, locales)
|
190
|
+
locales.each do |locale|
|
191
|
+
normalized_locale = Mobility.normalize_locale(locale)
|
192
|
+
define_method "#{attribute}_#{normalized_locale}" do |**options|
|
193
|
+
mobility_get(attribute, options.merge(locale: locale))
|
194
|
+
end
|
195
|
+
define_method "#{attribute}_#{normalized_locale}?" do |**options|
|
196
|
+
mobility_present?(attribute, options.merge(locale: locale))
|
197
|
+
end
|
198
|
+
define_method "#{attribute}_#{normalized_locale}=" do |value, **options|
|
199
|
+
mobility_set(attribute, value, locale: locale)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_backend_class(backend: nil, model_class: nil)
|
205
|
+
raise Mobility::BackendRequired, "Backend option required if Mobility.config.default_backend is not set." if backend.nil?
|
206
|
+
klass = Module === backend ? backend : Mobility::Backend.const_get(backend.to_s.camelize.gsub(/\s+/, ''))
|
207
|
+
model_class.nil? ? klass : klass.for(model_class)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Mobility
|
2
|
+
=begin
|
3
|
+
|
4
|
+
Defines a minimum set of shared components included in any backend. These are:
|
5
|
+
|
6
|
+
- a reader returning the +model+ on which the backend is defined ({#model})
|
7
|
+
- a reader returning the +attribute+ for which the backend is defined
|
8
|
+
({#attribute})
|
9
|
+
- a reader returning +options+ configuring the backend ({#options})
|
10
|
+
- a constructor setting these three elements (+model+, +attribute+, +options+),
|
11
|
+
and extracting fallbacks from the options hash ({#initialize})
|
12
|
+
- a +setup+ method adding any configuration code to the model class
|
13
|
+
({Setup#setup})
|
14
|
+
|
15
|
+
On top of this, a backend will normally:
|
16
|
+
|
17
|
+
- implement a +read+ instance method to read from the backend
|
18
|
+
- implement a +write+ instance method to write to the backend
|
19
|
+
- implement a +configure!+ class method to apply any normalization to the
|
20
|
+
options hash
|
21
|
+
- call the +setup+ method yielding attributes and options to configure the
|
22
|
+
model class
|
23
|
+
|
24
|
+
@example Defining a Backend
|
25
|
+
class MyBackend
|
26
|
+
include Backend
|
27
|
+
|
28
|
+
def read(locale, **options)
|
29
|
+
# ...
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(locale, value, **options)
|
33
|
+
# ...
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.configure!(options)
|
37
|
+
# ...
|
38
|
+
end
|
39
|
+
|
40
|
+
setup do |attributes, options|
|
41
|
+
# Do something with attributes and options in context of model class.
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
@see Mobility::Attributes
|
46
|
+
|
47
|
+
=end
|
48
|
+
|
49
|
+
module Backend
|
50
|
+
autoload :ActiveModel, 'mobility/backend/active_model'
|
51
|
+
autoload :ActiveRecord, 'mobility/backend/active_record'
|
52
|
+
autoload :Cache, 'mobility/backend/cache'
|
53
|
+
autoload :Column, 'mobility/backend/column'
|
54
|
+
autoload :Dirty, 'mobility/backend/dirty'
|
55
|
+
autoload :Fallbacks, 'mobility/backend/fallbacks'
|
56
|
+
autoload :Hstore, 'mobility/backend/hstore'
|
57
|
+
autoload :Jsonb, 'mobility/backend/jsonb'
|
58
|
+
autoload :KeyValue, 'mobility/backend/key_value'
|
59
|
+
autoload :Null, 'mobility/backend/null'
|
60
|
+
autoload :OrmDelegator, 'mobility/backend/orm_delegator'
|
61
|
+
autoload :Sequel, 'mobility/backend/sequel'
|
62
|
+
autoload :Serialized, 'mobility/backend/serialized'
|
63
|
+
autoload :Table, 'mobility/backend/table'
|
64
|
+
|
65
|
+
# @return [String] Backend attribute
|
66
|
+
attr_reader :attribute
|
67
|
+
|
68
|
+
# @return [Object] Model on which backend is defined
|
69
|
+
attr_reader :model
|
70
|
+
|
71
|
+
# @return [Hash] Backend options
|
72
|
+
attr_reader :options
|
73
|
+
|
74
|
+
# @!macro [new] backend_constructor
|
75
|
+
# @param model Model on which backend is defined
|
76
|
+
# @param [String] attribute Backend attribute
|
77
|
+
# @option options [Hash] fallbacks Fallbacks hash
|
78
|
+
def initialize(model, attribute, **options)
|
79
|
+
@model = model
|
80
|
+
@attribute = attribute
|
81
|
+
@options = options
|
82
|
+
fallbacks = options[:fallbacks]
|
83
|
+
@fallbacks = I18n::Locale::Fallbacks.new(fallbacks) if fallbacks.is_a?(Hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!macro [new] backend_reader
|
87
|
+
# @param [Symbol] locale Locale to read
|
88
|
+
# @param [Hash] options
|
89
|
+
# @return [Object] Value of translation
|
90
|
+
#
|
91
|
+
# @!macro [new] backend_writer
|
92
|
+
# @param [Symbol] locale Locale to write
|
93
|
+
# @param [Object] value Value to write
|
94
|
+
# @param [Hash] options
|
95
|
+
# @return [Object] Updated value
|
96
|
+
|
97
|
+
# Extend included class with +setup+ method
|
98
|
+
def self.included(base)
|
99
|
+
base.extend(Setup)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param [String] attribute
|
103
|
+
# @return [String] name of backend reader method
|
104
|
+
def self.method_name(attribute)
|
105
|
+
"#{attribute}_backend"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Defines setup hooks for backend to customize model class.
|
109
|
+
module Setup
|
110
|
+
# Assign block to be called on model class.
|
111
|
+
# @yield [attributes, options]
|
112
|
+
# @note When called multiple times, setup blocks will be appended
|
113
|
+
# so that they are run together consecutively on class.
|
114
|
+
def setup &block
|
115
|
+
if @setup_block
|
116
|
+
setup_block = @setup_block
|
117
|
+
@setup_block = lambda do |*args|
|
118
|
+
class_exec(*args, &setup_block)
|
119
|
+
class_exec(*args, &block)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
@setup_block = block
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def inherited(subclass)
|
127
|
+
subclass.instance_variable_set(:@setup_block, @setup_block)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Call setup block on a class with attributes and options.
|
131
|
+
# @param model_class Class to be setup-ed
|
132
|
+
# @param [Array<String>] attributes
|
133
|
+
# @param [Hash] options
|
134
|
+
def setup_model(model_class, attributes, **options)
|
135
|
+
return unless setup_block = @setup_block
|
136
|
+
model_class.class_exec(attributes, options, &setup_block)
|
137
|
+
end
|
138
|
+
|
139
|
+
# {Attributes} uses this method to get a backend class specific to the
|
140
|
+
# model using the backend. Backend classes can override this method to
|
141
|
+
# return a class specific to the model class using the backend (e.g.
|
142
|
+
# either an ActiveRecord or Sequel backend class depending on whether the
|
143
|
+
# model is an ActiveRecord model or a Sequel model.)
|
144
|
+
# @see OrmDelegator
|
145
|
+
# @see Attributes
|
146
|
+
# @return [self] returns itself
|
147
|
+
def for(_)
|
148
|
+
self
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Mobility
|
2
|
+
module Backend
|
3
|
+
=begin
|
4
|
+
|
5
|
+
Dirty tracking for models which include the +ActiveModel::Dirty+ module.
|
6
|
+
|
7
|
+
Assuming we have an attribute +title+, this module will add support for the
|
8
|
+
following methods:
|
9
|
+
- +title_changed?+
|
10
|
+
- +title_change+
|
11
|
+
- +title_was+
|
12
|
+
- +title_will_change!+
|
13
|
+
- +title_previously_changed?+
|
14
|
+
- +title_previous_change+
|
15
|
+
- +restore_title!+
|
16
|
+
|
17
|
+
In addition, the private method +restore_attribute!+ will also restore the
|
18
|
+
value of the translated attribute if passed to it.
|
19
|
+
|
20
|
+
@see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html Rails documentation for Active Model Dirty module
|
21
|
+
|
22
|
+
=end
|
23
|
+
module ActiveModel::Dirty
|
24
|
+
# @!group Backend Accessors
|
25
|
+
# @!macro backend_writer
|
26
|
+
def write(locale, value, **options)
|
27
|
+
locale_accessor = "#{attribute}_#{locale}"
|
28
|
+
if model.changed_attributes.has_key?(locale_accessor) && model.changed_attributes[locale_accessor] == value
|
29
|
+
model.attributes_changed_by_setter.except!(locale_accessor)
|
30
|
+
else
|
31
|
+
model.send(:attribute_will_change!, "#{attribute}_#{locale}")
|
32
|
+
end
|
33
|
+
super
|
34
|
+
end
|
35
|
+
# @!endgroup
|
36
|
+
|
37
|
+
# @param [Class] backend_class Class of backend
|
38
|
+
def self.included(backend_class)
|
39
|
+
backend_class.extend(ClassMethods)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds hook after {Backend::Setup#setup_model} to add dirty-tracking
|
43
|
+
# methods for translated attributes onto model class.
|
44
|
+
module ClassMethods
|
45
|
+
# (see Mobility::Backend::Setup#setup_model)
|
46
|
+
def setup_model(model_class, attributes, **options)
|
47
|
+
super
|
48
|
+
model_class.class_eval do
|
49
|
+
%w[changed? change was will_change! previously_changed? previous_change].each do |suffix|
|
50
|
+
attributes.each do |attribute|
|
51
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
52
|
+
def #{attribute}_#{suffix}
|
53
|
+
attribute_#{suffix}("#{attribute}_#\{Mobility.locale\}")
|
54
|
+
end
|
55
|
+
EOM
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
restore_methods = Module.new do
|
61
|
+
attributes.each do |attribute|
|
62
|
+
locale_accessor = "#{attribute}_#{Mobility.locale}"
|
63
|
+
define_method "restore_#{attribute}!" do
|
64
|
+
if attribute_changed?(locale_accessor)
|
65
|
+
__send__("#{attribute}=", changed_attributes[locale_accessor])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
define_method :restore_attribute! do |attr|
|
71
|
+
if attributes.include?(attr.to_s)
|
72
|
+
send("restore_#{attr}!")
|
73
|
+
else
|
74
|
+
super(attr)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
private :restore_attribute!
|
78
|
+
end
|
79
|
+
model_class.include restore_methods
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|