mobility 0.8.10 → 1.0.0.beta2
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +66 -0
- data/Gemfile +50 -18
- data/Gemfile.lock +36 -101
- data/README.md +183 -91
- data/Rakefile +6 -4
- data/lib/mobility.rb +44 -166
- data/lib/mobility/arel.rb +1 -1
- data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
- data/lib/mobility/backend.rb +27 -51
- data/lib/mobility/backends.rb +20 -0
- data/lib/mobility/backends/active_record.rb +4 -0
- data/lib/mobility/backends/active_record/column.rb +2 -0
- data/lib/mobility/backends/active_record/container.rb +6 -7
- data/lib/mobility/backends/active_record/hstore.rb +3 -1
- data/lib/mobility/backends/active_record/json.rb +2 -0
- data/lib/mobility/backends/active_record/jsonb.rb +2 -0
- data/lib/mobility/backends/active_record/key_value.rb +6 -4
- data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
- data/lib/mobility/backends/active_record/serialized.rb +6 -0
- data/lib/mobility/backends/active_record/table.rb +6 -4
- data/lib/mobility/backends/column.rb +0 -6
- data/lib/mobility/backends/container.rb +10 -1
- data/lib/mobility/backends/hash.rb +39 -0
- data/lib/mobility/backends/hash_valued.rb +4 -0
- data/lib/mobility/backends/hstore.rb +0 -1
- data/lib/mobility/backends/json.rb +0 -1
- data/lib/mobility/backends/jsonb.rb +1 -2
- data/lib/mobility/backends/key_value.rb +31 -26
- data/lib/mobility/backends/null.rb +2 -0
- data/lib/mobility/backends/sequel.rb +5 -2
- data/lib/mobility/backends/sequel/column.rb +2 -0
- data/lib/mobility/backends/sequel/container.rb +6 -6
- data/lib/mobility/backends/sequel/hstore.rb +3 -1
- data/lib/mobility/backends/sequel/json.rb +3 -0
- data/lib/mobility/backends/sequel/jsonb.rb +3 -1
- data/lib/mobility/backends/sequel/key_value.rb +8 -6
- data/lib/mobility/backends/sequel/serialized.rb +6 -0
- data/lib/mobility/backends/sequel/table.rb +5 -2
- data/lib/mobility/backends/serialized.rb +1 -3
- data/lib/mobility/backends/table.rb +29 -26
- data/lib/mobility/pluggable.rb +56 -0
- data/lib/mobility/plugin.rb +260 -0
- data/lib/mobility/plugins.rb +27 -24
- data/lib/mobility/plugins/active_model.rb +17 -0
- data/lib/mobility/plugins/active_model/cache.rb +26 -0
- data/lib/mobility/plugins/active_model/dirty.rb +119 -78
- data/lib/mobility/plugins/active_record.rb +34 -0
- data/lib/mobility/plugins/active_record/backend.rb +25 -0
- data/lib/mobility/plugins/active_record/cache.rb +28 -0
- data/lib/mobility/plugins/active_record/dirty.rb +34 -17
- data/lib/mobility/plugins/active_record/query.rb +48 -34
- data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
- data/lib/mobility/plugins/attribute_methods.rb +29 -20
- data/lib/mobility/plugins/attributes.rb +72 -0
- data/lib/mobility/plugins/backend.rb +161 -0
- data/lib/mobility/plugins/backend_reader.rb +34 -0
- data/lib/mobility/plugins/cache.rb +68 -26
- data/lib/mobility/plugins/default.rb +22 -17
- data/lib/mobility/plugins/dirty.rb +12 -33
- data/lib/mobility/plugins/fallbacks.rb +52 -44
- data/lib/mobility/plugins/fallthrough_accessors.rb +25 -25
- data/lib/mobility/plugins/locale_accessors.rb +22 -35
- data/lib/mobility/plugins/presence.rb +28 -21
- data/lib/mobility/plugins/query.rb +8 -17
- data/lib/mobility/plugins/reader.rb +50 -0
- data/lib/mobility/plugins/sequel.rb +34 -0
- data/lib/mobility/plugins/sequel/backend.rb +25 -0
- data/lib/mobility/plugins/sequel/cache.rb +24 -0
- data/lib/mobility/plugins/sequel/dirty.rb +33 -22
- data/lib/mobility/plugins/sequel/query.rb +21 -6
- data/lib/mobility/plugins/writer.rb +44 -0
- data/lib/mobility/translations.rb +95 -0
- data/lib/mobility/version.rb +12 -1
- data/lib/rails/generators/mobility/templates/initializer.rb +96 -78
- metadata +28 -27
- metadata.gz.sig +0 -0
- data/lib/mobility/active_model.rb +0 -4
- data/lib/mobility/active_model/backend_resetter.rb +0 -26
- data/lib/mobility/active_record.rb +0 -23
- data/lib/mobility/active_record/backend_resetter.rb +0 -26
- data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
- data/lib/mobility/attributes.rb +0 -324
- data/lib/mobility/backend/orm_delegator.rb +0 -44
- data/lib/mobility/backend_resetter.rb +0 -50
- data/lib/mobility/configuration.rb +0 -138
- data/lib/mobility/fallbacks.rb +0 -28
- data/lib/mobility/interface.rb +0 -0
- data/lib/mobility/loaded.rb +0 -4
- data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
- data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
- data/lib/mobility/sequel.rb +0 -9
- data/lib/mobility/sequel/backend_resetter.rb +0 -23
- data/lib/mobility/translates.rb +0 -73
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
module Mobility
|
|
3
|
+
module Plugins
|
|
4
|
+
=begin
|
|
5
|
+
|
|
6
|
+
Defines convenience methods for accessing backends, of the form
|
|
7
|
+
"<name>_backend". The format for this method can be customized by passing a
|
|
8
|
+
different format string as the plugin option.
|
|
9
|
+
|
|
10
|
+
=end
|
|
11
|
+
module BackendReader
|
|
12
|
+
extend Plugin
|
|
13
|
+
|
|
14
|
+
default true
|
|
15
|
+
requires :backend
|
|
16
|
+
|
|
17
|
+
initialize_hook do |*names|
|
|
18
|
+
if backend_reader = options[:backend_reader]
|
|
19
|
+
backend_reader = "%s_backend" if backend_reader == true
|
|
20
|
+
|
|
21
|
+
names.each do |name|
|
|
22
|
+
module_eval <<-EOM, __FILE__, __LINE__ + 1
|
|
23
|
+
def #{backend_reader % name}
|
|
24
|
+
mobility_backends[:#{name}]
|
|
25
|
+
end
|
|
26
|
+
EOM
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
register_plugin(:backend_reader, BackendReader)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require "mobility/plugins/cache/translation_cacher"
|
|
3
2
|
|
|
4
3
|
module Mobility
|
|
5
4
|
module Plugins
|
|
@@ -8,8 +7,7 @@ module Mobility
|
|
|
8
7
|
Caches values fetched from the backend so subsequent fetches can be performed
|
|
9
8
|
more quickly. The cache stores cached values in a simple hash, which is not
|
|
10
9
|
optimal for some storage strategies, so some backends (KeyValue, Table) use a
|
|
11
|
-
custom module
|
|
12
|
-
details see the documentation for these backends.
|
|
10
|
+
custom module by defining a method, +include_cache+, on the backend class.
|
|
13
11
|
|
|
14
12
|
The cache is reset when one of a set of events happens (saving, reloading,
|
|
15
13
|
etc.). See {BackendResetter} for details.
|
|
@@ -21,34 +19,78 @@ Values are added to the cache in two ways:
|
|
|
21
19
|
|
|
22
20
|
=end
|
|
23
21
|
module Cache
|
|
22
|
+
extend Plugin
|
|
23
|
+
|
|
24
|
+
default true
|
|
25
|
+
requires :backend, include: :before
|
|
26
|
+
|
|
24
27
|
# Applies cache plugin to attributes.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
model_class = attributes.model_class
|
|
33
|
-
model_class.include BackendResetter.for(model_class).new(attributes.names) { clear_cache }
|
|
28
|
+
included_hook do |_, backend_class|
|
|
29
|
+
if options[:cache]
|
|
30
|
+
if backend_class.respond_to?(:include_cache)
|
|
31
|
+
backend_class.include_cache
|
|
32
|
+
else
|
|
33
|
+
include_cache(backend_class)
|
|
34
|
+
end
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def include_cache(backend_class)
|
|
41
|
+
backend_class.include BackendMethods
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Used in ORM cache plugins
|
|
45
|
+
def define_cache_hooks(klass, *reset_methods)
|
|
46
|
+
mod = self
|
|
47
|
+
private_methods = reset_methods & klass.private_instance_methods
|
|
48
|
+
reset_methods.each do |method_name|
|
|
49
|
+
define_method method_name do |*args|
|
|
50
|
+
super(*args).tap do
|
|
51
|
+
mod.names.each { |name| mobility_backends[name].clear_cache }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
klass.class_eval { private(*private_methods) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module BackendMethods
|
|
59
|
+
# @group Backend Accessors
|
|
60
|
+
#
|
|
61
|
+
# @!macro backend_reader
|
|
62
|
+
# @!method read(locale, value, options = {})
|
|
63
|
+
# @option options [Boolean] cache *false* to disable cache.
|
|
64
|
+
def read(locale, **options)
|
|
65
|
+
return super(locale, **options) if options.delete(:cache) == false
|
|
66
|
+
if cache.has_key?(locale)
|
|
67
|
+
cache[locale]
|
|
68
|
+
else
|
|
69
|
+
cache[locale] = super(locale, **options)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @!macro backend_writer
|
|
74
|
+
# @option options [Boolean] cache
|
|
75
|
+
# *false* to disable cache.
|
|
76
|
+
def write(locale, value, **options)
|
|
77
|
+
return super if options.delete(:cache) == false
|
|
78
|
+
cache[locale] = super
|
|
79
|
+
end
|
|
80
|
+
# @!endgroup
|
|
81
|
+
|
|
82
|
+
def clear_cache
|
|
83
|
+
@cache = {}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def cache
|
|
89
|
+
@cache ||= {}
|
|
90
|
+
end
|
|
50
91
|
end
|
|
51
|
-
# @!endgroup
|
|
52
92
|
end
|
|
93
|
+
|
|
94
|
+
register_plugin(:cache, Cache)
|
|
53
95
|
end
|
|
54
96
|
end
|
|
@@ -6,8 +6,7 @@ module Mobility
|
|
|
6
6
|
|
|
7
7
|
Defines value or proc to fall through to if return value from getter would
|
|
8
8
|
otherwise be nil. This plugin is disabled by default but will be enabled if any
|
|
9
|
-
value
|
|
10
|
-
option key.
|
|
9
|
+
value is passed as the +default+ option key.
|
|
11
10
|
|
|
12
11
|
If default is a +Proc+, it will be called with the context of the model, and
|
|
13
12
|
passed arguments:
|
|
@@ -63,11 +62,13 @@ The proc can accept zero to three arguments (see examples below)
|
|
|
63
62
|
#=> "Post"
|
|
64
63
|
=end
|
|
65
64
|
module Default
|
|
65
|
+
extend Plugin
|
|
66
|
+
|
|
67
|
+
requires :backend, include: :before
|
|
68
|
+
|
|
66
69
|
# Applies default plugin to attributes.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def self.apply(attributes, option)
|
|
70
|
-
attributes.backend_class.include(self) unless option == Plugins::OPTION_UNSET
|
|
70
|
+
included_hook do |_klass, backend_class|
|
|
71
|
+
backend_class.include(BackendMethods)
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
# Generate a default value for given parameters.
|
|
@@ -82,19 +83,23 @@ The proc can accept zero to three arguments (see examples below)
|
|
|
82
83
|
model.instance_exec(*args, &default_value)
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
module BackendMethods
|
|
87
|
+
# @!group Backend Accessors
|
|
88
|
+
# @!macro backend_reader
|
|
89
|
+
# @option accessor_options [Boolean] default
|
|
90
|
+
# *false* to disable presence filter.
|
|
91
|
+
def read(locale, accessor_options = {})
|
|
92
|
+
default = accessor_options.has_key?(:default) ? accessor_options.delete(:default) : options[:default]
|
|
93
|
+
if (value = super(locale, **accessor_options)).nil?
|
|
94
|
+
Default[default, locale: locale, accessor_options: accessor_options, model: model, attribute: attribute]
|
|
95
|
+
else
|
|
96
|
+
value
|
|
97
|
+
end
|
|
95
98
|
end
|
|
99
|
+
# @!endgroup
|
|
96
100
|
end
|
|
97
|
-
# @!endgroup
|
|
98
101
|
end
|
|
102
|
+
|
|
103
|
+
register_plugin(:default, Default)
|
|
99
104
|
end
|
|
100
105
|
end
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require "mobility/backend_resetter"
|
|
3
|
-
require "mobility/plugins/fallthrough_accessors"
|
|
4
2
|
|
|
5
3
|
module Mobility
|
|
6
4
|
module Plugins
|
|
@@ -20,40 +18,21 @@ details.
|
|
|
20
18
|
|
|
21
19
|
=end
|
|
22
20
|
module Dirty
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# @param [Boolean] option Value of option
|
|
27
|
-
# @raise [ArgumentError] if model class does not support dirty tracking
|
|
28
|
-
def apply(attributes, option)
|
|
29
|
-
if option
|
|
30
|
-
FallthroughAccessors.apply(attributes, true)
|
|
31
|
-
include_dirty_module(attributes.backend_class, attributes.model_class, *attributes.names)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
21
|
+
extend Plugin
|
|
22
|
+
|
|
23
|
+
default true
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Plugins::ActiveRecord::Dirty
|
|
43
|
-
else
|
|
44
|
-
require "mobility/plugins/active_model/dirty"
|
|
45
|
-
Plugins::ActiveModel::Dirty
|
|
46
|
-
end
|
|
47
|
-
elsif Loaded::Sequel && model_class < ::Sequel::Model
|
|
48
|
-
require "mobility/plugins/sequel/dirty"
|
|
49
|
-
Plugins::Sequel::Dirty
|
|
50
|
-
else
|
|
51
|
-
raise ArgumentError, "#{model_class} does not support Dirty module."
|
|
52
|
-
end
|
|
53
|
-
backend_class.include dirty_module.const_get(:BackendMethods)
|
|
54
|
-
model_class.include dirty_module.const_get(:MethodsBuilder).new(*attribute_names)
|
|
25
|
+
requires :backend, include: :before
|
|
26
|
+
requires :fallthrough_accessors
|
|
27
|
+
|
|
28
|
+
initialize_hook do
|
|
29
|
+
if options[:dirty] && !options[:fallthrough_accessors]
|
|
30
|
+
warn 'The Dirty plugin depends on Fallthrough Accessors being enabled, '\
|
|
31
|
+
'but fallthrough_accessors option is falsey'
|
|
55
32
|
end
|
|
56
33
|
end
|
|
57
34
|
end
|
|
35
|
+
|
|
36
|
+
register_plugin(:dirty, Dirty)
|
|
58
37
|
end
|
|
59
38
|
end
|
|
@@ -8,15 +8,14 @@ module Mobility
|
|
|
8
8
|
Falls back to one or more alternative locales in case no value is defined for a
|
|
9
9
|
given locale.
|
|
10
10
|
|
|
11
|
-
For +fallbacks: true+, Mobility will use
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
configured (see {Mobility::Configuration}).
|
|
11
|
+
For +fallbacks: true+, Mobility will use an instance of
|
|
12
|
+
+I18n::Locale::Fallbacks+, but this can be configured by overriding
|
|
13
|
+
+generate_fallbacks+ in the translations class.
|
|
15
14
|
|
|
16
15
|
If a hash is passed to the +fallbacks+ option, a new fallbacks instance will be
|
|
17
16
|
created for the model with the hash defining additional fallbacks. To set a
|
|
18
|
-
default value for this hash,
|
|
19
|
-
|
|
17
|
+
default value for this hash, pass this value to the plugin in your Mobility
|
|
18
|
+
configuration.
|
|
20
19
|
|
|
21
20
|
In addition, fallbacks are disabled in certain situations. To explicitly disable
|
|
22
21
|
fallbacks when reading and writing, you can pass the <tt>fallback: false</tt>
|
|
@@ -48,7 +47,7 @@ the current locale was +nil+.
|
|
|
48
47
|
Mobility.locale = :ja
|
|
49
48
|
post.title
|
|
50
49
|
#=> "foo"
|
|
51
|
-
|
|
50
|
+
|
|
52
51
|
post.title = "bar"
|
|
53
52
|
post.title
|
|
54
53
|
#=> "bar"
|
|
@@ -108,58 +107,67 @@ the current locale was +nil+.
|
|
|
108
107
|
#=> nil
|
|
109
108
|
post.title_fr
|
|
110
109
|
#=> nil
|
|
110
|
+
=end
|
|
111
|
+
module Fallbacks
|
|
112
|
+
extend Plugin
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# ...
|
|
115
|
-
config.default_options[:fallbacks] = { :'fr' => 'en' }
|
|
116
|
-
# ...
|
|
117
|
-
end
|
|
114
|
+
default true
|
|
115
|
+
requires :backend, include: :before
|
|
118
116
|
|
|
119
|
-
class Post
|
|
120
|
-
# Post will fallback from French to English by default
|
|
121
|
-
translates :title, fallbacks: true
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
=end
|
|
125
|
-
class Fallbacks < Module
|
|
126
117
|
# Applies fallbacks plugin to attributes. Completely disables fallbacks
|
|
127
118
|
# on model if option is +false+.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
attributes.backend_class.include(new(option)) unless option == false
|
|
119
|
+
included_hook do |_, backend_class|
|
|
120
|
+
fallbacks = options[:fallbacks]
|
|
121
|
+
backend_class.include(BackendReader.new(fallbacks, method(:generate_fallbacks))) unless fallbacks == false
|
|
132
122
|
end
|
|
133
123
|
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def generate_fallbacks(fallbacks)
|
|
127
|
+
fallbacks_class = I18n.respond_to?(:fallbacks) ? I18nFallbacks : I18n::Locale::Fallbacks
|
|
128
|
+
fallbacks_class.new(fallbacks)
|
|
136
129
|
end
|
|
137
130
|
|
|
138
|
-
|
|
131
|
+
class I18nFallbacks < ::I18n::Locale::Fallbacks
|
|
132
|
+
def [](locale)
|
|
133
|
+
super | I18n.fallbacks[locale]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
139
136
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
class BackendReader < Module
|
|
138
|
+
def initialize(fallbacks_option, fallbacks_generator)
|
|
139
|
+
@fallbacks_generator = fallbacks_generator
|
|
140
|
+
define_read(convert_option_to_fallbacks(fallbacks_option))
|
|
141
|
+
end
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def define_read(fallbacks)
|
|
146
|
+
define_method :read do |locale, fallback: true, **options|
|
|
147
|
+
return super(locale, **options) if !fallback || options[:locale]
|
|
149
148
|
|
|
150
|
-
|
|
149
|
+
locales = fallback == true ? fallbacks[locale] : [locale, *fallback]
|
|
150
|
+
locales.each do |fallback_locale|
|
|
151
|
+
value = super(fallback_locale, **options)
|
|
152
|
+
return value if Util.present?(value)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
super(locale, **options)
|
|
156
|
+
end
|
|
151
157
|
end
|
|
152
|
-
end
|
|
153
158
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
def convert_option_to_fallbacks(option)
|
|
160
|
+
if option.is_a?(::Hash)
|
|
161
|
+
@fallbacks_generator[option]
|
|
162
|
+
elsif option == true
|
|
163
|
+
@fallbacks_generator[{}]
|
|
164
|
+
else
|
|
165
|
+
::Hash.new { [] }
|
|
166
|
+
end
|
|
161
167
|
end
|
|
162
168
|
end
|
|
163
169
|
end
|
|
170
|
+
|
|
171
|
+
register_plugin(:fallbacks, Fallbacks)
|
|
164
172
|
end
|
|
165
173
|
end
|
|
@@ -18,42 +18,40 @@ This is a less efficient (but more open-ended) implementation of locale
|
|
|
18
18
|
accessors, for use in cases where the locales to be used are not known when the
|
|
19
19
|
model class is generated.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"title in #{Mobility.locale}"
|
|
25
|
-
end
|
|
26
|
-
include Mobility::FallthroughAccessors.new("title")
|
|
27
|
-
end
|
|
21
|
+
=end
|
|
22
|
+
module FallthroughAccessors
|
|
23
|
+
extend Plugin
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
post = Post.new
|
|
31
|
-
post.title
|
|
32
|
-
#=> "title in en"
|
|
33
|
-
post.title_fr
|
|
34
|
-
#=> "title in fr"
|
|
25
|
+
default true
|
|
35
26
|
|
|
36
|
-
=end
|
|
37
|
-
class FallthroughAccessors < Module
|
|
38
27
|
# Apply fallthrough accessors plugin to attributes.
|
|
39
|
-
# @param [
|
|
28
|
+
# @param [Translations] translations
|
|
40
29
|
# @param [Boolean] option
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
initialize_hook do
|
|
31
|
+
if options[:fallthrough_accessors]
|
|
32
|
+
define_fallthrough_accessors(names)
|
|
33
|
+
end
|
|
43
34
|
end
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def define_fallthrough_accessors(*names)
|
|
39
|
+
method_name_regex = /\A(#{names.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
|
|
48
40
|
|
|
49
|
-
define_method :method_missing do |method_name, *
|
|
41
|
+
define_method :method_missing do |method_name, *args, &block|
|
|
50
42
|
if method_name =~ method_name_regex
|
|
51
|
-
|
|
43
|
+
attribute_method = "#{$1}#{$4}"
|
|
52
44
|
locale, suffix = $2.split('_')
|
|
53
45
|
locale = "#{locale}-#{suffix.upcase}" if suffix
|
|
54
|
-
|
|
46
|
+
if $4 == '=' # writer
|
|
47
|
+
kwargs = args[1].is_a?(Hash) ? args[1] : {}
|
|
48
|
+
public_send(attribute_method, args[0], **kwargs, locale: locale)
|
|
49
|
+
else # reader
|
|
50
|
+
kwargs = args[0].is_a?(Hash) ? args[0] : {}
|
|
51
|
+
public_send(attribute_method, **kwargs, locale: locale)
|
|
52
|
+
end
|
|
55
53
|
else
|
|
56
|
-
super(method_name, *
|
|
54
|
+
super(method_name, *args, &block)
|
|
57
55
|
end
|
|
58
56
|
end
|
|
59
57
|
|
|
@@ -62,5 +60,7 @@ model class is generated.
|
|
|
62
60
|
end
|
|
63
61
|
end
|
|
64
62
|
end
|
|
63
|
+
|
|
64
|
+
register_plugin :fallthrough_accessors, FallthroughAccessors
|
|
65
65
|
end
|
|
66
66
|
end
|