mobility 0.8.8 → 1.0.0.alpha

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 +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