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
@@ -12,30 +12,38 @@ attributes only.
12
12
 
13
13
  =end
14
14
  module AttributeMethods
15
- class << self
16
- # Applies attribute_methods plugin for a given option value.
17
- # @param [Attributes] attributes
18
- # @param [Boolean] option Value of option
19
- # @raise [ArgumentError] if model class does not support dirty tracking
20
- def apply(attributes, option)
21
- if option
22
- include_attribute_methods_module(attributes.model_class, *attributes.names)
23
- end
15
+ extend Plugin
16
+
17
+ default true
18
+
19
+ initialize_hook do |*names|
20
+ include InstanceMethods
21
+
22
+ define_method :translated_attributes do
23
+ super().merge(names.inject({}) do |attributes, name|
24
+ attributes.merge(name.to_s => send(name))
25
+ end)
26
+ end
27
+ end
28
+
29
+ # Applies attribute_methods plugin for a given option value.
30
+ included_hook do
31
+ if options[:attribute_methods]
32
+ define_method :untranslated_attributes, ::ActiveRecord::Base.instance_method(:attributes)
24
33
  end
34
+ end
25
35
 
26
- private
27
-
28
- def include_attribute_methods_module(model_class, *attribute_names)
29
- module_builder =
30
- if Loaded::ActiveRecord && model_class.ancestors.include?(::ActiveRecord::AttributeMethods)
31
- require "mobility/plugins/active_record/attribute_methods"
32
- Plugins::ActiveRecord::AttributeMethods
33
- else
34
- raise ArgumentError, "#{model_class} does not support AttributeMethods plugin."
35
- end
36
- model_class.include module_builder.new(*attribute_names)
36
+ module InstanceMethods
37
+ def translated_attributes
38
+ {}
39
+ end
40
+
41
+ def attributes
42
+ super.merge(translated_attributes)
37
43
  end
38
44
  end
39
45
  end
46
+
47
+ register_plugin(:attribute_methods, AttributeMethods)
40
48
  end
41
49
  end
@@ -0,0 +1,70 @@
1
+ # frozen-string-literal: true
2
+ module Mobility
3
+ module Plugins
4
+ =begin
5
+
6
+ Takes arguments, converts them to strings, and stores in an array +@names+,
7
+ made available with an +attr_reader+. Also provides some convenience methods
8
+ for aggregating attributes.
9
+
10
+ =end
11
+ module Attributes
12
+ extend Plugin
13
+
14
+ # Attribute names for which accessors will be defined
15
+ # @return [Array<String>] Array of names
16
+ attr_reader :names
17
+
18
+ initialize_hook do |*names|
19
+ @names = names.map(&:to_s).freeze
20
+ end
21
+
22
+ # Yield each attribute name to block
23
+ # @yieldparam [String] Attribute
24
+ def each &block
25
+ names.each(&block)
26
+ end
27
+
28
+ # Show useful information about this module.
29
+ # @return [String]
30
+ def inspect
31
+ "#<Translations @names=#{names.join(", ")}>"
32
+ end
33
+
34
+ included_hook do |klass|
35
+ klass.extend ClassMethods
36
+ @names.each { |name| klass.register_mobility_attribute(name) }
37
+ end
38
+
39
+ module ClassMethods
40
+ # Return true if attribute name is translated on this model.
41
+ # @param [String, Symbol] Attribute name
42
+ # @return [Boolean]
43
+ def mobility_attribute?(name)
44
+ mobility_attributes.include?(name.to_s)
45
+ end
46
+
47
+ # Register a new attribute name. Public, but treat as internal.
48
+ # @param [String, Symbol] Attribute name
49
+ def register_mobility_attribute(name)
50
+ (self.mobility_attributes << name.to_s).uniq!
51
+ end
52
+
53
+ def inherited(klass)
54
+ super
55
+ mobility_attributes.each { |name| klass.register_mobility_attribute(name) }
56
+ end
57
+
58
+ protected
59
+
60
+ # Return translated attribute names on this model.
61
+ # @return [Array<String>] Attribute names
62
+ def mobility_attributes
63
+ @mobility_attributes ||= []
64
+ end
65
+ end
66
+ end
67
+
68
+ register_plugin(:attributes, Attributes)
69
+ end
70
+ end
@@ -0,0 +1,138 @@
1
+ # frozen-string-literal: true
2
+ module Mobility
3
+ module Plugins
4
+ =begin
5
+
6
+ Plugin for setting up a backend for a set of model attributes. All backend
7
+ plugins must depend on this.
8
+
9
+ Defines:
10
+ - instance method +mobility_backends+ which returns a hash whose keys are
11
+ attribute names and values a backend for each attribute.
12
+ - class method +mobility_backend_class+ which takes an attribute name and
13
+ returns the backend class for that name.
14
+
15
+ =end
16
+ module Backend
17
+ extend Plugin
18
+
19
+ requires :attributes, include: :before
20
+
21
+ # Backend class
22
+ # @return [Class] Backend class
23
+ attr_reader :backend_class
24
+
25
+ # Backend
26
+ # @return [Symbol,Class,Class] Name of backend, or backend class
27
+ attr_reader :backend
28
+
29
+ # Backend options
30
+ # @return [Hash] Options for backend
31
+ attr_reader :backend_options
32
+
33
+ def initialize(*args, **original_options)
34
+ super
35
+ return unless Plugins::Backend.dependencies_satisfied?(self.class)
36
+
37
+ case options[:backend]
38
+ when String, Symbol, Class
39
+ @backend, @backend_options = options[:backend], options
40
+ when Array
41
+ @backend, @backend_options = options[:backend]
42
+ @backend_options = @backend_options.merge(original_options)
43
+ when NilClass
44
+ @backend = @backend_options = nil
45
+ else
46
+ raise ArgumentError, "backend must be either a backend name, a backend class, or a two-element array"
47
+ end
48
+
49
+ include InstanceMethods
50
+ end
51
+
52
+ # Setup backend class, include modules into model class, include/extend
53
+ # shared modules and setup model with backend setup block (see
54
+ # {Mobility::Backend::Setup#setup_model}).
55
+ def included(klass)
56
+ super
57
+
58
+ klass.extend ClassMethods
59
+
60
+ if backend
61
+ @backend_class = load_backend(backend).
62
+ build_subclass(klass, backend_options)
63
+
64
+ backend_class.setup_model(klass, names)
65
+
66
+ @names.each do |name|
67
+ klass.register_mobility_backend_class(name, @backend_class)
68
+ end
69
+
70
+ backend_class
71
+ end
72
+ end
73
+
74
+ # Include backend name in inspect string.
75
+ # @return [String]
76
+ def inspect
77
+ "#<Translations (#{backend}) @names=#{names.join(", ")}>"
78
+ end
79
+
80
+ def load_backend(backend)
81
+ Backends.load_backend(backend)
82
+ rescue Backends::LoadError => e
83
+ raise e, "could not find a #{backend} backend. Did you forget to include an ORM plugin like active_record or sequel?"
84
+ end
85
+
86
+ # Override default argument-handling in DSL to store kwargs passed along
87
+ # with plugin name.
88
+ def self.configure_default(defaults, key, *args, **kwargs)
89
+ defaults[key] = [args[0], kwargs] unless args.empty?
90
+ end
91
+
92
+ module InstanceMethods
93
+ # Return a new backend for an attribute name.
94
+ # @return [Hash] Hash of attribute names and backend instances
95
+ # @api private
96
+ def mobility_backends
97
+ @mobility_backends ||= ::Hash.new do |hash, name|
98
+ next hash[name.to_sym] if String === name
99
+ hash[name] = self.class.mobility_backend_class(name).new(self, name.to_s)
100
+ end
101
+ end
102
+
103
+ def initialize_dup(other)
104
+ @mobility_backends = nil
105
+ super
106
+ end
107
+ end
108
+
109
+ module ClassMethods
110
+ # Return backend class for a given attribute name.
111
+ # @param [Symbol,String] Name of attribute
112
+ # @return [Class] Backend class
113
+ def mobility_backend_class(name)
114
+ mobility_backend_classes.fetch(name.to_sym)
115
+ rescue KeyError
116
+ raise KeyError, "No backend for: #{name}"
117
+ end
118
+
119
+ def register_mobility_backend_class(name, backend_class)
120
+ mobility_backend_classes[name.to_sym] = backend_class
121
+ end
122
+
123
+ def inherited(klass)
124
+ klass.mobility_backend_classes.merge!(@mobility_backend_classes)
125
+ super
126
+ end
127
+
128
+ protected
129
+
130
+ def mobility_backend_classes
131
+ @mobility_backend_classes ||= {}
132
+ end
133
+ end
134
+ end
135
+
136
+ register_plugin(:backend, Backend)
137
+ end
138
+ end
@@ -0,0 +1,34 @@
1
+ # frozen-string-literal: true
2
+ module Mobility
3
+ module Plugins
4
+ =begin
5
+
6
+ Defines convenience methods for accessing backends, of the form
7
+ "<name>_backend". The format for this method can be customized by passing a
8
+ different format string as the plugin option.
9
+
10
+ =end
11
+ module BackendReader
12
+ extend Plugin
13
+
14
+ default true
15
+ requires :backend
16
+
17
+ initialize_hook do |*names|
18
+ if backend_reader = options[:backend_reader]
19
+ backend_reader = "%s_backend" if backend_reader == true
20
+
21
+ names.each do |name|
22
+ module_eval <<-EOM, __FILE__, __LINE__ + 1
23
+ def #{backend_reader % name}
24
+ mobility_backends[:#{name}]
25
+ end
26
+ EOM
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ register_plugin(:backend_reader, BackendReader)
33
+ end
34
+ end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require "mobility/plugins/cache/translation_cacher"
3
2
 
4
3
  module Mobility
5
4
  module Plugins
@@ -21,34 +20,70 @@ Values are added to the cache in two ways:
21
20
 
22
21
  =end
23
22
  module Cache
23
+ extend Plugin
24
+
25
+ default true
26
+ requires :backend, include: :before
27
+
24
28
  # Applies cache plugin to attributes.
25
- # @param [Attributes] attributes
26
- # @param [Boolean] option
27
- def self.apply(attributes, option)
28
- if option
29
- backend_class = attributes.backend_class
30
- backend_class.include(self) unless backend_class.apply_plugin(:cache)
31
-
32
- model_class = attributes.model_class
33
- model_class.include BackendResetter.for(model_class).new(attributes.names) { clear_cache }
29
+ included_hook do |_, backend_class|
30
+ if options[:cache]
31
+ backend_class.include(BackendMethods) unless backend_class.apply_plugin(:cache)
34
32
  end
35
33
  end
36
34
 
37
- # @group Backend Accessors
38
- #
39
- # @!macro backend_reader
40
- # @!method read(locale, value, options = {})
41
- # @option options [Boolean] cache *false* to disable cache.
42
- include TranslationCacher.new(:read)
43
-
44
- # @!macro backend_writer
45
- # @option options [Boolean] cache
46
- # *false* to disable cache.
47
- def write(locale, value, **options)
48
- return super if options.delete(:cache) == false
49
- cache[locale] = super
35
+ private
36
+
37
+ # Used in ORM cache plugins
38
+ def define_cache_hooks(klass, *reset_methods)
39
+ mod = self
40
+ private_methods = reset_methods & klass.private_instance_methods
41
+ reset_methods.each do |method_name|
42
+ define_method method_name do |*args|
43
+ super(*args).tap do
44
+ mod.names.each { |name| mobility_backends[name].clear_cache }
45
+ end
46
+ end
47
+ end
48
+ klass.class_eval { private(*private_methods) }
49
+ end
50
+
51
+ module BackendMethods
52
+ # @group Backend Accessors
53
+ #
54
+ # @!macro backend_reader
55
+ # @!method read(locale, value, options = {})
56
+ # @option options [Boolean] cache *false* to disable cache.
57
+ def read(locale, **options)
58
+ return super(locale, options) if options.delete(:cache) == false
59
+ if cache.has_key?(locale)
60
+ cache[locale]
61
+ else
62
+ cache[locale] = super(locale, options)
63
+ end
64
+ end
65
+
66
+ # @!macro backend_writer
67
+ # @option options [Boolean] cache
68
+ # *false* to disable cache.
69
+ def write(locale, value, **options)
70
+ return super if options.delete(:cache) == false
71
+ cache[locale] = super
72
+ end
73
+ # @!endgroup
74
+
75
+ def clear_cache
76
+ @cache = {}
77
+ end
78
+
79
+ private
80
+
81
+ def cache
82
+ @cache ||= {}
83
+ end
50
84
  end
51
- # @!endgroup
52
85
  end
86
+
87
+ register_plugin(:cache, Cache)
53
88
  end
54
89
  end
@@ -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