mobility 0.8.10 → 1.0.0.beta2

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 +66 -0
  5. data/Gemfile +50 -18
  6. data/Gemfile.lock +36 -101
  7. data/README.md +183 -91
  8. data/Rakefile +6 -4
  9. data/lib/mobility.rb +44 -166
  10. data/lib/mobility/arel.rb +1 -1
  11. data/lib/mobility/arel/nodes/pg_ops.rb +1 -1
  12. data/lib/mobility/backend.rb +27 -51
  13. data/lib/mobility/backends.rb +20 -0
  14. data/lib/mobility/backends/active_record.rb +4 -0
  15. data/lib/mobility/backends/active_record/column.rb +2 -0
  16. data/lib/mobility/backends/active_record/container.rb +6 -7
  17. data/lib/mobility/backends/active_record/hstore.rb +3 -1
  18. data/lib/mobility/backends/active_record/json.rb +2 -0
  19. data/lib/mobility/backends/active_record/jsonb.rb +2 -0
  20. data/lib/mobility/backends/active_record/key_value.rb +6 -4
  21. data/lib/mobility/backends/active_record/pg_hash.rb +1 -1
  22. data/lib/mobility/backends/active_record/serialized.rb +6 -0
  23. data/lib/mobility/backends/active_record/table.rb +6 -4
  24. data/lib/mobility/backends/column.rb +0 -6
  25. data/lib/mobility/backends/container.rb +10 -1
  26. data/lib/mobility/backends/hash.rb +39 -0
  27. data/lib/mobility/backends/hash_valued.rb +4 -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 +1 -2
  31. data/lib/mobility/backends/key_value.rb +31 -26
  32. data/lib/mobility/backends/null.rb +2 -0
  33. data/lib/mobility/backends/sequel.rb +5 -2
  34. data/lib/mobility/backends/sequel/column.rb +2 -0
  35. data/lib/mobility/backends/sequel/container.rb +6 -6
  36. data/lib/mobility/backends/sequel/hstore.rb +3 -1
  37. data/lib/mobility/backends/sequel/json.rb +3 -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 +6 -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 +29 -26
  44. data/lib/mobility/pluggable.rb +56 -0
  45. data/lib/mobility/plugin.rb +260 -0
  46. data/lib/mobility/plugins.rb +27 -24
  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 +119 -78
  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 +34 -17
  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 +29 -20
  57. data/lib/mobility/plugins/attributes.rb +72 -0
  58. data/lib/mobility/plugins/backend.rb +161 -0
  59. data/lib/mobility/plugins/backend_reader.rb +34 -0
  60. data/lib/mobility/plugins/cache.rb +68 -26
  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 +52 -44
  64. data/lib/mobility/plugins/fallthrough_accessors.rb +25 -25
  65. data/lib/mobility/plugins/locale_accessors.rb +22 -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 +33 -22
  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 +96 -78
  78. metadata +28 -27
  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,4 +1,24 @@
1
1
  module Mobility
2
2
  module Backends
3
+ @backends = {}
4
+
5
+ class << self
6
+ # @param [Symbol, Object] backend Name of backend to load.
7
+ def load_backend(name)
8
+ return name if Module === name || name.nil?
9
+
10
+ unless (backend = @backends[name])
11
+ require "mobility/backends/#{name}"
12
+ raise LoadError, "backend #{name} did not register itself correctly in Mobility::Backends" unless (backend = @backends[name])
13
+ end
14
+ backend
15
+ end
16
+ end
17
+
18
+ def self.register_backend(name, mod)
19
+ @backends[name] = mod
20
+ end
21
+
22
+ class LoadError < Error; end
3
23
  end
4
24
  end
@@ -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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "mobility/backends/active_record"
3
+ require "mobility/backends/container"
3
4
  require "mobility/arel/nodes/pg_ops"
4
5
 
5
6
  module Mobility
@@ -11,11 +12,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
11
12
  =end
12
13
  class ActiveRecord::Container
13
14
  include ActiveRecord
14
-
15
- # @!method column_name
16
- # Returns name of json or jsonb column used to store translations
17
- # @return [Symbol] (:translations) Name of translations column
18
- option_reader :column_name
15
+ include Container
19
16
 
20
17
  # @!group Backend Accessors
21
18
  #
@@ -71,7 +68,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
71
68
  private
72
69
 
73
70
  def get_column_type
74
- options[:model_class].type_for_attribute(options[:column_name].to_s).try(:type).tap do |type|
71
+ model_class.type_for_attribute(options[:column_name].to_s).try(:type).tap do |type|
75
72
  unless %i[json jsonb].include? type
76
73
  raise InvalidColumnType, "#{options[:column_name]} must be a column of type json or jsonb"
77
74
  end
@@ -122,7 +119,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
122
119
 
123
120
  class Coder
124
121
  def self.dump(obj)
125
- if obj.is_a? Hash
122
+ if obj.is_a? ::Hash
126
123
  obj.inject({}) do |translations, (locale, value)|
127
124
  value.each do |k, v|
128
125
  (translations[locale] ||= {})[k] = v if v.present?
@@ -141,5 +138,7 @@ Implements the {Mobility::Backends::Container} backend for ActiveRecord models.
141
138
 
142
139
  class InvalidColumnType < StandardError; end
143
140
  end
141
+
142
+ register_backend(:active_record_container, ActiveRecord::Container)
144
143
  end
145
144
  end
@@ -18,7 +18,7 @@ Implements the {Mobility::Backends::Hstore} backend for ActiveRecord models.
18
18
 
19
19
  # @!macro backend_writer
20
20
  def write(locale, value, options = {})
21
- super(locale, value && value.to_s, options)
21
+ super(locale, value && value.to_s, **options)
22
22
  end
23
23
  # @!endgroup
24
24
 
@@ -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
@@ -51,7 +51,7 @@ Implements the {Mobility::Backends::KeyValue} backend for ActiveRecord models.
51
51
  # translation table value column
52
52
  def build_node(attr, locale)
53
53
  aliased_table = class_name.arel_table.alias(table_alias(attr, locale))
54
- Arel::Attribute.new(aliased_table, :value, locale, self, attribute_name: attr.to_sym)
54
+ Arel::Attribute.new(aliased_table, :value, locale, self, attr.to_sym)
55
55
  end
56
56
 
57
57
  # Joins translations using either INNER/OUTER join appropriate to the query.
@@ -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
@@ -30,6 +30,10 @@ Implements {Mobility::Backends::Serialized} backend for ActiveRecord models.
30
30
  include ActiveRecord
31
31
  include HashValued
32
32
 
33
+ def self.valid_keys
34
+ super + [:format]
35
+ end
36
+
33
37
  # @!group Backend Configuration
34
38
  # @param (see Backends::Serialized.configure)
35
39
  # @option (see Backends::Serialized.configure)
@@ -72,5 +76,7 @@ Implements {Mobility::Backends::Serialized} backend for ActiveRecord models.
72
76
  EOM
73
77
  end
74
78
  end
79
+
80
+ register_backend(:active_record_serialized, ActiveRecord::Serialized)
75
81
  end
76
82
  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
 
@@ -283,7 +283,7 @@ columns to that table.
283
283
 
284
284
  # Returns translation for a given locale, or builds one if none is present.
285
285
  # @param [Symbol] locale
286
- def translation_for(locale, _)
286
+ def translation_for(locale, **)
287
287
  translation = translations.in_locale(locale)
288
288
  translation ||= translations.build(locale: locale)
289
289
  translation
@@ -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,16 @@ stored).
19
19
 
20
20
  =end
21
21
  module Container
22
- extend Backend::OrmDelegator
22
+ def self.included(backend_class)
23
+ backend_class.extend ClassMethods
24
+ backend_class.option_reader :column_name
25
+ end
26
+
27
+ module ClassMethods
28
+ def valid_keys
29
+ [:column_name]
30
+ end
31
+ end
23
32
  end
24
33
  end
25
34
  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
@@ -36,6 +36,10 @@ Defines read and write methods that access the value at a key with value
36
36
  end
37
37
 
38
38
  module ClassMethods
39
+ def valid_keys
40
+ [:column_prefix, :column_suffix]
41
+ end
42
+
39
43
  def configure(options)
40
44
  options[:column_affix] = "#{options[:column_prefix]}%s#{options[:column_suffix]}"
41
45
  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
@@ -6,7 +6,7 @@ Stores translations as hash on Postgres jsonb column.
6
6
 
7
7
  ==Backend Options
8
8
 
9
- ===+prefix+ and +suffix+
9
+ ===+column_prefix+ and +column_suffix+
10
10
 
11
11
  Prefix and suffix to add to attribute name to generate jsonb column name.
12
12
 
@@ -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
@@ -56,13 +54,13 @@ other backends on model (otherwise one will overwrite the other).
56
54
 
57
55
  # @!group Backend Accessors
58
56
  # @!macro backend_reader
59
- def read(locale, options = {})
60
- translation_for(locale, options).value
57
+ def read(locale, **options)
58
+ translation_for(locale, **options).value
61
59
  end
62
60
 
63
61
  # @!macro backend_writer
64
- def write(locale, value, options = {})
65
- translation_for(locale, options).value = value
62
+ def write(locale, value, **options)
63
+ translation_for(locale, **options).value = value
66
64
  end
67
65
  # @!endgroup
68
66
 
@@ -85,9 +83,13 @@ other backends on model (otherwise one will overwrite the other).
85
83
  end
86
84
 
87
85
  module ClassMethods
86
+ def valid_keys
87
+ [:type, :association_name, :class_name]
88
+ end
89
+
88
90
  # @!group Backend Configuration
89
91
  # @option options [Symbol,String] type Column type to use
90
- # @option options [Symbol] associaiton_name (:<type>_translations) Name
92
+ # @option options [Symbol] association_name (:<type>_translations) Name
91
93
  # of association method, defaults to +<type>_translations+
92
94
  # @option options [Symbol] class_name Translation class, defaults to
93
95
  # +Mobility::<ORM>::<type>Translation+
@@ -98,27 +100,13 @@ other backends on model (otherwise one will overwrite the other).
98
100
  options[:association_name] &&= options[:association_name].to_sym
99
101
  options[:class_name] &&= Util.constantize(options[:class_name])
100
102
  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
103
+ raise ArgumentError, "KeyValue backend requires an explicit type option, either text or string."
109
104
  end
110
105
  end
111
106
 
112
- # Apply custom processing for plugin
113
- # @param (see Backend::Setup#apply_plugin)
114
- # @return (see Backend::Setup#apply_plugin)
115
- def apply_plugin(name)
116
- if name == :cache
117
- include self::Cache
118
- true
119
- else
120
- super
121
- end
107
+ # Apply custom processing for cache plugin
108
+ def include_cache
109
+ include self::Cache
122
110
  end
123
111
 
124
112
  def table_alias(attr, locale)
@@ -127,7 +115,24 @@ each translated model, or set a default option in your configuration.
127
115
  end
128
116
 
129
117
  module Cache
130
- include Plugins::Cache::TranslationCacher.new(:translation_for)
118
+ def translation_for(locale, **options)
119
+ return super(locale, options) if options.delete(:cache) == false
120
+ if cache.has_key?(locale)
121
+ cache[locale]
122
+ else
123
+ cache[locale] = super(locale, **options)
124
+ end
125
+ end
126
+
127
+ def clear_cache
128
+ @cache = {}
129
+ end
130
+
131
+ private
132
+
133
+ def cache
134
+ @cache ||= {}
135
+ end
131
136
  end
132
137
  end
133
138
  end