mobility 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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