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,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require "mobility/backend"
3
+ require "mobility/arel"
4
+
1
5
  module Mobility
2
6
  module Backends
3
7
  module ActiveRecord
@@ -73,5 +73,7 @@ can be run again to add new attributes or locales.)
73
73
  end.compact
74
74
  end
75
75
  end
76
+
77
+ register_backend(:active_record_column, ActiveRecord::Column)
76
78
  end
77
79
  end
@@ -71,7 +71,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
71
71
  private
72
72
 
73
73
  def get_column_type
74
- options[:model_class].type_for_attribute(options[:column_name].to_s).try(:type).tap do |type|
74
+ model_class.type_for_attribute(options[:column_name].to_s).try(:type).tap do |type|
75
75
  unless %i[json jsonb].include? type
76
76
  raise InvalidColumnType, "#{options[:column_name]} must be a column of type json or jsonb"
77
77
  end
@@ -122,7 +122,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
122
122
 
123
123
  class Coder
124
124
  def self.dump(obj)
125
- if obj.is_a? Hash
125
+ if obj.is_a? ::Hash
126
126
  obj.inject({}) do |translations, (locale, value)|
127
127
  value.each do |k, v|
128
128
  (translations[locale] ||= {})[k] = v if v.present?
@@ -141,5 +141,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
141
141
 
142
142
  class InvalidColumnType < StandardError; end
143
143
  end
144
+
145
+ register_backend(:active_record_container, ActiveRecord::Container)
144
146
  end
145
147
  end
@@ -32,5 +32,7 @@ Implements the {Mobility::Backends::Hstore} backend for ActiveRecord models.
32
32
  end
33
33
  end
34
34
  end
35
+
36
+ register_backend(:active_record_hstore, ActiveRecord::Hstore)
35
37
  end
36
38
  end
@@ -40,5 +40,7 @@ Implements the {Mobility::Backends::Json} backend for ActiveRecord models.
40
40
  end
41
41
  end
42
42
  end
43
+
44
+ register_backend(:active_record_json, ActiveRecord::Json)
43
45
  end
44
46
  end
@@ -40,5 +40,7 @@ Implements the {Mobility::Backends::Jsonb} backend for ActiveRecord models.
40
40
  end
41
41
  end
42
42
  end
43
+
44
+ register_backend(:active_record_jsonb, ActiveRecord::Jsonb)
43
45
  end
44
46
  end
@@ -39,7 +39,7 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
39
39
  options[:association_name] ||= :"#{options[:type]}_translations"
40
40
  options[:class_name] ||= Mobility::ActiveRecord.const_get("#{type.capitalize}Translation")
41
41
  end
42
- options[:table_alias_affix] = "#{options[:model_class]}_%s_#{options[:association_name]}"
42
+ options[:table_alias_affix] = "#{model_class}_%s_#{options[:association_name]}"
43
43
  rescue NameError
44
44
  raise ArgumentError, "You must define a Mobility::ActiveRecord::#{type.capitalize}Translation class."
45
45
  end
@@ -119,7 +119,7 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
119
119
  # The title predicate has a non-nil value, so we can use an INNER JOIN,
120
120
  # whereas we are searching for nil content, which requires an OUTER JOIN.
121
121
  #
122
- class Visitor < Arel::Visitor
122
+ class Visitor < ::Mobility::Arel::Visitor
123
123
  private
124
124
 
125
125
  def visit_Arel_Nodes_Equality(object)
@@ -137,7 +137,7 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
137
137
  alias :visit_Array :visit_collection
138
138
 
139
139
  def visit_Arel_Nodes_Or(object)
140
- [object.left, object.right].map(&method(:visit)).compact.inject(&:merge).
140
+ [object.left, object.right].map(&method(:visit)).compact.inject(:merge).
141
141
  transform_values { OUTER_JOIN }
142
142
  end
143
143
 
@@ -212,5 +212,7 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
212
212
  end
213
213
  end
214
214
  end
215
+
216
+ register_backend(:active_record_key_value, ActiveRecord::KeyValue)
215
217
  end
216
218
  end
@@ -30,7 +30,7 @@ Internal class used by ActiveRecord backends backed by a Postgres data type
30
30
 
31
31
  class Coder
32
32
  def self.dump(obj)
33
- if obj.is_a? Hash
33
+ if obj.is_a? ::Hash
34
34
  obj.inject({}) do |translations, (locale, value)|
35
35
  translations[locale] = value if value.present?
36
36
  translations
@@ -72,5 +72,7 @@ Implements {Mobility::Backends::Serialized} backend for ActiveRecord models.
72
72
  EOM
73
73
  end
74
74
  end
75
+
76
+ register_backend(:active_record_serialized, ActiveRecord::Serialized)
75
77
  end
76
78
  end
@@ -101,7 +101,7 @@ columns to that table.
101
101
  # @option options [Symbol] subclass_name (:Translation) Name of subclass
102
102
  # to append to model class to generate translation class
103
103
  def configure(options)
104
- table_name = options[:model_class].table_name
104
+ table_name = model_class.table_name
105
105
  options[:table_name] ||= "#{table_name.singularize}_translations"
106
106
  options[:foreign_key] ||= table_name.downcase.singularize.camelize.foreign_key
107
107
  if (association_name = options[:association_name]).present?
@@ -153,7 +153,7 @@ columns to that table.
153
153
  def already_joined?(relation, locale, join_type)
154
154
  if join = get_join(relation, locale)
155
155
  return true if (join_type == Visitor::OUTER_JOIN) || (Visitor::INNER_JOIN === join)
156
- relation.joins_values = relation.joins_values - [join]
156
+ relation.joins_values -= [join]
157
157
  end
158
158
  false
159
159
  end
@@ -265,7 +265,7 @@ columns to that table.
265
265
  touch: true
266
266
 
267
267
  before_save do
268
- required_attributes = self.class.mobility_attributes & translation_class.attribute_names
268
+ required_attributes = translation_class.attribute_names.select { |name| self.class.mobility_attribute?(name) }
269
269
  send(association_name).destroy_empty_translations(required_attributes)
270
270
  end
271
271
 
@@ -304,5 +304,7 @@ columns to that table.
304
304
  end
305
305
  end
306
306
  end
307
+
308
+ register_backend(:active_record_table, ActiveRecord::Table)
307
309
  end
308
310
  end
@@ -27,8 +27,6 @@ be ignored if set, since it would cause a conflict with column accessors.
27
27
 
28
28
  =end
29
29
  module Column
30
- extend Backend::OrmDelegator
31
-
32
30
  # Returns name of column where translated attribute is stored
33
31
  # @param [Symbol] locale
34
32
  # @return [String]
@@ -44,10 +42,6 @@ be ignored if set, since it would cause a conflict with column accessors.
44
42
  normalized_locale = Mobility.normalize_locale(locale)
45
43
  "#{attribute}_#{normalized_locale}".to_sym
46
44
  end
47
-
48
- def self.included(base)
49
- base.extend Backend::OrmDelegator
50
- end
51
45
  end
52
46
  end
53
47
  end
@@ -19,7 +19,8 @@ stored).
19
19
 
20
20
  =end
21
21
  module Container
22
- extend Backend::OrmDelegator
23
22
  end
23
+
24
+ register_backend(:container, Container)
24
25
  end
25
26
  end
@@ -0,0 +1,39 @@
1
+ module Mobility
2
+ module Backends
3
+ =begin
4
+
5
+ Backend which stores translations in an in-memory hash.
6
+
7
+ =end
8
+ class Hash
9
+ include Backend
10
+
11
+ # @!group Backend Accessors
12
+ # @!macro backend_reader
13
+ # @return [Object]
14
+ def read(locale, _ = {})
15
+ translations[locale]
16
+ end
17
+
18
+ # @!macro backend_writer
19
+ # @return [Object]
20
+ def write(locale, value, _ = {})
21
+ translations[locale] = value
22
+ end
23
+ # @!endgroup
24
+
25
+ # @!macro backend_iterator
26
+ def each_locale
27
+ translations.each { |l, _| yield l }
28
+ end
29
+
30
+ private
31
+
32
+ def translations
33
+ @translations ||= {}
34
+ end
35
+ end
36
+
37
+ register_backend(:hash, Hash)
38
+ end
39
+ end
@@ -17,7 +17,6 @@ Prefix and suffix to add to attribute name to generate hstore column name.
17
17
 
18
18
  =end
19
19
  module Hstore
20
- extend Backend::OrmDelegator
21
20
  end
22
21
  end
23
22
  end
@@ -16,7 +16,6 @@ Prefix and suffix to add to attribute name to generate json column name.
16
16
 
17
17
  =end
18
18
  module Json
19
- extend Backend::OrmDelegator
20
19
  end
21
20
  end
22
21
  end
@@ -16,7 +16,6 @@ Prefix and suffix to add to attribute name to generate jsonb column name.
16
16
 
17
17
  =end
18
18
  module Jsonb
19
- extend Backend::OrmDelegator
20
19
  end
21
20
  end
22
21
  end
@@ -44,8 +44,6 @@ other backends on model (otherwise one will overwrite the other).
44
44
 
45
45
  =end
46
46
  module KeyValue
47
- extend Backend::OrmDelegator
48
-
49
47
  # @!method association_name
50
48
  # Returns the name of the polymorphic association.
51
49
  # @return [Symbol] Name of the association
@@ -87,7 +85,7 @@ other backends on model (otherwise one will overwrite the other).
87
85
  module ClassMethods
88
86
  # @!group Backend Configuration
89
87
  # @option options [Symbol,String] type Column type to use
90
- # @option options [Symbol] associaiton_name (:<type>_translations) Name
88
+ # @option options [Symbol] association_name (:<type>_translations) Name
91
89
  # of association method, defaults to +<type>_translations+
92
90
  # @option options [Symbol] class_name Translation class, defaults to
93
91
  # +Mobility::<ORM>::<type>Translation+
@@ -98,20 +96,13 @@ other backends on model (otherwise one will overwrite the other).
98
96
  options[:association_name] &&= options[:association_name].to_sym
99
97
  options[:class_name] &&= Util.constantize(options[:class_name])
100
98
  if !(options[:type] || (options[:class_name] && options[:association_name]))
101
- # TODO: Remove warning and raise ArgumentError in v1.0
102
- warn %{
103
- WARNING: In previous versions, the Mobility KeyValue backend defaulted to a
104
- text type column, but this behavior is now deprecated and will be removed in
105
- the next release. Either explicitly specify the type by passing type: :text in
106
- each translated model, or set a default option in your configuration.
107
- }
108
- options[:type] = :text
99
+ raise ArgumentError, "KeyValue backend requires an explicit type option, either text or string."
109
100
  end
110
101
  end
111
102
 
112
103
  # Apply custom processing for plugin
113
- # @param (see Backend::Setup#apply_plugin)
114
- # @return (see Backend::Setup#apply_plugin)
104
+ # @param (see Backend::ClassMethods#apply_plugin)
105
+ # @return (see Backend::ClassMethods#apply_plugin)
115
106
  def apply_plugin(name)
116
107
  if name == :cache
117
108
  include self::Cache
@@ -127,7 +118,24 @@ each translated model, or set a default option in your configuration.
127
118
  end
128
119
 
129
120
  module Cache
130
- include Plugins::Cache::TranslationCacher.new(:translation_for)
121
+ def translation_for(locale, **options)
122
+ return super(locale, options) if options.delete(:cache) == false
123
+ if cache.has_key?(locale)
124
+ cache[locale]
125
+ else
126
+ cache[locale] = super(locale, options)
127
+ end
128
+ end
129
+
130
+ def clear_cache
131
+ @cache = {}
132
+ end
133
+
134
+ private
135
+
136
+ def cache
137
+ @cache ||= {}
138
+ end
131
139
  end
132
140
  end
133
141
  end
@@ -20,5 +20,7 @@ Backend which does absolutely nothing. Mostly for testing purposes.
20
20
  def self.configure(_); end
21
21
  # @!endgroup
22
22
  end
23
+
24
+ register_backend(:null, Null)
23
25
  end
24
26
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+ require "mobility/backend"
3
+
1
4
  module Mobility
2
5
  module Backends
3
6
  module Sequel
@@ -50,5 +50,7 @@ Implements the {Mobility::Backends::Column} backend for Sequel models.
50
50
  end.compact
51
51
  end
52
52
  end
53
+
54
+ register_backend(:sequel_column, Sequel::Column)
53
55
  end
54
56
  end
@@ -44,7 +44,7 @@ Implements the {Mobility::Backends::Container} backend for Sequel models.
44
44
  def self.configure(options)
45
45
  options[:column_name] ||= :translations
46
46
  options[:column_name] = options[:column_name].to_sym
47
- column_name, db_schema = options[:column_name], options[:model_class].db_schema
47
+ column_name, db_schema = options[:column_name], model_class.db_schema
48
48
  options[:column_type] = db_schema[column_name] && (db_schema[column_name][:db_type]).to_sym
49
49
  unless %i[json jsonb].include?(options[:column_type])
50
50
  raise InvalidColumnType, "#{options[:column_name]} must be a column of type json or jsonb"
@@ -114,5 +114,7 @@ Implements the {Mobility::Backends::Container} backend for Sequel models.
114
114
  end
115
115
  end
116
116
  end
117
+
118
+ register_backend(:sequel_container, Sequel::Container)
117
119
  end
118
120
  end
@@ -35,5 +35,7 @@ Implements the {Mobility::Backends::Hstore} backend for Sequel models.
35
35
  class HStoreOp < ::Sequel::Postgres::HStoreOp; end
36
36
  end
37
37
  end
38
+
39
+ register_backend(:sequel_hstore, Sequel::Hstore)
38
40
  end
39
41
  end
@@ -42,5 +42,7 @@ Implements the {Mobility::Backends::Json} backend for Sequel models.
42
42
  class JSONOp < ::Sequel::Postgres::JSONOp; end
43
43
  end
44
44
  end
45
+
46
+ register_backend(:sequel_json, Sequel::Json)
45
47
  end
46
48
  end
@@ -55,7 +55,7 @@ Implements the {Mobility::Backends::Jsonb} backend for Sequel models.
55
55
 
56
56
  def =~(other)
57
57
  case other
58
- when Integer, Hash
58
+ when Integer, ::Hash
59
59
  to_dash_arrow =~ other.to_json
60
60
  when NilClass
61
61
  ~to_question
@@ -66,5 +66,7 @@ Implements the {Mobility::Backends::Jsonb} backend for Sequel models.
66
66
  end
67
67
  end
68
68
  end
69
+
70
+ register_backend(:sequel_jsonb, Sequel::Jsonb)
69
71
  end
70
72
  end
@@ -36,7 +36,7 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
36
36
  options[:association_name] ||= :"#{options[:type]}_translations"
37
37
  options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translation")
38
38
  end
39
- options[:table_alias_affix] = "#{options[:model_class]}_%s_#{options[:association_name]}"
39
+ options[:table_alias_affix] = "#{model_class}_%s_#{options[:association_name]}"
40
40
  rescue NameError
41
41
  raise ArgumentError, "You must define a Mobility::Sequel::#{type.capitalize}Translation class."
42
42
  end
@@ -93,7 +93,7 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
93
93
  join_type = nils.empty? ? :inner : :left_outer
94
94
  # TODO: simplify to hash.transform_values { join_type } when
95
95
  # support for Ruby 2.3 is deprecated
96
- Hash[hash.keys.map { |key| [key, join_type] }]
96
+ ::Hash[hash.keys.map { |key| [key, join_type] }]
97
97
  else
98
98
  {}
99
99
  end
@@ -101,13 +101,13 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
101
101
  hash = visit(boolean.args, locale)
102
102
  # TODO: simplify to hash.transform_values { :inner } when
103
103
  # support for Ruby 2.3 is deprecated
104
- Hash[hash.keys.map { |key| [key, :inner] }]
104
+ ::Hash[hash.keys.map { |key| [key, :inner] }]
105
105
  elsif boolean.op == :OR
106
106
  hash = boolean.args.map { |op| visit(op, locale) }.
107
- compact.inject(&:merge)
107
+ compact.inject(:merge)
108
108
  # TODO: simplify to hash.transform_values { :left_outer } when
109
109
  # support for Ruby 2.3 is deprecated
110
- Hash[hash.keys.map { |key| [key, :left_outer] }]
110
+ ::Hash[hash.keys.map { |key| [key, :left_outer] }]
111
111
  else
112
112
  visit(boolean.args, locale)
113
113
  end
@@ -153,7 +153,7 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
153
153
  end
154
154
  define_method :after_save do
155
155
  super()
156
- attributes.each { |attribute| public_send(Backend.method_name(attribute)).save_translations }
156
+ attributes.each { |attribute| mobility_backends[attribute].save_translations }
157
157
  end
158
158
  end
159
159
  include callback_methods
@@ -202,5 +202,7 @@ Implements the {Mobility::Backends::KeyValue} backend for Sequel models.
202
202
  end
203
203
  end
204
204
  end
205
+
206
+ register_backend(:sequel_key_value, Sequel::KeyValue)
205
207
  end
206
208
  end