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
@@ -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
@@ -0,0 +1,50 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Mobility
4
+ module Plugins
5
+ module Reader
6
+ =begin
7
+
8
+ Defines attribute reader that delegates to +Mobility::Backend#read+.
9
+
10
+ =end
11
+ extend Plugin
12
+
13
+ default true
14
+ requires :backend
15
+
16
+ initialize_hook do |*names, **|
17
+ if options[:reader]
18
+ names.each do |name|
19
+ class_eval <<-EOM, __FILE__, __LINE__ + 1
20
+ def #{name}(locale: nil, **options)
21
+ #{Reader.setup_source}
22
+ mobility_backends[:#{name}].read(locale, options)
23
+ end
24
+ EOM
25
+ class_eval <<-EOM, __FILE__, __LINE__ + 1
26
+ def #{name}?(locale: nil, **options)
27
+ #{Reader.setup_source}
28
+ mobility_backends[:#{name}].present?(locale, options)
29
+ end
30
+ EOM
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.setup_source
36
+ <<-EOL
37
+ return super() if options[:super]
38
+ if (locale &&= locale.to_sym)
39
+ #{"Mobility.enforce_available_locales!(locale)" if I18n.enforce_available_locales}
40
+ options[:locale] = true
41
+ else
42
+ locale = Mobility.locale
43
+ end
44
+ EOL
45
+ end
46
+ end
47
+
48
+ register_plugin(:reader, Reader)
49
+ end
50
+ end
@@ -1,6 +1,40 @@
1
+ require "sequel"
2
+ raise VersionNotSupportedError, "Mobility is only compatible with Sequel 4.0 and greater" if ::Sequel::MAJOR < 4
3
+ require "sequel/plugins/mobility"
4
+ unless defined?(ActiveSupport::Inflector)
5
+ # TODO: avoid automatically including the inflector extension
6
+ require "sequel/extensions/inflector"
7
+ end
8
+ require "sequel/plugins/dirty"
9
+ require_relative "./sequel/backend"
10
+ require_relative "./sequel/dirty"
11
+ require_relative "./sequel/cache"
12
+ require_relative "./sequel/query"
13
+
1
14
  module Mobility
2
15
  module Plugins
3
16
  module Sequel
17
+ extend Plugin
18
+
19
+ requires :sequel_backend, include: :after
20
+ requires :sequel_dirty
21
+ requires :sequel_cache
22
+ requires :sequel_query
23
+
24
+ included_hook do |klass|
25
+ unless sequel_class?(klass)
26
+ name = klass.name || klass.to_s
27
+ raise TypeError, "#{name} should be a subclass of Sequel::Model to use the sequel plugin"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def sequel_class?(klass)
34
+ klass < ::Sequel::Model
35
+ end
4
36
  end
37
+
38
+ register_plugin(:sequel, Sequel)
5
39
  end
6
40
  end
@@ -0,0 +1,25 @@
1
+ module Mobility
2
+ module Plugins
3
+ module Sequel
4
+ module Backend
5
+ extend Plugin
6
+
7
+ requires :backend, include: :before
8
+
9
+ def load_backend(backend)
10
+ if Symbol === backend
11
+ require "mobility/backends/sequel/#{backend}"
12
+ Backends.load_backend("sequel_#{backend}".to_sym)
13
+ else
14
+ super
15
+ end
16
+ rescue LoadError => e
17
+ raise unless e.message =~ /sequel\/#{backend}/
18
+ super
19
+ end
20
+ end
21
+ end
22
+
23
+ register_plugin(:sequel_backend, Sequel::Backend)
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Mobility
4
+ module Plugins
5
+ module Sequel
6
+ =begin
7
+
8
+ Adds hook to clear Mobility cache when +refresh+ is called on Sequel model.
9
+
10
+ =end
11
+ module Cache
12
+ extend Plugin
13
+
14
+ requires :cache, include: false
15
+
16
+ included_hook do |klass|
17
+ define_cache_hooks(klass, :refresh) if options[:cache]
18
+ end
19
+ end
20
+ end
21
+
22
+ register_plugin(:sequel_cache, Sequel::Cache)
23
+ end
24
+ end
@@ -13,29 +13,37 @@ Automatically includes dirty plugin in model class when enabled.
13
13
  =end
14
14
  module Sequel
15
15
  module Dirty
16
- # Builds module which overrides dirty methods to handle translated as
17
- # well as normal (untranslated) attributes.
18
- class MethodsBuilder < Module
19
- def initialize(*attribute_names)
20
- # Although we load the plugin in the included callback method, we
21
- # need to include this module here in advance to ensure that its
22
- # instance methods are included *before* the ones defined here.
23
- include ::Sequel::Plugins::Dirty::InstanceMethods
24
-
25
- %w[initial_value column_change column_changed? reset_column].each do |method_name|
26
- define_method method_name do |column|
27
- if attribute_names.map(&:to_sym).include?(column)
28
- super(Mobility.normalize_locale_accessor(column).to_sym)
29
- else
30
- super(column)
31
- end
32
- end
33
- end
34
- end
16
+ extend Plugin
17
+
18
+ requires :dirty, include: false
35
19
 
36
- def included(model_class)
20
+ initialize_hook do
21
+ # Although we load the plugin in the included callback method, we
22
+ # need to include this module here in advance to ensure that its
23
+ # instance methods are included *before* the ones defined here.
24
+ include ::Sequel::Plugins::Dirty::InstanceMethods
25
+ end
26
+
27
+ included_hook do |klass, backend_class|
28
+ if options[:dirty]
37
29
  # this just adds Sequel::Plugins::Dirty to @plugins
38
- model_class.plugin :dirty
30
+ klass.plugin :dirty
31
+ define_dirty_methods(names)
32
+ backend_class.include BackendMethods
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def define_dirty_methods(names)
39
+ %w[initial_value column_change column_changed? reset_column].each do |method_name|
40
+ define_method method_name do |column|
41
+ if names.map(&:to_sym).include?(column)
42
+ super(Mobility.normalize_locale_accessor(column).to_sym)
43
+ else
44
+ super(column)
45
+ end
46
+ end
39
47
  end
40
48
  end
41
49
 
@@ -55,7 +63,10 @@ Automatically includes dirty plugin in model class when enabled.
55
63
  end
56
64
  # @!endgroup
57
65
  end
66
+
58
67
  end
59
68
  end
69
+
70
+ register_plugin(:sequel_dirty, Sequel::Dirty)
60
71
  end
61
72
  end
@@ -8,11 +8,18 @@ See ActiveRecord::Query plugin.
8
8
  =end
9
9
  module Sequel
10
10
  module Query
11
- class << self
12
- def apply(attributes)
13
- attributes.model_class.class_eval do
11
+ extend Plugin
12
+
13
+ requires :query, include: false
14
+
15
+ included_hook do |klass, _|
16
+ plugin = self
17
+ if options[:query]
18
+ raise MissingBackend, "backend required for Query plugin" unless backend_class
19
+
20
+ klass.class_eval do
14
21
  extend QueryMethod
15
- singleton_class.send :alias_method, Mobility.query_method, :__mobility_query_dataset__
22
+ singleton_class.send :alias_method, plugin.query_method, :__mobility_query_dataset__
16
23
  end
17
24
  end
18
25
  end
@@ -95,7 +102,7 @@ See ActiveRecord::Query plugin.
95
102
 
96
103
  class << self
97
104
  def build(dataset, query_method, query_conds, &block)
98
- return yield unless Hash === query_conds.first
105
+ return yield unless ::Hash === query_conds.first
99
106
 
100
107
  cond = query_conds.first.dup
101
108
  locale = cond.delete(:locale) || Mobility.locale
@@ -109,7 +116,7 @@ See ActiveRecord::Query plugin.
109
116
  keys, predicates = cond.keys, []
110
117
  model = dataset.model
111
118
 
112
- query_map = model.mobility_modules.inject(IDENTITY) do |qm, mod|
119
+ query_map = attribute_modules(model).inject(IDENTITY) do |qm, mod|
113
120
  i18n_keys = mod.names.map(&:to_sym) & keys
114
121
  next qm if i18n_keys.empty?
115
122
 
@@ -127,6 +134,10 @@ See ActiveRecord::Query plugin.
127
134
  query_map[dataset.public_send(query_method, ::Sequel.&(*predicates))]
128
135
  end
129
136
 
137
+ def attribute_modules(model)
138
+ model.ancestors.grep(::Mobility::Translations)
139
+ end
140
+
130
141
  def build_predicate(op, values)
131
142
  vals = values.is_a?(Array) ? values.uniq: [values]
132
143
  vals = vals.first if vals.size == 1
@@ -135,6 +146,10 @@ See ActiveRecord::Query plugin.
135
146
  end
136
147
  end
137
148
  end
149
+
150
+ class MissingBackend < Mobility::Error; end
138
151
  end
152
+
153
+ register_plugin(:sequel_query, Sequel::Query)
139
154
  end
140
155
  end
@@ -0,0 +1,44 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Mobility
4
+ module Plugins
5
+ module Writer
6
+ =begin
7
+
8
+ Defines attribute writer that delegates to +Mobility::Backend#write+.
9
+
10
+ =end
11
+ extend Plugin
12
+
13
+ default true
14
+ requires :backend
15
+
16
+ initialize_hook do |*names|
17
+ if options[:writer]
18
+ names.each do |name|
19
+ class_eval <<-EOM, __FILE__, __LINE__ + 1
20
+ def #{name}=(value, locale: nil, **options)
21
+ #{Writer.setup_source}
22
+ mobility_backends[:#{name}].write(locale, value, options)
23
+ end
24
+ EOM
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.setup_source
30
+ <<-EOL
31
+ return super(value) if options[:super]
32
+ if (locale &&= locale.to_sym)
33
+ #{"Mobility.enforce_available_locales!(locale)" if I18n.enforce_available_locales}
34
+ options[:locale] = true
35
+ else
36
+ locale = Mobility.locale
37
+ end
38
+ EOL
39
+ end
40
+ end
41
+
42
+ register_plugin(:writer, Writer)
43
+ end
44
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+ require "mobility/pluggable"
3
+
4
+ module Mobility
5
+ =begin
6
+
7
+ Module containing translation accessor methods and other methods for accessing
8
+ translations.
9
+
10
+ Normally this class will be created by calling +translates+ on the model class,
11
+ added when extending {Mobility}, but it can also be used independent of that
12
+ macro.
13
+
14
+ ==Including Translations in a Class
15
+
16
+ Since {Translations} is a subclass of +Module+, including an instance of it is
17
+ like including a module. Options passed to the initializer are passed to
18
+ plugins (if those plugins have been enabled).
19
+
20
+ We can first enable plugins by subclassing `Mobility::Translations`, and
21
+ calling +plugins+ to enable any plugins we want to use. We want reader and
22
+ writer methods for accessing translations, so we enable those plugins:
23
+
24
+ class Translations < Mobility::Translations
25
+ plugins do
26
+ reader
27
+ writer
28
+ end
29
+ end
30
+
31
+ Both `reader` and `writer` depend on the `backend` plugin, so this is also enabled.
32
+
33
+ Then create an instance like this:
34
+
35
+ Translations.new("title", backend: :my_backend)
36
+
37
+ This will generate an anonymous module that behaves approximately like this:
38
+
39
+ Module.new do
40
+ # From Mobility::Plugins::Backend module
41
+ #
42
+ def mobility_backends
43
+ # Returns a memoized hash with attribute name keys and backend instance
44
+ # values. When a key is fetched from the hash, the hash calls
45
+ # +self.class.mobility_backend_class(name)+ (where +name+ is the
46
+ # attribute name) to get the backend class, then instantiate it (passing
47
+ # the model instance and attribute name to its initializer) and return it.
48
+ end
49
+
50
+ # From Mobility::Plugins::Reader module
51
+ #
52
+ def title(locale: Mobility.locale)
53
+ mobility_backends[:title].read(locale)
54
+ end
55
+
56
+ def title?(locale: Mobility.locale)
57
+ mobility_backends[:title].read(locale).present?
58
+ end
59
+
60
+ # From Mobility::Plugins::Writer module
61
+ def title=(value, locale: Mobility.locale)
62
+ mobility_backends[:title].write(locale, value)
63
+ end
64
+ end
65
+
66
+ Including this module into a model class will thus add the backends method and
67
+ reader and writer methods for accessing translations. Other plugins (e.g.
68
+ fallbacks, cache) modify the result returned by the backend, by hooking into
69
+ the +included+ callback method on the module, see {Mobility::Plugin} for
70
+ details.
71
+
72
+ ==Setting up the Model Class
73
+
74
+ Accessor methods alone are of limited use without a hook to actually modify the
75
+ model class. This hook is provided by the {Backend::Setup#setup_model} method,
76
+ which is added to every backend class when it includes the {Backend} module.
77
+
78
+ Assuming the backend has defined a setup block by calling +setup+, this block
79
+ will be called when {Translations} is {#included} in the model class, passed
80
+ attributes and options defined when the backend was defined on the model class.
81
+ This allows a backend to do things like (for example) define associations on a
82
+ model class required by the backend, as happens in the {Backends::KeyValue} and
83
+ {Backends::Table} backends.
84
+
85
+ Since setup blocks are evaluated on the model class, it is possible that
86
+ backends can conflict (for example, overwriting previously defined methods).
87
+ Care should be taken to avoid defining methods on the model class, or where
88
+ necessary, ensure that names are defined in such a way as to avoid conflicts
89
+ with other backends.
90
+
91
+ =end
92
+ class Translations < Pluggable
93
+ include Plugins.load_plugin(:attributes)
94
+ end
95
+ end