mobility 0.5.1 → 0.6.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -2
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +41 -1
  5. data/Gemfile.lock +3 -58
  6. data/README.md +22 -21
  7. data/lib/mobility.rb +3 -2
  8. data/lib/mobility/accumulator.rb +1 -2
  9. data/lib/mobility/active_model/backend_resetter.rb +1 -1
  10. data/lib/mobility/active_record.rb +12 -9
  11. data/lib/mobility/active_record/backend_resetter.rb +6 -7
  12. data/lib/mobility/active_record/uniqueness_validator.rb +12 -2
  13. data/lib/mobility/adapter.rb +1 -0
  14. data/lib/mobility/attributes.rb +3 -13
  15. data/lib/mobility/backends/active_record/column.rb +1 -0
  16. data/lib/mobility/backends/active_record/column/query_methods.rb +25 -20
  17. data/lib/mobility/backends/active_record/container/json_query_methods.rb +22 -16
  18. data/lib/mobility/backends/active_record/container/jsonb_query_methods.rb +19 -19
  19. data/lib/mobility/backends/active_record/hstore.rb +14 -12
  20. data/lib/mobility/backends/active_record/hstore/query_methods.rb +14 -5
  21. data/lib/mobility/backends/active_record/json.rb +21 -19
  22. data/lib/mobility/backends/active_record/json/query_methods.rb +16 -11
  23. data/lib/mobility/backends/active_record/jsonb.rb +21 -19
  24. data/lib/mobility/backends/active_record/jsonb/query_methods.rb +14 -5
  25. data/lib/mobility/backends/active_record/key_value.rb +9 -9
  26. data/lib/mobility/backends/active_record/key_value/query_methods.rb +53 -46
  27. data/lib/mobility/backends/active_record/pg_hash.rb +29 -25
  28. data/lib/mobility/backends/active_record/pg_query_methods.rb +76 -40
  29. data/lib/mobility/backends/active_record/query_methods.rb +17 -10
  30. data/lib/mobility/backends/active_record/serialized.rb +4 -2
  31. data/lib/mobility/backends/active_record/serialized/query_methods.rb +18 -15
  32. data/lib/mobility/backends/active_record/table.rb +21 -12
  33. data/lib/mobility/backends/active_record/table/query_methods.rb +82 -83
  34. data/lib/mobility/backends/hash_valued.rb +19 -0
  35. data/lib/mobility/backends/hstore.rb +3 -1
  36. data/lib/mobility/backends/json.rb +3 -1
  37. data/lib/mobility/backends/jsonb.rb +3 -1
  38. data/lib/mobility/backends/key_value.rb +32 -15
  39. data/lib/mobility/backends/sequel/column/query_methods.rb +16 -12
  40. data/lib/mobility/backends/sequel/container/json_query_methods.rb +25 -18
  41. data/lib/mobility/backends/sequel/container/jsonb_query_methods.rb +25 -18
  42. data/lib/mobility/backends/sequel/hstore.rb +14 -12
  43. data/lib/mobility/backends/sequel/hstore/query_methods.rb +18 -11
  44. data/lib/mobility/backends/sequel/json.rb +21 -19
  45. data/lib/mobility/backends/sequel/json/query_methods.rb +18 -11
  46. data/lib/mobility/backends/sequel/jsonb.rb +21 -19
  47. data/lib/mobility/backends/sequel/jsonb/query_methods.rb +18 -11
  48. data/lib/mobility/backends/sequel/key_value.rb +10 -11
  49. data/lib/mobility/backends/sequel/key_value/query_methods.rb +39 -34
  50. data/lib/mobility/backends/sequel/pg_hash.rb +37 -25
  51. data/lib/mobility/backends/sequel/pg_query_methods.rb +45 -20
  52. data/lib/mobility/backends/sequel/query_methods.rb +5 -0
  53. data/lib/mobility/backends/sequel/serialized.rb +18 -13
  54. data/lib/mobility/backends/sequel/serialized/query_methods.rb +10 -7
  55. data/lib/mobility/backends/sequel/table.rb +1 -1
  56. data/lib/mobility/backends/sequel/table/query_methods.rb +40 -35
  57. data/lib/mobility/plugins/cache/translation_cacher.rb +15 -15
  58. data/lib/mobility/plugins/default.rb +0 -7
  59. data/lib/mobility/plugins/fallbacks.rb +4 -0
  60. data/lib/mobility/sequel.rb +11 -5
  61. data/lib/mobility/sequel/backend_resetter.rb +6 -7
  62. data/lib/mobility/sequel/column_changes.rb +4 -4
  63. data/lib/mobility/version.rb +1 -1
  64. data/lib/rails/generators/mobility/backend_generators/base.rb +4 -0
  65. data/lib/rails/generators/mobility/backend_generators/table_backend.rb +0 -12
  66. data/lib/rails/generators/mobility/templates/column_translations.rb +2 -2
  67. data/lib/rails/generators/mobility/templates/create_string_translations.rb +5 -5
  68. data/lib/rails/generators/mobility/templates/create_text_translations.rb +5 -5
  69. data/lib/rails/generators/mobility/templates/initializer.rb +8 -0
  70. data/lib/rails/generators/mobility/templates/table_migration.rb +2 -3
  71. data/lib/rails/generators/mobility/templates/table_translations.rb +3 -4
  72. data/lib/rails/generators/mobility/translations_generator.rb +6 -5
  73. metadata +2 -3
  74. metadata.gz.sig +0 -0
  75. data/lib/mobility/backend/stringify_locale.rb +0 -18
@@ -3,51 +3,56 @@ require "mobility/backends/sequel/query_methods"
3
3
 
4
4
  module Mobility
5
5
  module Backends
6
- class Sequel::KeyValue::QueryMethods < Sequel::QueryMethods
7
- def initialize(attributes, association_name: nil, class_name: nil, **)
8
- super
6
+ module Sequel
7
+ class KeyValue::QueryMethods < QueryMethods
8
+ def initialize(attributes, association_name: nil, class_name: nil, **)
9
+ super
9
10
 
10
- define_join_method(association_name, class_name)
11
- define_query_methods(association_name)
12
- end
11
+ define_join_method(association_name, class_name)
12
+ define_query_methods(association_name)
13
+ end
13
14
 
14
- private
15
+ private
15
16
 
16
- def define_join_method(association_name, translation_class)
17
- define_method :"join_#{association_name}" do |*attributes, **options|
18
- attributes.inject(self) do |relation, attribute|
19
- join_type = options[:outer_join] ? :left_outer : :inner
20
- relation.join_table(join_type,
21
- translation_class.table_name,
22
- {
23
- key: attribute.to_s,
24
- locale: Mobility.locale.to_s,
25
- translatable_type: model.name,
26
- translatable_id: ::Sequel[:"#{model.table_name}"][:id]
27
- },
28
- table_alias: "#{attribute}_#{association_name}")
17
+ def define_join_method(association_name, translation_class)
18
+ define_method :"join_#{association_name}" do |*attributes, **options|
19
+ attributes.inject(self) do |relation, attribute|
20
+ join_type = options[:outer_join] ? :left_outer : :inner
21
+ relation.join_table(join_type,
22
+ translation_class.table_name,
23
+ {
24
+ key: attribute.to_s,
25
+ locale: Mobility.locale.to_s,
26
+ translatable_type: model.name,
27
+ translatable_id: ::Sequel[:"#{model.table_name}"][:id]
28
+ },
29
+ table_alias: "#{attribute}_#{association_name}")
30
+ end
29
31
  end
30
32
  end
31
- end
32
33
 
33
- def define_query_methods(association_name)
34
- q = self
34
+ def define_query_methods(association_name)
35
+ q = self
35
36
 
36
- %w[exclude or where].each do |method_name|
37
- define_method method_name do |*conds, &block|
38
- if i18n_keys = q.extract_attributes(conds.first)
39
- cond = conds.first.dup
40
- i18n_nulls = i18n_keys.select { |key| cond[key].nil? }
41
- i18n_keys.each { |attr| cond[::Sequel[:"#{attr}_#{association_name}"][:value]] = cond.delete(attr) }
42
- super(cond, &block).
43
- send("join_#{association_name}", *(i18n_keys - i18n_nulls), outer_join: method_name == "or").
44
- send("join_#{association_name}", *i18n_nulls, outer_join: true)
45
- else
46
- super(*conds, &block)
37
+ %w[exclude or where].each do |method_name|
38
+ define_method method_name do |*conds, &block|
39
+ if i18n_keys = q.extract_attributes(conds.first)
40
+ cond = conds.first.dup
41
+ i18n_nulls = i18n_keys.select { |key| cond[key].nil? }
42
+ i18n_keys.each do |attr|
43
+ cond[::Sequel[:"#{attr}_#{association_name}"][:value]] = q.collapse cond.delete(attr)
44
+ end
45
+ super(cond, &block).
46
+ send("join_#{association_name}", *(i18n_keys - i18n_nulls), outer_join: method_name == "or").
47
+ send("join_#{association_name}", *i18n_nulls, outer_join: true)
48
+ else
49
+ super(*conds, &block)
50
+ end
47
51
  end
48
52
  end
49
53
  end
50
54
  end
55
+ KeyValue.private_constant :QueryMethods
51
56
  end
52
57
  end
53
58
  end
@@ -2,7 +2,6 @@
2
2
  require "mobility/util"
3
3
  require "mobility/backends/sequel"
4
4
  require "mobility/backends/hash_valued"
5
- require "mobility/backend/stringify_locale"
6
5
  require "mobility/sequel/column_changes"
7
6
 
8
7
  module Mobility
@@ -13,36 +12,49 @@ Internal class used by Sequel backends backed by a Postgres data type (hstore,
13
12
  jsonb).
14
13
 
15
14
  =end
16
- class Sequel::PgHash
17
- include Sequel
18
- include HashValued
19
- include StringifyLocale
20
-
21
- # @!macro backend_iterator
22
- def each_locale
23
- super { |l| yield l.to_sym }
24
- end
15
+ module Sequel
16
+ class PgHash
17
+ include Sequel
18
+ include HashValued
25
19
 
26
- def translations
27
- model[attribute.to_sym]
28
- end
20
+ def read(locale, options = {})
21
+ super(locale.to_s, options)
22
+ end
23
+
24
+ def write(locale, value, options = {})
25
+ super(locale.to_s, value, options)
26
+ end
27
+
28
+ # @!macro backend_iterator
29
+ def each_locale
30
+ super { |l| yield l.to_sym }
31
+ end
32
+
33
+ def translations
34
+ model[column_name.to_sym]
35
+ end
29
36
 
30
- setup do |attributes|
31
- before_validation = Module.new do
32
- define_method :before_validation do
33
- attributes.each do |attribute|
34
- self[attribute.to_sym].delete_if { |_, v| Util.blank?(v) }
37
+ setup do |attributes, options|
38
+ column_affix = "#{options[:column_prefix]}%s#{options[:column_suffix]}"
39
+ columns = attributes.map { |attribute| (column_affix % attribute).to_sym }
40
+
41
+ before_validation = Module.new do
42
+ define_method :before_validation do
43
+ columns.each do |column|
44
+ self[column].delete_if { |_, v| Util.blank?(v) }
45
+ end
46
+ super()
35
47
  end
36
- super()
37
48
  end
38
- end
39
- include before_validation
40
- include Mobility::Sequel::HashInitializer.new(*attributes)
41
- include Mobility::Sequel::ColumnChanges.new(*attributes)
49
+ include before_validation
50
+ include Mobility::Sequel::HashInitializer.new(*columns)
51
+ include Mobility::Sequel::ColumnChanges.new(attributes, column_affix: column_affix)
42
52
 
43
- plugin :defaults_setter
44
- attributes.each { |attribute| default_values[attribute.to_sym] = {} }
53
+ plugin :defaults_setter
54
+ columns.each { |column| default_values[column] = {} }
55
+ end
45
56
  end
57
+ private_constant :PgHash
46
58
  end
47
59
  end
48
60
  end
@@ -1,25 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mobility
2
4
  module Backends
3
5
  module Sequel
4
6
  =begin
5
7
 
6
- Defines query methods for Postgres backends. Including class must define two
7
- private methods:
8
+ Internal module builder defining query methods for Postgres backends. Including
9
+ class must define three methods:
8
10
 
9
- - a private method +matches+ which takes a key (column name), value and
10
- locale and returns an SQL expression, and checks that the column has the
11
- specified value in the specified locale
12
- - a private method +has_locale+ which takes a key (column name) and locale, and
13
- returns an SQL expression which checks that the column has a value in the
14
- locale
11
+ - a method +matches+ which takes a key (column name) and a locale to match and
12
+ returns an SQL expression checking that the column has the specified value
13
+ in the specified locale
14
+ - a method +exists+ which takes a key (column name) and locale, and returns
15
+ an SQL expression which checks that the column has a value in the locale
16
+ - a method +quote+ which quotes the value to be matched
15
17
 
16
- (The +matches+ and +has_locale+ methods are implemented slightly differently
18
+ (The +matches+/+exists+/+quote+ methods are implemented slightly differently
17
19
  for hstore/json/jsonb/container backends.)
18
20
 
21
+ @see Mobility::Backends::Sequel::Json::QueryMethods
22
+ @see Mobility::Backends::Sequel::Jsonb::QueryMethods
23
+ @see Mobility::Backends::Sequel::Hstore::QueryMethods
24
+ @see Mobility::Backends::Sequel::Container::JsonQueryMethods
25
+ @see Mobility::Backends::Sequel::Container::JsonbQueryMethods
26
+
19
27
  =end
20
28
  module PgQueryMethods
21
- def initialize(attributes, _)
29
+ attr_reader :column_affix
30
+
31
+ def initialize(attributes, options)
22
32
  super
33
+ @column_affix = "#{options[:column_prefix]}%s#{options[:column_suffix]}"
23
34
  define_query_methods
24
35
  end
25
36
 
@@ -33,11 +44,23 @@ for hstore/json/jsonb/container backends.)
33
44
  def create_query!(cond, keys, invert = false)
34
45
  keys.map { |key|
35
46
  values = cond.delete(key)
36
- values = [values] unless values.is_a?(Array)
37
- values.map { |value| create_query_op(key, value, invert) }.inject(&:|)
47
+ values = values.is_a?(Array) ? values.uniq: [values]
48
+ create_query_op(key, values, invert)
38
49
  }.inject(invert ? :| : :&)
39
50
  end
40
51
 
52
+ def matches(_key, _locale)
53
+ raise NotImplementedError
54
+ end
55
+
56
+ def exists(_key, _locale)
57
+ raise NotImplementedError
58
+ end
59
+
60
+ def quote(_value)
61
+ raise NotImplementedError
62
+ end
63
+
41
64
  private
42
65
 
43
66
  def define_query_methods
@@ -66,24 +89,26 @@ for hstore/json/jsonb/container backends.)
66
89
  end
67
90
  end
68
91
 
69
- def create_query_op(key, value, invert)
92
+ def create_query_op(key, values, invert)
70
93
  locale = Mobility.locale.to_s
94
+ values = values.map(&method(:quote))
95
+ values = values.first if values.size == 1
96
+
97
+ match = matches(key, locale) =~ values
71
98
 
72
99
  if invert
73
- has_locale(key, locale) & ~matches(key, value, locale)
100
+ exists(key, locale) & ~match
74
101
  else
75
- value.nil? ? ~has_locale(key, locale) : matches(key, value, locale)
102
+ values.nil? ? ~exists(key, locale) : match
76
103
  end
77
104
  end
78
105
 
79
- def matches(_key, _value, _locale)
80
- raise NotImplementedError
81
- end
82
106
 
83
- def has_locale(_key, _locale)
84
- raise NotImplementedError
107
+ def column_name(attribute)
108
+ (column_affix % attribute).to_sym
85
109
  end
86
110
  end
111
+ private_constant :PgQueryMethods
87
112
  end
88
113
  end
89
114
  end
@@ -25,7 +25,12 @@ models. For details see backend-specific subclasses.
25
25
  def extract_attributes(cond)
26
26
  cond.is_a?(Hash) && Util.presence(cond.keys & @attributes)
27
27
  end
28
+
29
+ def collapse(value)
30
+ value.is_a?(Array) ? value.uniq : value
31
+ end
28
32
  end
33
+ private_constant :QueryMethods
29
34
  end
30
35
  end
31
36
  end
@@ -49,18 +49,20 @@ Sequel serialization plugin.
49
49
 
50
50
  setup do |attributes, options|
51
51
  format = options[:format]
52
+ column_affix = "#{options[:column_prefix]}%s#{options[:column_suffix]}"
53
+ columns = attributes.map { |attribute| (column_affix % attribute).to_sym }
54
+
52
55
  plugin :serialization
53
56
  plugin :serialization_modification_detection
54
57
 
55
- attributes.each do |attribute_|
56
- attribute = attribute_.to_sym
57
- self.serialization_map[attribute] = Serialized.serializer_for(format)
58
- self.deserialization_map[attribute] = Serialized.deserializer_for(format)
58
+ columns.each do |column|
59
+ self.serialization_map[column] = Serialized.serializer_for(format)
60
+ self.deserialization_map[column] = Serialized.deserializer_for(format)
59
61
  end
60
62
 
61
63
  method_overrides = Module.new do
62
64
  define_method :initialize_set do |values|
63
- attributes.each { |attribute| self[attribute.to_sym] = {}.send(:"to_#{format}") }
65
+ columns.each { |column| self[column] = {}.send(:"to_#{format}") }
64
66
  super(values)
65
67
  end
66
68
  end
@@ -74,13 +76,12 @@ Sequel serialization plugin.
74
76
  # Returns deserialized column value
75
77
  # @return [Hash]
76
78
  def translations
77
- attribute_ = attribute.to_sym
78
- if model.deserialized_values.has_key?(attribute_)
79
- model.deserialized_values[attribute_]
79
+ if model.deserialized_values.has_key?(column_name)
80
+ model.deserialized_values[column_name]
80
81
  elsif model.frozen?
81
- deserialize_value(attribute_, serialized_value)
82
+ deserialize_value(serialized_value)
82
83
  else
83
- model.deserialized_values[attribute_] = deserialize_value(attribute_, serialized_value)
84
+ model.deserialized_values[column_name] = deserialize_value(serialized_value)
84
85
  end
85
86
  end
86
87
 
@@ -96,12 +97,16 @@ Sequel serialization plugin.
96
97
 
97
98
  private
98
99
 
99
- def deserialize_value(column, value)
100
- model.send(:deserialize_value, column, value)
100
+ def deserialize_value(value)
101
+ model.send(:deserialize_value, column_name, value)
101
102
  end
102
103
 
103
104
  def serialized_value
104
- model[attribute.to_sym]
105
+ model[column_name]
106
+ end
107
+
108
+ def column_name
109
+ super.to_sym
105
110
  end
106
111
  end
107
112
  end
@@ -3,17 +3,20 @@ require "mobility/backends/sequel/query_methods"
3
3
 
4
4
  module Mobility
5
5
  module Backends
6
- class Sequel::Serialized::QueryMethods < Sequel::QueryMethods
7
- include Serialized
6
+ module Sequel
7
+ class Serialized::QueryMethods < QueryMethods
8
+ include Backends::Serialized
8
9
 
9
- def initialize(attributes, _)
10
- super
11
- q = self
10
+ def initialize(attributes, _)
11
+ super
12
+ q = self
12
13
 
13
- define_method :where do |*cond, &block|
14
- q.check_opts(cond.first) || super(*cond, &block)
14
+ define_method :where do |*cond, &block|
15
+ q.check_opts(cond.first) || super(*cond, &block)
16
+ end
15
17
  end
16
18
  end
19
+ Serialized.private_constant :QueryMethods
17
20
  end
18
21
  end
19
22
  end
@@ -84,7 +84,7 @@ Implements the {Mobility::Backends::Table} backend for Sequel models.
84
84
  end
85
85
  include callback_methods
86
86
 
87
- include Mobility::Sequel::ColumnChanges.new(*attributes)
87
+ include Mobility::Sequel::ColumnChanges.new(attributes)
88
88
  end
89
89
 
90
90
  setup_query_methods(QueryMethods)
@@ -3,51 +3,56 @@ require "mobility/backends/sequel/query_methods"
3
3
 
4
4
  module Mobility
5
5
  module Backends
6
- class Sequel::Table::QueryMethods < Sequel::QueryMethods
7
- def initialize(attributes, association_name: nil, model_class: nil, subclass_name: nil, **options)
8
- super
9
- translation_class = model_class.const_get(subclass_name)
6
+ module Sequel
7
+ class Table::QueryMethods < QueryMethods
8
+ def initialize(attributes, association_name: nil, model_class: nil, subclass_name: nil, **options)
9
+ super
10
+ translation_class = model_class.const_get(subclass_name)
10
11
 
11
- define_join_method(association_name, translation_class, **options)
12
- define_query_methods(association_name, translation_class, **options)
13
- end
12
+ define_join_method(association_name, translation_class, **options)
13
+ define_query_methods(association_name, translation_class, **options)
14
+ end
14
15
 
15
- def define_join_method(association_name, translation_class, table_name: nil, foreign_key: nil, **)
16
- define_method :"join_#{association_name}" do |**options|
17
- if joins = @opts[:join]
18
- # Return self if we've already joined this table
19
- return self if joins.any? { |clause| clause.table_expr == table_name }
20
- end
16
+ def define_join_method(association_name, translation_class, table_name: nil, foreign_key: nil, **)
17
+ define_method :"join_#{association_name}" do |**options|
18
+ if joins = @opts[:join]
19
+ # Return self if we've already joined this table
20
+ return self if joins.any? { |clause| clause.table_expr == table_name }
21
+ end
21
22
 
22
- join_type = options[:outer_join] ? :left_outer : :inner
23
- join_table(join_type,
24
- translation_class.table_name,
25
- {
26
- locale: Mobility.locale.to_s,
27
- foreign_key => ::Sequel[model.table_name][:id]
28
- })
23
+ join_type = options[:outer_join] ? :left_outer : :inner
24
+ join_table(join_type,
25
+ translation_class.table_name,
26
+ {
27
+ locale: Mobility.locale.to_s,
28
+ foreign_key => ::Sequel[model.table_name][:id]
29
+ })
30
+ end
29
31
  end
30
- end
31
32
 
32
- def define_query_methods(association_name, translation_class, **)
33
- q = self
33
+ def define_query_methods(association_name, translation_class, **)
34
+ q = self
34
35
 
35
- # See note in AR Table QueryMethods class about limitations of
36
- # query methods on translated attributes when searching on nil values.
37
- #
38
- %w[exclude or where].each do |method_name|
39
- define_method method_name do |*conds, &block|
40
- if i18n_keys = q.extract_attributes(conds.first)
41
- cond = conds.first.dup
42
- outer_join = method_name == "or" || i18n_keys.all? { |key| cond[key].nil? }
43
- i18n_keys.each { |attr| cond[::Sequel[translation_class.table_name][attr]] = cond.delete(attr) }
44
- super(cond, &block).send("join_#{association_name}", outer_join: outer_join)
45
- else
46
- super(*conds, &block)
36
+ # See note in AR Table QueryMethods class about limitations of
37
+ # query methods on translated attributes when searching on nil values.
38
+ #
39
+ %w[exclude or where].each do |method_name|
40
+ define_method method_name do |*conds, &block|
41
+ if i18n_keys = q.extract_attributes(conds.first)
42
+ cond = conds.first.dup
43
+ outer_join = method_name == "or" || i18n_keys.all? { |key| cond[key].nil? }
44
+ i18n_keys.each do |attr|
45
+ cond[::Sequel[translation_class.table_name][attr]] = q.collapse cond.delete(attr)
46
+ end
47
+ super(cond, &block).send("join_#{association_name}", outer_join: outer_join)
48
+ else
49
+ super(*conds, &block)
50
+ end
47
51
  end
48
52
  end
49
53
  end
50
54
  end
55
+ Table.private_constant :QueryMethods
51
56
  end
52
57
  end
53
58
  end