mobility 0.8.8 → 1.0.0.alpha

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.
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 +56 -0
  5. data/Gemfile +52 -16
  6. data/Gemfile.lock +113 -52
  7. data/Guardfile +23 -1
  8. data/README.md +184 -92
  9. data/Rakefile +6 -4
  10. data/lib/mobility.rb +40 -166
  11. data/lib/mobility/active_record/translation.rb +1 -1
  12. data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
  13. data/lib/mobility/backend.rb +19 -41
  14. data/lib/mobility/backends.rb +20 -0
  15. data/lib/mobility/backends/active_record.rb +4 -0
  16. data/lib/mobility/backends/active_record/column.rb +2 -0
  17. data/lib/mobility/backends/active_record/container.rb +4 -2
  18. data/lib/mobility/backends/active_record/hstore.rb +2 -0
  19. data/lib/mobility/backends/active_record/json.rb +2 -0
  20. data/lib/mobility/backends/active_record/jsonb.rb +2 -0
  21. data/lib/mobility/backends/active_record/key_value.rb +5 -3
  22. data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
  23. data/lib/mobility/backends/active_record/serialized.rb +2 -0
  24. data/lib/mobility/backends/active_record/table.rb +5 -3
  25. data/lib/mobility/backends/column.rb +0 -6
  26. data/lib/mobility/backends/container.rb +2 -1
  27. data/lib/mobility/backends/hash.rb +39 -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 +0 -1
  31. data/lib/mobility/backends/key_value.rb +22 -14
  32. data/lib/mobility/backends/null.rb +2 -0
  33. data/lib/mobility/backends/sequel.rb +3 -0
  34. data/lib/mobility/backends/sequel/column.rb +2 -0
  35. data/lib/mobility/backends/sequel/container.rb +3 -1
  36. data/lib/mobility/backends/sequel/hstore.rb +2 -0
  37. data/lib/mobility/backends/sequel/json.rb +2 -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 +2 -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 +14 -6
  44. data/lib/mobility/pluggable.rb +36 -0
  45. data/lib/mobility/plugin.rb +260 -0
  46. data/lib/mobility/plugins.rb +26 -25
  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 +310 -54
  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 +72 -101
  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 +28 -20
  57. data/lib/mobility/plugins/attributes.rb +70 -0
  58. data/lib/mobility/plugins/backend.rb +138 -0
  59. data/lib/mobility/plugins/backend_reader.rb +34 -0
  60. data/lib/mobility/plugins/cache.rb +59 -24
  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 +51 -43
  64. data/lib/mobility/plugins/fallthrough_accessors.rb +26 -25
  65. data/lib/mobility/plugins/locale_accessors.rb +25 -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 +45 -32
  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 +95 -77
  78. metadata +51 -51
  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,34 @@ 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
61
+ translates_with(Class.new(Translations)) unless @translations_class
62
+ yield translations_class
63
+ end
64
+ # @!endgroup
65
+
66
+ def translates_with(pluggable)
67
+ raise ArgumentError, "translations class must be a subclass of Module." unless Module === pluggable
68
+ @translations_class = pluggable
69
+ end
70
+
71
+ def translations_class
72
+ @translations_class ||
73
+ raise(Error, "Mobility has not been configured. "\
74
+ "Configure with Mobility.configure, or assign a translations class with Mobility.translates_with(<class>)")
75
+ end
76
+
77
+ def reset_translations_class
78
+ @translations_class = nil
79
+ end
80
+
113
81
  # @!group Locale Accessors
114
82
  # @return [Symbol] Mobility locale
115
83
  def locale
@@ -144,55 +112,6 @@ module Mobility
144
112
  RequestStore.store
145
113
  end
146
114
 
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
115
  # Return normalized locale
197
116
  # @param [String,Symbol] locale
198
117
  # @return [String] Normalized locale
@@ -228,17 +147,7 @@ module Mobility
228
147
  # @param [String,Symbol] locale
229
148
  # @raise [InvalidLocale] if locale is present but not available
230
149
  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
150
+ raise Mobility::InvalidLocale.new(locale) unless (locale.nil? || available_locales.include?(locale.to_sym))
242
151
  end
243
152
 
244
153
  # Returns available locales. Defaults to I18n.available_locales, but will
@@ -250,7 +159,7 @@ EOL
250
159
  # simply default to +I18n.available_locales+, we may define many more
251
160
  # methods (in LocaleAccessors) than is really necessary.
252
161
  def available_locales
253
- if Loaded::Rails && Rails.application
162
+ if defined?(Rails) && Rails.application
254
163
  Rails.application.config.i18n.available_locales || I18n.available_locales
255
164
  else
256
165
  I18n.available_locales
@@ -270,41 +179,6 @@ EOL
270
179
  end
271
180
  end
272
181
 
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
182
  class InvalidLocale < I18n::InvalidLocale; end
309
183
  class NotImplementedError < StandardError; end
310
184
  end
@@ -6,7 +6,7 @@ module Mobility
6
6
 
7
7
  belongs_to :translatable, polymorphic: true, touch: true
8
8
 
9
- validates :key, presence: true, uniqueness: { scope: [:translatable_id, :translatable_type, :locale] }
9
+ validates :key, presence: true, uniqueness: { scope: [:translatable_id, :translatable_type, :locale], case_sensitive: true }
10
10
  validates :translatable, presence: true
11
11
  validates :locale, presence: true
12
12
  end
@@ -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
@@ -118,17 +115,7 @@ 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.
@@ -152,6 +139,7 @@ On top of this, a backend will normally:
152
139
  def inherited(subclass)
153
140
  subclass.instance_variable_set(:@setup_block, @setup_block)
154
141
  subclass.instance_variable_set(:@options, @options)
142
+ subclass.instance_variable_set(:@model_class, @model_class)
155
143
  end
156
144
 
157
145
  # Call setup block on a class with attributes and options.
@@ -166,14 +154,14 @@ On top of this, a backend will normally:
166
154
  # Build a subclass of this backend class for a given set of options
167
155
  # @note This method also freezes the options hash to prevent it from
168
156
  # being changed.
157
+ # @param [Class] model_class Class
169
158
  # @param [Hash] options
170
159
  # @return [Class] backend subclass
171
- def with_options(options = {}, &block)
172
- configure(options) if respond_to?(:configure)
173
- options.freeze
160
+ def build_subclass(model_class, options)
174
161
  Class.new(self) do
175
- @options = options
176
- class_eval(&block) if block_given?
162
+ @model_class = model_class
163
+ configure(options) if respond_to?(:configure)
164
+ @options = options.freeze
177
165
  end
178
166
  end
179
167
 
@@ -191,18 +179,6 @@ On top of this, a backend will normally:
191
179
  EOM
192
180
  end
193
181
 
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
182
  # Called from plugins to apply custom processing for this backend.
207
183
  # Name is the name of the plugin.
208
184
  # @param [Symbol] name Name of plugin
@@ -220,10 +196,12 @@ On top of this, a backend will normally:
220
196
  end
221
197
 
222
198
  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
199
+ def read(options = {})
200
+ backend.read(locale, options)
201
+ end
202
+
203
+ def write(value, options = {})
204
+ backend.write(locale, value, options)
227
205
  end
228
206
  end
229
207
  end
@@ -1,4 +1,24 @@
1
1
  module Mobility
2
2
  module Backends
3
+ @backends = {}
4
+
5
+ class << self
6
+ # @param [Symbol, Object] backend Name of backend to load.
7
+ def load_backend(name)
8
+ return name if Module === name
9
+
10
+ unless (backend = @backends[name])
11
+ require "mobility/backends/#{name}"
12
+ raise LoadError, "backend #{name} did not register itself correctly in Mobility::Backends" unless (backend = @backends[name])
13
+ end
14
+ backend
15
+ end
16
+ end
17
+
18
+ def self.register_backend(name, mod)
19
+ @backends[name] = mod
20
+ end
21
+
22
+ class LoadError < Error; end
3
23
  end
4
24
  end