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
@@ -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
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,42 +18,41 @@ 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
48
40
 
49
- define_method :method_missing do |method_name, *arguments, **options, &block|
41
+ def define_fallthrough_accessors(*names)
42
+ method_name_regex = /\A(#{names.join('|')})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
43
+
44
+ define_method :method_missing do |method_name, *args, &block|
50
45
  if method_name =~ method_name_regex
51
- attribute = $1.to_sym
46
+ attribute_method = "#{$1}#{$4}"
52
47
  locale, suffix = $2.split('_')
53
48
  locale = "#{locale}-#{suffix.upcase}" if suffix
54
- public_send("#{attribute}#{$4}", *arguments, **options, locale: locale.to_sym)
49
+ if $4 == '=' # writer
50
+ public_send(attribute_method, args[0], **(args[1] || {}), locale: locale)
51
+ else # reader
52
+ public_send(attribute_method, **(args[0] || {}), locale: locale)
53
+ end
55
54
  else
56
- super(method_name, *arguments, &block)
55
+ super(method_name, *args, &block)
57
56
  end
58
57
  end
59
58
 
@@ -62,5 +61,7 @@ model class is generated.
62
61
  end
63
62
  end
64
63
  end
64
+
65
+ register_plugin :fallthrough_accessors, FallthroughAccessors
65
66
  end
66
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
@@ -3,29 +3,20 @@ module Mobility
3
3
  module Plugins
4
4
  =begin
5
5
 
6
- @see {Mobility::Plugins::ActiveRecord::Query}
6
+ @see {Mobility::Plugins::ActiveRecord::Query} or {Mobility::Plugins::Sequel::Query}.
7
7
 
8
8
  =end
9
9
  module Query
10
- class << self
11
- def apply(attributes, option)
12
- if option
13
- include_query_module(attributes)
14
- end
15
- end
10
+ extend Plugin
16
11
 
17
- private
12
+ default :i18n
13
+ requires :backend, include: :before
18
14
 
19
- def include_query_module(attributes)
20
- if Loaded::ActiveRecord && attributes.model_class < ::ActiveRecord::Base
21
- require "mobility/plugins/active_record/query"
22
- ActiveRecord::Query.apply(attributes)
23
- elsif Loaded::Sequel && attributes.model_class < ::Sequel::Model
24
- require "mobility/plugins/sequel/query"
25
- Sequel::Query.apply(attributes)
26
- end
27
- end
15
+ def query_method
16
+ (options[:query] == true) ? self.class.defaults[:query] : options[:query]
28
17
  end
29
18
  end
19
+
20
+ register_plugin(:query, Query)
30
21
  end
31
22
  end