mobility 0.8.13 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) 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 +26 -0
  5. data/Gemfile +5 -2
  6. data/Gemfile.lock +79 -8
  7. data/README.md +183 -91
  8. data/lib/mobility.rb +40 -166
  9. data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
  10. data/lib/mobility/backend.rb +19 -41
  11. data/lib/mobility/backends.rb +20 -0
  12. data/lib/mobility/backends/active_record.rb +4 -0
  13. data/lib/mobility/backends/active_record/column.rb +2 -0
  14. data/lib/mobility/backends/active_record/container.rb +4 -2
  15. data/lib/mobility/backends/active_record/hstore.rb +2 -0
  16. data/lib/mobility/backends/active_record/json.rb +2 -0
  17. data/lib/mobility/backends/active_record/jsonb.rb +2 -0
  18. data/lib/mobility/backends/active_record/key_value.rb +5 -3
  19. data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
  20. data/lib/mobility/backends/active_record/serialized.rb +2 -0
  21. data/lib/mobility/backends/active_record/table.rb +5 -3
  22. data/lib/mobility/backends/column.rb +0 -6
  23. data/lib/mobility/backends/container.rb +2 -1
  24. data/lib/mobility/backends/hash.rb +39 -0
  25. data/lib/mobility/backends/hstore.rb +0 -1
  26. data/lib/mobility/backends/json.rb +0 -1
  27. data/lib/mobility/backends/jsonb.rb +0 -1
  28. data/lib/mobility/backends/key_value.rb +22 -14
  29. data/lib/mobility/backends/null.rb +2 -0
  30. data/lib/mobility/backends/sequel.rb +3 -0
  31. data/lib/mobility/backends/sequel/column.rb +2 -0
  32. data/lib/mobility/backends/sequel/container.rb +3 -1
  33. data/lib/mobility/backends/sequel/hstore.rb +2 -0
  34. data/lib/mobility/backends/sequel/json.rb +2 -0
  35. data/lib/mobility/backends/sequel/jsonb.rb +3 -1
  36. data/lib/mobility/backends/sequel/key_value.rb +8 -6
  37. data/lib/mobility/backends/sequel/serialized.rb +2 -0
  38. data/lib/mobility/backends/sequel/table.rb +5 -2
  39. data/lib/mobility/backends/serialized.rb +1 -3
  40. data/lib/mobility/backends/table.rb +14 -6
  41. data/lib/mobility/pluggable.rb +36 -0
  42. data/lib/mobility/plugin.rb +260 -0
  43. data/lib/mobility/plugins.rb +26 -25
  44. data/lib/mobility/plugins/active_model.rb +17 -0
  45. data/lib/mobility/plugins/active_model/cache.rb +26 -0
  46. data/lib/mobility/plugins/active_model/dirty.rb +112 -77
  47. data/lib/mobility/plugins/active_record.rb +34 -0
  48. data/lib/mobility/plugins/active_record/backend.rb +25 -0
  49. data/lib/mobility/plugins/active_record/cache.rb +28 -0
  50. data/lib/mobility/plugins/active_record/dirty.rb +34 -17
  51. data/lib/mobility/plugins/active_record/query.rb +43 -31
  52. data/lib/mobility/plugins/active_record/uniqueness_validation.rb +60 -0
  53. data/lib/mobility/plugins/attribute_methods.rb +28 -20
  54. data/lib/mobility/plugins/attributes.rb +70 -0
  55. data/lib/mobility/plugins/backend.rb +138 -0
  56. data/lib/mobility/plugins/backend_reader.rb +34 -0
  57. data/lib/mobility/plugins/cache.rb +59 -24
  58. data/lib/mobility/plugins/default.rb +22 -17
  59. data/lib/mobility/plugins/dirty.rb +12 -33
  60. data/lib/mobility/plugins/fallbacks.rb +51 -43
  61. data/lib/mobility/plugins/fallthrough_accessors.rb +20 -23
  62. data/lib/mobility/plugins/locale_accessors.rb +25 -35
  63. data/lib/mobility/plugins/presence.rb +28 -21
  64. data/lib/mobility/plugins/query.rb +8 -17
  65. data/lib/mobility/plugins/reader.rb +50 -0
  66. data/lib/mobility/plugins/sequel.rb +34 -0
  67. data/lib/mobility/plugins/sequel/backend.rb +25 -0
  68. data/lib/mobility/plugins/sequel/cache.rb +24 -0
  69. data/lib/mobility/plugins/sequel/dirty.rb +32 -21
  70. data/lib/mobility/plugins/sequel/query.rb +21 -6
  71. data/lib/mobility/plugins/writer.rb +44 -0
  72. data/lib/mobility/translations.rb +95 -0
  73. data/lib/mobility/version.rb +12 -1
  74. data/lib/rails/generators/mobility/templates/initializer.rb +95 -77
  75. metadata +28 -27
  76. metadata.gz.sig +0 -0
  77. data/lib/mobility/active_model.rb +0 -4
  78. data/lib/mobility/active_model/backend_resetter.rb +0 -26
  79. data/lib/mobility/active_record.rb +0 -23
  80. data/lib/mobility/active_record/backend_resetter.rb +0 -26
  81. data/lib/mobility/active_record/uniqueness_validator.rb +0 -60
  82. data/lib/mobility/attributes.rb +0 -324
  83. data/lib/mobility/backend/orm_delegator.rb +0 -44
  84. data/lib/mobility/backend_resetter.rb +0 -50
  85. data/lib/mobility/configuration.rb +0 -138
  86. data/lib/mobility/fallbacks.rb +0 -28
  87. data/lib/mobility/interface.rb +0 -0
  88. data/lib/mobility/loaded.rb +0 -4
  89. data/lib/mobility/plugins/active_record/attribute_methods.rb +0 -38
  90. data/lib/mobility/plugins/cache/translation_cacher.rb +0 -40
  91. data/lib/mobility/sequel.rb +0 -9
  92. data/lib/mobility/sequel/backend_resetter.rb +0 -23
  93. data/lib/mobility/translates.rb +0 -73
@@ -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 (other than +Mobility::Plugins::OPTION_UNSET+) is passed as the +default+
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
- # @param [Attributes] attributes
68
- # @param [Object] option
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
- # @!group Backend Accessors
86
- # @!macro backend_reader
87
- # @option accessor_options [Boolean] default
88
- # *false* to disable presence filter.
89
- def read(locale, accessor_options = {})
90
- default = accessor_options.has_key?(:default) ? accessor_options.delete(:default) : options[:default]
91
- if (value = super(locale, accessor_options)).nil?
92
- Default[default, locale: locale, accessor_options: accessor_options, model: model, attribute: attribute]
93
- else
94
- value
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
- class << self
24
- # Applies dirty plugin to attributes for a given option value.
25
- # @param [Attributes] attributes
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
- private
36
-
37
- def include_dirty_module(backend_class, model_class, *attribute_names)
38
- dirty_module =
39
- if Loaded::ActiveRecord && model_class.ancestors.include?(::ActiveModel::Dirty)
40
- if (model_class < ::ActiveRecord::Base)
41
- require "mobility/plugins/active_record/dirty"
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 the value of
12
- {Mobility::Configuration#new_fallbacks} for the fallbacks instance. This
13
- defaults to an instance of +I18n::Locale::Fallbacks+, but can be
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, use set the value of `default_options[:fallbacks]`
19
- in your Mobility configuration (see below).
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>
@@ -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
- @example Setting default fallbacks across all models
113
- Mobility.configure do |config|
114
- # ...
115
- config.default_options[:fallbacks] = { :'fr' => 'en' }
116
- # ...
117
- end
118
-
119
- class Post
120
- # Post will fallback from French to English by default
121
- translates :title, fallbacks: true
122
- end
114
+ default true
115
+ requires :backend, include: :before
123
116
 
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
- # @param [Attributes] attributes
129
- # @param [Boolean] option
130
- def self.apply(attributes, option)
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
- def initialize(fallbacks_option)
135
- define_read(convert_option_to_fallbacks(fallbacks_option))
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
- private
131
+ class I18nFallbacks < ::I18n::Locale::Fallbacks
132
+ def [](locale)
133
+ super | I18n.fallbacks[locale]
134
+ end
135
+ end
139
136
 
140
- def define_read(fallbacks)
141
- define_method :read do |locale, fallback: true, **options|
142
- return super(locale, options) if !fallback || options[:locale]
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
- locales = fallback == true ? fallbacks[locale] : [locale, *fallback]
145
- locales.each do |fallback_locale|
146
- value = super(fallback_locale, options)
147
- return value if Util.present?(value)
148
- end
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
- super(locale, options)
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
- def convert_option_to_fallbacks(option)
155
- if option.is_a?(Hash)
156
- Mobility.new_fallbacks(option)
157
- elsif option == true
158
- Mobility.new_fallbacks
159
- else
160
- Hash.new { [] }
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,33 +18,28 @@ 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
- @example Using fallthrough locales on a plain old ruby class
22
- class Post
23
- def title
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
- Mobility.locale = :en
30
- post = Post.new
31
- post.title
32
- #=> "title in en"
33
- post.title_fr
34
- #=> "title in fr"
25
+ default true
26
+
27
+ requires :reader
28
+ requires :writer
35
29
 
36
- =end
37
- class FallthroughAccessors < Module
38
30
  # Apply fallthrough accessors plugin to attributes.
39
- # @param [Attributes] attributes
31
+ # @param [Translations] translations
40
32
  # @param [Boolean] option
41
- def self.apply(attributes, option)
42
- attributes.model_class.include new(*attributes.names) if option
33
+ initialize_hook do
34
+ if options[:fallthrough_accessors] && options[:reader] && options[:writer]
35
+ define_fallthrough_accessors(names)
36
+ end
43
37
  end
44
38
 
45
- # @param [String] One or more attributes
46
- def initialize(*attributes)
47
- method_name_regex = /\A(#{attributes.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
39
+ private
40
+
41
+ def define_fallthrough_accessors(*names)
42
+ method_name_regex = /\A(#{names.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
48
43
 
49
44
  define_method :method_missing do |method_name, *args, &block|
50
45
  if method_name =~ method_name_regex
@@ -52,9 +47,9 @@ model class is generated.
52
47
  locale, suffix = $2.split('_')
53
48
  locale = "#{locale}-#{suffix.upcase}" if suffix
54
49
  if $4 == '=' # writer
55
- public_send(attribute_method, args[0], **(args[1] || {}), locale: locale.to_sym)
50
+ public_send(attribute_method, args[0], **(args[1] || {}), locale: locale)
56
51
  else # reader
57
- public_send(attribute_method, **(args[0] || {}), locale: locale.to_sym)
52
+ public_send(attribute_method, **(args[0] || {}), locale: locale)
58
53
  end
59
54
  else
60
55
  super(method_name, *args, &block)
@@ -66,5 +61,7 @@ model class is generated.
66
61
  end
67
62
  end
68
63
  end
64
+
65
+ register_plugin :fallthrough_accessors, FallthroughAccessors
69
66
  end
70
67
  end
@@ -13,47 +13,33 @@ If no locales are passed as an option to the initializer,
13
13
  +Mobility.available_locales+ (i.e. +I18n.available_locales+, or Rails-set
14
14
  available locales for a Rails application) will be used by default.
15
15
 
16
- @example
17
- class Post
18
- def title
19
- "title in #{Mobility.locale}"
20
- end
21
- include Mobility::Plugins::LocaleAccessors.new("title", locales: [:en, :fr])
22
- end
16
+ =end
17
+ module LocaleAccessors
18
+ extend Plugin
23
19
 
24
- Mobility.locale = :en
25
- post = Post.new
26
- post.title
27
- #=> "title in en"
28
- post.title_fr
29
- #=> "title in fr"
20
+ default true
21
+
22
+ requires :reader
23
+ requires :writer
30
24
 
31
- =end
32
- class LocaleAccessors < Module
33
25
  # Apply locale accessors plugin to attributes.
34
- # @param [Attributes] attributes
26
+ # @param [Translations] translations
35
27
  # @param [Boolean] option
36
- def self.apply(attributes, option)
37
- if accessor_locales = option
38
- accessor_locales = Mobility.config.default_accessor_locales if accessor_locales == true
39
- attributes.model_class.include new(*attributes.names, locales: accessor_locales)
40
- end
41
- end
42
-
43
- # @param [String] One or more attribute names
44
- # @param [Array<Symbol>] Locales
45
- def initialize(*attribute_names, locales:)
46
- attribute_names.each do |name|
47
- locales.each do |locale|
48
- define_reader(name, locale)
49
- define_writer(name, locale)
28
+ initialize_hook do |*names|
29
+ if locales = options[:locale_accessors]
30
+ locales = Mobility.available_locales if locales == true
31
+ names.each do |name|
32
+ locales.each do |locale|
33
+ define_locale_reader(name, locale) if options[:reader]
34
+ define_locale_writer(name, locale) if options[:writer]
35
+ end
50
36
  end
51
37
  end
52
38
  end
53
39
 
54
40
  private
55
41
 
56
- def define_reader(name, locale)
42
+ def define_locale_reader(name, locale)
57
43
  warning_message = "locale passed as option to locale accessor will be ignored"
58
44
  normalized_locale = Mobility.normalize_locale(locale)
59
45
 
@@ -61,18 +47,20 @@ available locales for a Rails application) will be used by default.
61
47
  def #{name}_#{normalized_locale}(options = {})
62
48
  return super() if options.delete(:super)
63
49
  warn "#{warning_message}" if options[:locale]
64
- #{name}(**options, locale: :'#{locale}')
50
+ #{name}(**options, locale: #{locale.inspect})
65
51
  end
52
+ EOM
66
53
 
54
+ module_eval <<-EOM, __FILE__, __LINE__ + 1
67
55
  def #{name}_#{normalized_locale}?(options = {})
68
56
  return super() if options.delete(:super)
69
57
  warn "#{warning_message}" if options[:locale]
70
- #{name}?(**options, locale: :'#{locale}')
58
+ #{name}?(**options, locale: #{locale.inspect})
71
59
  end
72
60
  EOM
73
61
  end
74
62
 
75
- def define_writer(name, locale)
63
+ def define_locale_writer(name, locale)
76
64
  warning_message = "locale passed as option to locale accessor will be ignored"
77
65
  normalized_locale = Mobility.normalize_locale(locale)
78
66
 
@@ -80,10 +68,12 @@ available locales for a Rails application) will be used by default.
80
68
  def #{name}_#{normalized_locale}=(value, options = {})
81
69
  return super(value) if options.delete(:super)
82
70
  warn "#{warning_message}" if options[:locale]
83
- public_send(:#{name}=, value, **options, locale: :'#{locale}')
71
+ public_send(:#{name}=, value, **options, locale: #{locale.inspect})
84
72
  end
85
73
  EOM
86
74
  end
87
75
  end
76
+
77
+ register_plugin(:locale_accessors, LocaleAccessors)
88
78
  end
89
79
  end
@@ -6,43 +6,50 @@ module Mobility
6
6
  =begin
7
7
 
8
8
  Applies presence filter to values fetched from backend and to values set on
9
- backend. Included by default, but can be disabled with +presence: false+ option.
9
+ backend.
10
10
 
11
11
  @note For performance reasons, the presence plugin filters only for empty
12
12
  strings, not other values continued "blank" like empty arrays.
13
13
 
14
14
  =end
15
15
  module Presence
16
+ extend Plugin
17
+
18
+ default true
19
+ requires :backend, include: :before
20
+
16
21
  # Applies presence plugin to attributes.
17
- # @param [Attributes] attributes
18
- # @param [Boolean] option
19
- def self.apply(attributes, option)
20
- attributes.backend_class.include(self) if option
22
+ included_hook do |_, backend_class|
23
+ backend_class.include(BackendMethods) if options[:presence]
21
24
  end
22
25
 
23
- # @!group Backend Accessors
24
- # @!macro backend_reader
25
- # @option options [Boolean] presence
26
- # *false* to disable presence filter.
27
- def read(locale, **options)
28
- options.delete(:presence) == false ? super : Presence[super]
29
- end
26
+ module BackendMethods
27
+ # @!group Backend Accessors
28
+ # @!macro backend_reader
29
+ # @option options [Boolean] presence
30
+ # *false* to disable presence filter.
31
+ def read(locale, **options)
32
+ options.delete(:presence) == false ? super : Presence[super]
33
+ end
30
34
 
31
- # @!macro backend_writer
32
- # @option options [Boolean] presence
33
- # *false* to disable presence filter.
34
- def write(locale, value, **options)
35
- if options.delete(:presence) == false
36
- super
37
- else
38
- super(locale, Presence[value], options)
35
+ # @!macro backend_writer
36
+ # @option options [Boolean] presence
37
+ # *false* to disable presence filter.
38
+ def write(locale, value, **options)
39
+ if options.delete(:presence) == false
40
+ super
41
+ else
42
+ super(locale, Presence[value], options)
43
+ end
39
44
  end
45
+ # @!endgroup
40
46
  end
41
- # @!endgroup
42
47
 
43
48
  def self.[](value)
44
49
  (value == "") ? nil : value
45
50
  end
46
51
  end
52
+
53
+ register_plugin(:presence, Presence)
47
54
  end
48
55
  end