mobility 0.8.10 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +66 -0
  5. data/Gemfile +50 -18
  6. data/Gemfile.lock +36 -101
  7. data/README.md +183 -91
  8. data/Rakefile +6 -4
  9. data/lib/mobility.rb +44 -166
  10. data/lib/mobility/arel.rb +1 -1
  11. data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
  12. data/lib/mobility/backend.rb +27 -51
  13. data/lib/mobility/backends.rb +20 -0
  14. data/lib/mobility/backends/active_record.rb +4 -0
  15. data/lib/mobility/backends/active_record/column.rb +2 -0
  16. data/lib/mobility/backends/active_record/container.rb +6 -7
  17. data/lib/mobility/backends/active_record/hstore.rb +3 -1
  18. data/lib/mobility/backends/active_record/json.rb +2 -0
  19. data/lib/mobility/backends/active_record/jsonb.rb +2 -0
  20. data/lib/mobility/backends/active_record/key_value.rb +6 -4
  21. data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
  22. data/lib/mobility/backends/active_record/serialized.rb +6 -0
  23. data/lib/mobility/backends/active_record/table.rb +6 -4
  24. data/lib/mobility/backends/column.rb +0 -6
  25. data/lib/mobility/backends/container.rb +10 -1
  26. data/lib/mobility/backends/hash.rb +39 -0
  27. data/lib/mobility/backends/hash_valued.rb +4 -0
  28. data/lib/mobility/backends/hstore.rb +0 -1
  29. data/lib/mobility/backends/json.rb +0 -1
  30. data/lib/mobility/backends/jsonb.rb +1 -2
  31. data/lib/mobility/backends/key_value.rb +31 -26
  32. data/lib/mobility/backends/null.rb +2 -0
  33. data/lib/mobility/backends/sequel.rb +5 -2
  34. data/lib/mobility/backends/sequel/column.rb +2 -0
  35. data/lib/mobility/backends/sequel/container.rb +6 -6
  36. data/lib/mobility/backends/sequel/hstore.rb +3 -1
  37. data/lib/mobility/backends/sequel/json.rb +3 -0
  38. data/lib/mobility/backends/sequel/jsonb.rb +3 -1
  39. data/lib/mobility/backends/sequel/key_value.rb +8 -6
  40. data/lib/mobility/backends/sequel/serialized.rb +6 -0
  41. data/lib/mobility/backends/sequel/table.rb +5 -2
  42. data/lib/mobility/backends/serialized.rb +1 -3
  43. data/lib/mobility/backends/table.rb +29 -26
  44. data/lib/mobility/pluggable.rb +56 -0
  45. data/lib/mobility/plugin.rb +260 -0
  46. data/lib/mobility/plugins.rb +27 -24
  47. data/lib/mobility/plugins/active_model.rb +17 -0
  48. data/lib/mobility/plugins/active_model/cache.rb +26 -0
  49. data/lib/mobility/plugins/active_model/dirty.rb +119 -78
  50. data/lib/mobility/plugins/active_record.rb +34 -0
  51. data/lib/mobility/plugins/active_record/backend.rb +25 -0
  52. data/lib/mobility/plugins/active_record/cache.rb +28 -0
  53. data/lib/mobility/plugins/active_record/dirty.rb +34 -17
  54. data/lib/mobility/plugins/active_record/query.rb +48 -34
  55. data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
  56. data/lib/mobility/plugins/attribute_methods.rb +29 -20
  57. data/lib/mobility/plugins/attributes.rb +72 -0
  58. data/lib/mobility/plugins/backend.rb +161 -0
  59. data/lib/mobility/plugins/backend_reader.rb +34 -0
  60. data/lib/mobility/plugins/cache.rb +68 -26
  61. data/lib/mobility/plugins/default.rb +22 -17
  62. data/lib/mobility/plugins/dirty.rb +12 -33
  63. data/lib/mobility/plugins/fallbacks.rb +52 -44
  64. data/lib/mobility/plugins/fallthrough_accessors.rb +25 -25
  65. data/lib/mobility/plugins/locale_accessors.rb +22 -35
  66. data/lib/mobility/plugins/presence.rb +28 -21
  67. data/lib/mobility/plugins/query.rb +8 -17
  68. data/lib/mobility/plugins/reader.rb +50 -0
  69. data/lib/mobility/plugins/sequel.rb +34 -0
  70. data/lib/mobility/plugins/sequel/backend.rb +25 -0
  71. data/lib/mobility/plugins/sequel/cache.rb +24 -0
  72. data/lib/mobility/plugins/sequel/dirty.rb +33 -22
  73. data/lib/mobility/plugins/sequel/query.rb +21 -6
  74. data/lib/mobility/plugins/writer.rb +44 -0
  75. data/lib/mobility/translations.rb +95 -0
  76. data/lib/mobility/version.rb +12 -1
  77. data/lib/rails/generators/mobility/templates/initializer.rb +96 -78
  78. metadata +28 -27
  79. metadata.gz.sig +0 -0
  80. data/lib/mobility/active_model.rb +0 -4
  81. data/lib/mobility/active_model/backend_resetter.rb +0 -26
  82. data/lib/mobility/active_record.rb +0 -23
  83. data/lib/mobility/active_record/backend_resetter.rb +0 -26
  84. data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
  85. data/lib/mobility/attributes.rb +0 -324
  86. data/lib/mobility/backend/orm_delegator.rb +0 -44
  87. data/lib/mobility/backend_resetter.rb +0 -50
  88. data/lib/mobility/configuration.rb +0 -138
  89. data/lib/mobility/fallbacks.rb +0 -28
  90. data/lib/mobility/interface.rb +0 -0
  91. data/lib/mobility/loaded.rb +0 -4
  92. data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
  93. data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
  94. data/lib/mobility/sequel.rb +0 -9
  95. data/lib/mobility/sequel/backend_resetter.rb +0 -23
  96. data/lib/mobility/translates.rb +0 -73
data/Rakefile CHANGED
@@ -2,7 +2,9 @@ require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
  require "yaml"
4
4
 
5
- RSpec::Core::RakeTask.new(:spec)
5
+ RSpec::Core::RakeTask.new(:spec) do |task|
6
+ task.rspec_opts = '-f p'
7
+ end
6
8
 
7
9
  task :default => :spec
8
10
 
@@ -18,7 +20,7 @@ namespace :db do
18
20
  desc "Create the database"
19
21
  task create: :setup do
20
22
  commands = {
21
- "mysql" => "mysql -u #{config['username']} -e 'create database #{config["database"]} default character set #{config["encoding"]} default collate #{config["collation"]};' >/dev/null",
23
+ "mysql" => "mysql -h #{config['host']} -P #{config['port']} -u #{config['username']} --password=#{config['password']} -e 'create database #{config["database"]} default character set #{config["encoding"]} default collate #{config["collation"]};' >/dev/null",
22
24
  "postgres" => "psql -c 'create database #{config['database']};' -U #{config['username']} >/dev/null"
23
25
  }
24
26
  %x{#{commands[driver] || true}}
@@ -28,7 +30,7 @@ namespace :db do
28
30
  desc "Drop the database"
29
31
  task drop: :setup do
30
32
  commands = {
31
- "mysql" => "mysql -u #{config['username']} -e 'drop database #{config["database"]};' >/dev/null",
33
+ "mysql" => "mysql -h #{config['host']} -P #{config['port']} -u #{config['username']} --password=#{config['password']} -e 'drop database #{config["database"]};' >/dev/null",
32
34
  "postgres" => "psql -c 'drop database #{config['database']};' -U #{config['username']} >/dev/null"
33
35
  }
34
36
  %x{#{commands[driver] || true}}
@@ -42,8 +44,8 @@ namespace :db do
42
44
 
43
45
  require orm
44
46
  require "database"
45
- require "#{orm}/schema"
46
47
  DB = Mobility::Test::Database.connect(orm)
48
+ require "#{orm}/schema"
47
49
  Mobility::Test::Schema.up
48
50
  end
49
51
 
@@ -10,96 +10,36 @@ on a class. The {Mobility} module includes all necessary methods and modules to
10
10
  support defining backend accessors on a class.
11
11
 
12
12
  To enable Mobility on a class, simply include or extend the {Mobility} module,
13
- and define any attribute accessors using {Translates#mobility_accessor} (aliased to the
14
- value of {Mobility.accessor_method}, which defaults to +translates+).
13
+ and define any attribute accessors using {Translates#translates}.
15
14
 
16
15
  class MyClass
17
16
  extend Mobility
18
17
  translates :title, backend: :key_value
19
18
  end
20
19
 
21
- When defining this module, Mobility attempts to +require+ various gems (for
22
- example, +active_record+ and +sequel+) to evaluate which are loaded. Loaded
23
- gems are tracked with dynamic subclasses of the {Loaded} module and referenced
24
- in backends to define gem-dependent behavior.
25
-
26
20
  =end
27
21
  module Mobility
28
- require "mobility/attributes"
22
+ # A generic exception used by Mobility.
23
+ class Error < StandardError
24
+ end
25
+
29
26
  require "mobility/backend"
30
27
  require "mobility/backends"
31
- require "mobility/backend_resetter"
32
- require "mobility/configuration"
33
- require "mobility/fallbacks"
34
- require "mobility/loaded"
28
+ require "mobility/plugin"
35
29
  require "mobility/plugins"
36
- require "mobility/translates"
30
+ require "mobility/translations"
37
31
 
38
32
  # General error for version compatibility conflicts
39
33
  class VersionNotSupportedError < ArgumentError; end
40
34
  CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
41
35
  private_constant :CALL_COMPILABLE_REGEXP
42
36
 
43
- begin
44
- require "rails"
45
- Loaded::Rails = true
46
- rescue LoadError => e
47
- raise unless e.message =~ /rails/
48
- Loaded::Rails = false
49
- end
50
-
51
- begin
52
- require "active_record"
53
- raise VersionNotSupportedError, "Mobility is only compatible with ActiveRecord 4.2 and greater" if ::ActiveRecord::VERSION::STRING < "4.2"
54
- Loaded::ActiveRecord = true
55
- rescue LoadError => e
56
- raise unless e.message =~ /active_record/
57
- Loaded::ActiveRecord = false
58
- end
59
-
60
- if Loaded::ActiveRecord
61
- require "mobility/active_model"
62
- require "mobility/active_record"
63
- if Loaded::Rails
64
- require "rails/generators/mobility/generators"
65
- end
66
- end
67
-
68
- begin
69
- require "sequel"
70
- raise VersionNotSupportedError, "Mobility is only compatible with Sequel 4.0 and greater" if ::Sequel::MAJOR < 4
71
- require "sequel/plugins/mobility"
72
- unless defined?(ActiveSupport::Inflector)
73
- # TODO: avoid automatically including the inflector extension
74
- require "sequel/extensions/inflector"
75
- end
76
- require "sequel/plugins/dirty"
77
- require "mobility/sequel"
78
- Loaded::Sequel = true
79
- rescue LoadError => e
80
- raise unless e.message =~ /sequel/
81
- Loaded::Sequel = false
82
- end
37
+ require "rails/generators/mobility/generators" if defined?(Rails)
83
38
 
84
39
  class << self
85
40
  def extended(model_class)
86
- return if model_class.respond_to? :mobility_accessor
87
-
88
- model_class.extend Translates
89
- model_class.extend ClassMethods
90
- #TODO: Remove in v1.0
91
- model_class.include InstanceMethods
92
-
93
- if translates = Mobility.config.accessor_method
94
- model_class.singleton_class.send(:alias_method, translates, :mobility_accessor)
95
- end
96
-
97
- if Loaded::ActiveRecord && model_class < ::ActiveRecord::Base
98
- model_class.include(ActiveRecord)
99
- end
100
-
101
- if Loaded::Sequel && model_class < ::Sequel::Model
102
- model_class.include(Sequel)
41
+ def model_class.translates(*args, **options)
42
+ include Mobility.translations_class.new(*args, **options)
103
43
  end
104
44
  end
105
45
 
@@ -110,6 +50,38 @@ module Mobility
110
50
  model_class.extend self
111
51
  end
112
52
 
53
+ # @return [Symbol,Class]
54
+ def default_backend
55
+ translations_class.defaults[:backend]
56
+ end
57
+
58
+ # Configure Mobility
59
+ # @yield [Mobility::Translations]
60
+ def configure(&block)
61
+ translates_with(Class.new(Translations)) unless @translations_class
62
+ if block.arity == 0
63
+ translations_class.instance_exec(&block)
64
+ else
65
+ yield translations_class
66
+ end
67
+ end
68
+ # @!endgroup
69
+
70
+ def translates_with(pluggable)
71
+ raise ArgumentError, "translations class must be a subclass of Module." unless Module === pluggable
72
+ @translations_class = pluggable
73
+ end
74
+
75
+ def translations_class
76
+ @translations_class ||
77
+ raise(Error, "Mobility has not been configured. "\
78
+ "Configure with Mobility.configure, or assign a translations class with Mobility.translates_with(<class>)")
79
+ end
80
+
81
+ def reset_translations_class
82
+ @translations_class = nil
83
+ end
84
+
113
85
  # @!group Locale Accessors
114
86
  # @return [Symbol] Mobility locale
115
87
  def locale
@@ -144,55 +116,6 @@ module Mobility
144
116
  RequestStore.store
145
117
  end
146
118
 
147
- # @!group Configuration Methods
148
- # @return [Mobility::Configuration] Mobility configuration
149
- def config
150
- @configuration ||= Configuration.new
151
- end
152
-
153
- # (see Mobility::Configuration#accessor_method)
154
- # @!method accessor_method
155
- #
156
- # (see Mobility::Configuration#query_method)
157
- # @!method query_method
158
-
159
- # (see Mobility::Configuration#new_fallbacks)
160
- # @!method new_fallbacks
161
-
162
- # (see Mobility::Configuration#default_backend)
163
- # @!method default_backend
164
-
165
- # (see Mobility::Configuration#default_options)
166
- # @!method default_options
167
- #
168
- # (see Mobility::Configuration#plugins)
169
- # @!method plugins
170
- #
171
- # (see Mobility::Configuration#default_accessor_locales)
172
- # @!method default_accessor_locales
173
- %w[accessor_method query_method default_backend default_options plugins default_accessor_locales].each do |method_name|
174
- define_method method_name do
175
- config.public_send(method_name)
176
- end
177
- end
178
-
179
- # TODO: Remove in v1.0
180
- def default_fallbacks(*args)
181
- config.public_send(:default_fallbacks, *args)
182
- end
183
-
184
- # TODO: Make private in v1.0
185
- def new_fallbacks(*args)
186
- config.public_send(:new_fallbacks, *args)
187
- end
188
-
189
- # Configure Mobility
190
- # @yield [Mobility::Configuration] Mobility configuration
191
- def configure
192
- yield config
193
- end
194
- # @!endgroup
195
-
196
119
  # Return normalized locale
197
120
  # @param [String,Symbol] locale
198
121
  # @return [String] Normalized locale
@@ -228,17 +151,7 @@ module Mobility
228
151
  # @param [String,Symbol] locale
229
152
  # @raise [InvalidLocale] if locale is present but not available
230
153
  def enforce_available_locales!(locale)
231
- # TODO: Remove conditional in v1.0
232
- if I18n.enforce_available_locales
233
- raise Mobility::InvalidLocale.new(locale) unless (I18n.locale_available?(locale) || locale.nil?)
234
- else
235
- warn <<-EOL
236
- WARNING: You called Mobility.enforce_available_locales! in a situation where
237
- I18n.enforce_available_locales is false. In the past, Mobility would do nothing
238
- in this case, but as of the next major release Mobility will ignore the I18n
239
- setting and enforce available locales whenever this method is called.
240
- EOL
241
- end
154
+ raise Mobility::InvalidLocale.new(locale) unless (locale.nil? || available_locales.include?(locale.to_sym))
242
155
  end
243
156
 
244
157
  # Returns available locales. Defaults to I18n.available_locales, but will
@@ -250,7 +163,7 @@ EOL
250
163
  # simply default to +I18n.available_locales+, we may define many more
251
164
  # methods (in LocaleAccessors) than is really necessary.
252
165
  def available_locales
253
- if Loaded::Rails && Rails.application
166
+ if defined?(Rails) && Rails.application
254
167
  Rails.application.config.i18n.available_locales || I18n.available_locales
255
168
  else
256
169
  I18n.available_locales
@@ -270,41 +183,6 @@ EOL
270
183
  end
271
184
  end
272
185
 
273
- # TODO: Remove entire module in v1.0
274
- module InstanceMethods
275
- # Fetch backend for an attribute
276
- # @deprecated Use mobility_backends[:<attribute>] instead.
277
- # @param [String] attribute Attribute
278
- def mobility_backend_for(attribute)
279
- warn %{
280
- WARNING: mobility_backend_for is deprecated and will be removed in the next
281
- version of Mobility. Use <post>.<attribute>_backend instead.}
282
- mobility_backends[attribute.to_sym]
283
- end
284
-
285
- def mobility
286
- warn %{
287
- WARNING: <post>.mobility is deprecated and will be removed in the next
288
- version of Mobility. To get backends, use <post>.<attribute>_backend instead.}
289
- @mobility ||= Adapter.new(self)
290
- end
291
-
292
- class Adapter < Struct.new(:model)
293
- def backend_for(attribute)
294
- model.mobility_backends[attribute.to_sym]
295
- end
296
- end
297
- private_constant :Adapter
298
- end
299
-
300
- module ClassMethods
301
- # Return translated attribute names on this model.
302
- # @return [Array<String>] Attribute names
303
- def mobility_attributes
304
- []
305
- end
306
- end
307
-
308
186
  class InvalidLocale < I18n::InvalidLocale; end
309
187
  class NotImplementedError < StandardError; end
310
188
  end
@@ -38,7 +38,7 @@ module Mobility
38
38
  attr_reader :locale
39
39
  attr_reader :attribute_name
40
40
 
41
- def initialize(relation, column_name, locale, backend_class, attribute_name: nil)
41
+ def initialize(relation, column_name, locale, backend_class, attribute_name = nil)
42
42
  @backend_class = backend_class
43
43
  @locale = locale
44
44
  @attribute_name = attribute_name || column_name
@@ -49,7 +49,7 @@ module Mobility
49
49
  case other
50
50
  when NilClass
51
51
  to_question.not
52
- when Integer, Array, Hash
52
+ when Integer, Array, ::Hash
53
53
  to_dash_arrow.eq other.to_json
54
54
  when Jsonb
55
55
  to_dash_arrow.eq other.to_dash_arrow
@@ -1,6 +1,4 @@
1
1
  # frozen-string-literal: true
2
- require "mobility/backend/orm_delegator"
3
-
4
2
  module Mobility
5
3
  =begin
6
4
 
@@ -9,10 +7,9 @@ Defines a minimum set of shared components included in any backend. These are:
9
7
  - a reader returning the +model+ on which the backend is defined ({#model})
10
8
  - a reader returning the +attribute+ for which the backend is defined
11
9
  ({#attribute})
12
- - a constructor setting these two elements (+model+, +attribute+), and
13
- extracting fallbacks from the options hash ({#initialize})
10
+ - a constructor setting these two elements (+model+, +attribute+)
14
11
  - a +setup+ method adding any configuration code to the model class
15
- ({Setup#setup})
12
+ ({ClassMethods#setup})
16
13
 
17
14
  On top of this, a backend will normally:
18
15
 
@@ -50,7 +47,7 @@ On top of this, a backend will normally:
50
47
  end
51
48
  end
52
49
 
53
- @see Mobility::Attributes
50
+ @see Mobility::Translations
54
51
 
55
52
  =end
56
53
 
@@ -66,9 +63,9 @@ On top of this, a backend will normally:
66
63
  # @!macro [new] backend_constructor
67
64
  # @param model Model on which backend is defined
68
65
  # @param [String] attribute Backend attribute
69
- def initialize(model, attribute)
70
- @model = model
71
- @attribute = attribute
66
+ def initialize(*args)
67
+ @model = args[0]
68
+ @attribute = args[1]
72
69
  end
73
70
 
74
71
  # @!macro [new] backend_reader
@@ -103,7 +100,7 @@ On top of this, a backend will normally:
103
100
  # @param [Symbol] locale Locale to read
104
101
  # @return [TrueClass,FalseClass] Whether translation is present for locale
105
102
  def present?(locale, options = {})
106
- Util.present?(read(locale, options))
103
+ Util.present?(read(locale, **options))
107
104
  end
108
105
 
109
106
  # @!method model_class
@@ -118,21 +115,18 @@ On top of this, a backend will normally:
118
115
  # Extend included class with +setup+ method and other class methods
119
116
  def self.included(base)
120
117
  base.extend ClassMethods
121
- def base.options
122
- @options
123
- end
124
- base.option_reader :model_class
125
- end
126
-
127
- # @param [String] attribute
128
- # @return [String] name of backend reader method
129
- def self.method_name(attribute)
130
- @backend_method_names ||= {}
131
- @backend_method_names[attribute] ||= "#{attribute}_backend"
118
+ base.singleton_class.attr_reader :options, :model_class
132
119
  end
133
120
 
134
121
  # Defines setup hooks for backend to customize model class.
135
122
  module ClassMethods
123
+ # Returns valid option keys for this backend. This is overriden in
124
+ # backends to define which keys are valid for each backend class.
125
+ # @return [Array]
126
+ def valid_keys
127
+ []
128
+ end
129
+
136
130
  # Assign block to be called on model class.
137
131
  # @yield [attribute_names, options]
138
132
  # @note When called multiple times, setup blocks will be appended
@@ -152,6 +146,7 @@ On top of this, a backend will normally:
152
146
  def inherited(subclass)
153
147
  subclass.instance_variable_set(:@setup_block, @setup_block)
154
148
  subclass.instance_variable_set(:@options, @options)
149
+ subclass.instance_variable_set(:@model_class, @model_class)
155
150
  end
156
151
 
157
152
  # Call setup block on a class with attributes and options.
@@ -166,14 +161,14 @@ On top of this, a backend will normally:
166
161
  # Build a subclass of this backend class for a given set of options
167
162
  # @note This method also freezes the options hash to prevent it from
168
163
  # being changed.
164
+ # @param [Class] model_class Class
169
165
  # @param [Hash] options
170
166
  # @return [Class] backend subclass
171
- def with_options(options = {}, &block)
172
- configure(options) if respond_to?(:configure)
173
- options.freeze
167
+ def build_subclass(model_class, options)
174
168
  Class.new(self) do
175
- @options = options
176
- class_eval(&block) if block_given?
169
+ @model_class = model_class
170
+ configure(options) if respond_to?(:configure)
171
+ @options = options.freeze
177
172
  end
178
173
  end
179
174
 
@@ -191,27 +186,6 @@ On top of this, a backend will normally:
191
186
  EOM
192
187
  end
193
188
 
194
- # {Attributes} uses this method to get a backend class specific to the
195
- # model using the backend. Backend classes can override this method to
196
- # return a class specific to the model class using the backend (e.g.
197
- # either an ActiveRecord or Sequel backend class depending on whether the
198
- # model is an ActiveRecord model or a Sequel model.)
199
- # @see OrmDelegator
200
- # @see Attributes
201
- # @return [self] returns itself
202
- def for(_)
203
- self
204
- end
205
-
206
- # Called from plugins to apply custom processing for this backend.
207
- # Name is the name of the plugin.
208
- # @param [Symbol] name Name of plugin
209
- # @return [Boolean] Whether the plugin was applied
210
- # @note This is currently only called by Plugins::Cache.
211
- def apply_plugin(_)
212
- false
213
- end
214
-
215
189
  # Show useful information about this backend class, if it has no name.
216
190
  # @return [String]
217
191
  def inspect
@@ -220,10 +194,12 @@ On top of this, a backend will normally:
220
194
  end
221
195
 
222
196
  Translation = Struct.new(:backend, :locale) do
223
- %w[read write].each do |accessor|
224
- define_method accessor do |*args|
225
- backend.send(accessor, locale, *args)
226
- end
197
+ def read(options = {})
198
+ backend.read(locale, options)
199
+ end
200
+
201
+ def write(value, options = {})
202
+ backend.write(locale, value, options)
227
203
  end
228
204
  end
229
205
  end