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