mobility 0.1.20 → 0.2.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 (110) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/CHANGELOG.md +17 -0
  5. data/CONTRIBUTING.md +55 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +2 -56
  8. data/README.md +64 -15
  9. data/Rakefile +17 -17
  10. data/lib/mobility.rb +43 -40
  11. data/lib/mobility/active_model.rb +0 -1
  12. data/lib/mobility/active_model/backend_resetter.rb +1 -1
  13. data/lib/mobility/active_record.rb +8 -15
  14. data/lib/mobility/active_record/backend_resetter.rb +2 -0
  15. data/lib/mobility/active_record/model_translation.rb +1 -1
  16. data/lib/mobility/active_record/string_translation.rb +2 -0
  17. data/lib/mobility/active_record/text_translation.rb +2 -0
  18. data/lib/mobility/attributes.rb +74 -71
  19. data/lib/mobility/backend.rb +64 -25
  20. data/lib/mobility/backend/orm_delegator.rb +17 -7
  21. data/lib/mobility/backend/stringify_locale.rb +18 -0
  22. data/lib/mobility/backend_resetter.rb +8 -5
  23. data/lib/mobility/backends.rb +4 -0
  24. data/lib/mobility/backends/active_record.rb +20 -0
  25. data/lib/mobility/{backend → backends}/active_record/column.rb +25 -13
  26. data/lib/mobility/{backend → backends}/active_record/column/query_methods.rb +5 -3
  27. data/lib/mobility/backends/active_record/hstore.rb +29 -0
  28. data/lib/mobility/{backend → backends}/active_record/hstore/query_methods.rb +2 -2
  29. data/lib/mobility/{backend → backends}/active_record/jsonb.rb +6 -6
  30. data/lib/mobility/{backend → backends}/active_record/jsonb/query_methods.rb +4 -2
  31. data/lib/mobility/{backend → backends}/active_record/key_value.rb +10 -44
  32. data/lib/mobility/{backend → backends}/active_record/key_value/query_methods.rb +4 -2
  33. data/lib/mobility/{backend/active_record/hash_valued.rb → backends/active_record/pg_hash.rb} +13 -21
  34. data/lib/mobility/{backend → backends}/active_record/query_methods.rb +2 -2
  35. data/lib/mobility/{backend → backends}/active_record/serialized.rb +12 -28
  36. data/lib/mobility/{backend → backends}/active_record/serialized/query_methods.rb +5 -8
  37. data/lib/mobility/{backend → backends}/active_record/table.rb +11 -60
  38. data/lib/mobility/{backend → backends}/active_record/table/query_methods.rb +5 -3
  39. data/lib/mobility/{backend → backends}/column.rb +8 -4
  40. data/lib/mobility/backends/hash_valued.rb +29 -0
  41. data/lib/mobility/{backend → backends}/hstore.rb +4 -4
  42. data/lib/mobility/{backend → backends}/jsonb.rb +4 -4
  43. data/lib/mobility/backends/key_value.rb +111 -0
  44. data/lib/mobility/{backend → backends}/null.rb +4 -4
  45. data/lib/mobility/backends/sequel.rb +20 -0
  46. data/lib/mobility/backends/sequel/column.rb +52 -0
  47. data/lib/mobility/{backend → backends}/sequel/column/query_methods.rb +5 -3
  48. data/lib/mobility/backends/sequel/hstore.rb +29 -0
  49. data/lib/mobility/{backend → backends}/sequel/hstore/query_methods.rb +4 -3
  50. data/lib/mobility/{backend → backends}/sequel/jsonb.rb +6 -6
  51. data/lib/mobility/{backend → backends}/sequel/jsonb/query_methods.rb +4 -3
  52. data/lib/mobility/{backend → backends}/sequel/key_value.rb +28 -39
  53. data/lib/mobility/{backend → backends}/sequel/key_value/query_methods.rb +4 -5
  54. data/lib/mobility/backends/sequel/pg_hash.rb +46 -0
  55. data/lib/mobility/{backend → backends}/sequel/postgres_query_methods.rb +1 -2
  56. data/lib/mobility/{backend → backends}/sequel/query_methods.rb +6 -4
  57. data/lib/mobility/{backend → backends}/sequel/serialized.rb +17 -38
  58. data/lib/mobility/backends/sequel/serialized/query_methods.rb +17 -0
  59. data/lib/mobility/{backend → backends}/sequel/table.rb +29 -60
  60. data/lib/mobility/{backend → backends}/sequel/table/query_methods.rb +5 -3
  61. data/lib/mobility/{backend → backends}/serialized.rb +27 -5
  62. data/lib/mobility/{backend → backends}/table.rb +69 -29
  63. data/lib/mobility/configuration.rb +40 -0
  64. data/lib/mobility/{orm.rb → loaded.rb} +0 -0
  65. data/lib/mobility/plugins.rb +35 -0
  66. data/lib/mobility/plugins/active_model.rb +6 -0
  67. data/lib/mobility/plugins/active_model/dirty.rb +81 -0
  68. data/lib/mobility/plugins/active_record.rb +6 -0
  69. data/lib/mobility/plugins/active_record/dirty.rb +59 -0
  70. data/lib/mobility/plugins/cache.rb +54 -0
  71. data/lib/mobility/plugins/cache/translation_cacher.rb +40 -0
  72. data/lib/mobility/plugins/default.rb +73 -0
  73. data/lib/mobility/plugins/dirty.rb +61 -0
  74. data/lib/mobility/{backend → plugins}/fallbacks.rb +36 -31
  75. data/lib/mobility/plugins/fallthrough_accessors.rb +66 -0
  76. data/lib/mobility/plugins/locale_accessors.rb +84 -0
  77. data/lib/mobility/{backend → plugins}/presence.rb +15 -6
  78. data/lib/mobility/plugins/sequel.rb +6 -0
  79. data/lib/mobility/plugins/sequel/dirty.rb +59 -0
  80. data/lib/mobility/sequel.rb +5 -14
  81. data/lib/mobility/sequel/backend_resetter.rb +4 -6
  82. data/lib/mobility/sequel/column_changes.rb +4 -4
  83. data/lib/mobility/sequel/model_translation.rb +1 -1
  84. data/lib/mobility/sequel/string_translation.rb +2 -0
  85. data/lib/mobility/sequel/text_translation.rb +2 -0
  86. data/lib/mobility/translates.rb +1 -5
  87. data/lib/mobility/util.rb +126 -0
  88. data/lib/mobility/version.rb +1 -1
  89. data/lib/mobility/wrapper.rb +1 -1
  90. data/lib/rails/generators/mobility/translations_generator.rb +7 -3
  91. metadata +85 -55
  92. metadata.gz.sig +0 -0
  93. data/lib/mobility/backend/active_model.rb +0 -7
  94. data/lib/mobility/backend/active_model/dirty.rb +0 -95
  95. data/lib/mobility/backend/active_record.rb +0 -29
  96. data/lib/mobility/backend/active_record/dirty.rb +0 -54
  97. data/lib/mobility/backend/active_record/hstore.rb +0 -29
  98. data/lib/mobility/backend/cache.rb +0 -117
  99. data/lib/mobility/backend/dirty.rb +0 -38
  100. data/lib/mobility/backend/key_value.rb +0 -85
  101. data/lib/mobility/backend/sequel.rb +0 -29
  102. data/lib/mobility/backend/sequel/column.rb +0 -39
  103. data/lib/mobility/backend/sequel/dirty.rb +0 -57
  104. data/lib/mobility/backend/sequel/hash_valued.rb +0 -51
  105. data/lib/mobility/backend/sequel/hstore.rb +0 -29
  106. data/lib/mobility/backend/sequel/serialized/query_methods.rb +0 -20
  107. data/lib/mobility/core_ext/object.rb +0 -30
  108. data/lib/mobility/core_ext/string.rb +0 -16
  109. data/lib/mobility/fallthrough_accessors.rb +0 -57
  110. data/lib/mobility/locale_accessors.rb +0 -55
metadata.gz.sig ADDED
Binary file
@@ -1,7 +0,0 @@
1
- module Mobility
2
- module Backend
3
- module ActiveModel
4
- autoload :Dirty, 'mobility/backend/active_model/dirty'
5
- end
6
- end
7
- end
@@ -1,95 +0,0 @@
1
- module Mobility
2
- module Backend
3
- =begin
4
-
5
- Dirty tracking for models which include the +ActiveModel::Dirty+ module.
6
-
7
- Assuming we have an attribute +title+, this module will add support for the
8
- following methods:
9
- - +title_changed?+
10
- - +title_change+
11
- - +title_was+
12
- - +title_will_change!+
13
- - +title_previously_changed?+
14
- - +title_previous_change+
15
- - +restore_title!+
16
-
17
- In addition, the private method +restore_attribute!+ will also restore the
18
- value of the translated attribute if passed to it.
19
-
20
- @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html Rails documentation for Active Model Dirty module
21
-
22
- =end
23
- module ActiveModel::Dirty
24
- # @!group Backend Accessors
25
- # @!macro backend_writer
26
- # @param [Hash] options
27
- def write(locale, value, **options)
28
- locale_accessor = Mobility.normalize_locale_accessor(attribute, locale)
29
- if model.changed_attributes.has_key?(locale_accessor) && model.changed_attributes[locale_accessor] == value
30
- model.attributes_changed_by_setter.except!(locale_accessor)
31
- elsif read(locale, options.merge(fallback: false)) != value
32
- model.send(:attribute_will_change!, locale_accessor)
33
- end
34
- super
35
- end
36
- # @!endgroup
37
-
38
- # @param [Class] backend_class Class of backend
39
- def self.included(backend_class)
40
- backend_class.extend(ClassMethods)
41
- end
42
-
43
- # Adds hook after {Backend::Setup#setup_model} to add dirty-tracking
44
- # methods for translated attributes onto model class.
45
- module ClassMethods
46
- # (see Mobility::Backend::Setup#setup_model)
47
- def setup_model(model_class, attributes, **options)
48
- super
49
- model_class.class_eval do
50
- Mobility::Backend::ActiveModel::Dirty.method_suffixes.each do |suffix|
51
- attributes.each do |attribute|
52
- class_eval <<-EOM, __FILE__, __LINE__ + 1
53
- def #{attribute}#{suffix}
54
- attribute#{suffix}(Mobility.normalize_locale_accessor("#{attribute}"))
55
- end
56
- EOM
57
- end
58
- end
59
- end
60
-
61
- restore_methods = Module.new do
62
- attributes.each do |attribute|
63
- define_method "restore_#{attribute}!" do
64
- locale_accessor = Mobility.normalize_locale_accessor(attribute)
65
- if attribute_changed?(locale_accessor)
66
- __send__("#{attribute}=", changed_attributes[locale_accessor])
67
- end
68
- end
69
- end
70
-
71
- define_method :restore_attribute! do |attr|
72
- if attributes.include?(attr.to_s)
73
- send("restore_#{attr}!")
74
- else
75
- super(attr)
76
- end
77
- end
78
- private :restore_attribute!
79
- end
80
- model_class.include restore_methods
81
- end
82
- end
83
-
84
- # Get method suffixes. Creating an object just to get the list of
85
- # suffixes is not very efficient, but the most reliable way given that
86
- # they change from Rails version to version.
87
- def self.method_suffixes
88
- @method_suffixes ||=
89
- Class.new do
90
- include ::ActiveModel::Dirty
91
- end.attribute_method_matchers.map(&:suffix).select { |m| m =~ /\A_/ }
92
- end
93
- end
94
- end
95
- end
@@ -1,29 +0,0 @@
1
- module Mobility
2
- module Backend
3
- module ActiveRecord
4
- autoload :Column, 'mobility/backend/active_record/column'
5
- autoload :Dirty, 'mobility/backend/active_record/dirty'
6
- autoload :Hstore, 'mobility/backend/active_record/hstore'
7
- autoload :Jsonb, 'mobility/backend/active_record/jsonb'
8
- autoload :KeyValue, 'mobility/backend/active_record/key_value'
9
- autoload :Serialized, 'mobility/backend/active_record/serialized'
10
- autoload :QueryMethods, 'mobility/backend/active_record/query_methods'
11
- autoload :Table, 'mobility/backend/active_record/table'
12
-
13
- def setup_query_methods(query_methods)
14
- setup do |attributes, options|
15
- extend(Module.new do
16
- define_method ::Mobility.query_method do
17
- super().extending(query_methods.new(attributes, options))
18
- end
19
- end)
20
- end
21
- end
22
-
23
- def self.included(backend_class)
24
- backend_class.include(Backend)
25
- backend_class.extend(self)
26
- end
27
- end
28
- end
29
- end
@@ -1,54 +0,0 @@
1
- module Mobility
2
- module Backend
3
- =begin
4
-
5
- Dirty tracking for AR models. See {Mobility::Backend::ActiveModel::Dirty} for
6
- details on usage.
7
-
8
- =end
9
- module ActiveRecord::Dirty
10
- include ActiveModel::Dirty
11
-
12
- # @param [Class] backend_class Class of backend
13
- def self.included(backend_class)
14
- backend_class.extend(ActiveModel::Dirty::ClassMethods)
15
- backend_class.extend(ClassMethods)
16
- end
17
-
18
- # Adds hook after {Backend::Setup#setup_model} to patch AR so that it
19
- # handles changes to translated attributes just like normal attributes.
20
- module ClassMethods
21
- # (see Mobility::Backend::Setup#setup_model)
22
- def setup_model(model_class, attributes, **options)
23
- super
24
-
25
- method_name_regex = /\A(#{attributes.join('|'.freeze)})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
26
- has_attribute = Module.new do
27
- define_method :has_attribute? do |attr_name|
28
- super(attr_name) || !!method_name_regex.match(attr_name)
29
- end
30
- end
31
- model_class.extend has_attribute
32
-
33
- changes_applied_method = ::ActiveRecord::VERSION::STRING < '5.1' ? :changes_applied : :changes_internally_applied
34
- mod = Module.new do
35
- define_method changes_applied_method do
36
- @previously_changed = changes
37
- super()
38
- end
39
-
40
- def clear_changes_information
41
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
42
- super
43
- end
44
-
45
- def previous_changes
46
- (@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new).merge(super)
47
- end
48
- end
49
- model_class.include mod
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,29 +0,0 @@
1
- require 'mobility/backend/active_record/hash_valued'
2
-
3
- module Mobility
4
- module Backend
5
- =begin
6
-
7
- Implements the {Mobility::Backend::Hstore} backend for ActiveRecord models.
8
-
9
- @see Mobility::Backend::ActiveRecord::HashValued
10
-
11
- =end
12
- class ActiveRecord::Hstore < ActiveRecord::HashValued
13
- require 'mobility/backend/active_record/hstore/query_methods'
14
-
15
- # @!group Backend Accessors
16
- # @!macro backend_reader
17
- # @!method read(locale, **options)
18
-
19
- # @!group Backend Accessors
20
- # @!macro backend_writer
21
- def write(locale, value, **_)
22
- translations[locale] = value && value.to_s
23
- end
24
- # @!endgroup
25
-
26
- setup_query_methods(QueryMethods)
27
- end
28
- end
29
- end
@@ -1,117 +0,0 @@
1
- module Mobility
2
- module Backend
3
- =begin
4
-
5
- Caches values fetched from the backend so subsequent fetches can be performed
6
- more quickly.
7
-
8
- By default, the cache stores cached values in a simple hash, returned from the
9
- {new_cache} method added to the including backend instance class. To use a
10
- different cache class, simply define a +new_cache+ method in the backend and
11
- return a new instance of the cache class (many backends do this, see
12
- {Mobility::Backend::KeyValue} for one example.)
13
-
14
- The cache is reset by the {clear_cache} method, which by default simply assigns
15
- the result of {new_cache} to the +@cache+ instance variable. This behaviour can
16
- also be customized by defining a +new_cache+ method on the backend class (see
17
- {Mobility::Backend::ActiveRecord::Table#new_cache} for an example of a backend that does this).
18
-
19
- The cache is reset when one of a set of events happens (saving, reloading,
20
- etc.). See {BackendResetter} for details.
21
-
22
- Values are added to the cache in two ways:
23
-
24
- 1. first read from backend
25
- 2. any write to backend
26
-
27
- The latter can be customized by defining the {write_to_cache?} method, which by
28
- default returns +false+. If set to +true+, then writes will only update the
29
- cache and not hit the backend. This is a sensible setting in case the cache is
30
- actually an object which directly stores the translation (see one of the
31
- ORM-specific implementations of {Mobility::Backend::KeyValue} for examples of
32
- this).
33
-
34
- =end
35
- module Cache
36
- # @group Backend Accessors
37
- # @!macro backend_reader
38
- # @param [Hash] options
39
- # @param [Boolean] cache
40
- # *false* to disable cache.
41
- def read(locale, **options)
42
- return super if options.delete(:cache) == false
43
- if write_to_cache? || cache.has_key?(locale)
44
- cache[locale]
45
- else
46
- cache[locale] = super
47
- end
48
- end
49
-
50
- # @!macro backend_writer
51
- # @param [Hash] options
52
- # @param [Boolean] cache
53
- # *false* to disable cache.
54
- def write(locale, value, **options)
55
- return super if options.delete(:cache) == false
56
- cache[locale] = write_to_cache? ? value : super
57
- end
58
- # @!endgroup
59
-
60
- # Adds hook to {Backend::Setup#setup_model} to include instance of
61
- # model-specific {BackendResetter} subclass when setting up
62
- # model class, to trigger cache resetting at specific events (saving,
63
- # reloading, etc.)
64
- module Setup
65
- # @param model_class Model class
66
- # @param [Array<String>] attributes Backend attributes
67
- def setup_model(model_class, attributes, **_)
68
- super
69
- model_class.include BackendResetter.for(model_class).new(attributes) { clear_cache }
70
- end
71
- end
72
-
73
- # @!group Cache Methods
74
- # @!parse
75
- # def new_cache
76
- # {}
77
- # end
78
- #
79
- # @!parse
80
- # def write_to_cache?
81
- # false
82
- # end
83
- #
84
- # @!parse
85
- # def clear_cache
86
- # @cache = new_cache
87
- # end
88
- # @!endgroup
89
-
90
- # Includes cache methods to backend (unless they are already defined) and
91
- # extends backend class with {Mobility::Cache::Setup} for backend resetting.
92
- def self.included(backend_class)
93
- backend_class.class_eval do
94
- extend Setup
95
-
96
- def new_cache
97
- {}
98
- end unless method_defined?(:new_cache)
99
-
100
- def write_to_cache?
101
- false
102
- end unless method_defined?(:write_to_cache?)
103
-
104
- def clear_cache
105
- @cache = new_cache
106
- end unless method_defined?(:clear_cache)
107
- end
108
- end
109
-
110
- private
111
-
112
- def cache
113
- @cache ||= new_cache
114
- end
115
- end
116
- end
117
- end
@@ -1,38 +0,0 @@
1
- module Mobility
2
- module Backend
3
- =begin
4
-
5
- Dirty tracking for Mobility attributes. See class-specific implementations for
6
- details.
7
-
8
- @see Mobility::Backend::ActiveModel::Dirty
9
- @see Mobility::Backend::Sequel::Dirty
10
-
11
- @note Dirty tracking can have unexpected results when combined with fallbacks.
12
- A change in the fallback locale value will not mark an attribute falling
13
- through to that locale as changed, even though it may look like it has
14
- changed. However, when the value for the current locale is changed from nil
15
- or blank to a new value, the change will be recorded as a change from that
16
- fallback value, rather than from the nil or blank value. The specs are the
17
- most reliable source of information on the interaction between dirty tracking
18
- and fallbacks.
19
-
20
- =end
21
- module Dirty
22
- # @param model_class Class of model this backend is defined on.
23
- # @return [Backend]
24
- # @raise [ArgumentError] if model class does not support dirty tracking
25
- def self.for(model_class)
26
- model_class ||= Object
27
- if Loaded::ActiveRecord && model_class.ancestors.include?(::ActiveModel::Dirty)
28
- (model_class < ::ActiveRecord::Base) ?
29
- Backend::ActiveRecord::Dirty : Backend::ActiveModel::Dirty
30
- elsif Loaded::Sequel && model_class < ::Sequel::Model
31
- Backend::Sequel::Dirty
32
- else
33
- raise ArgumentError, "#{model_class.to_s} does not support Dirty module."
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,85 +0,0 @@
1
- module Mobility
2
- module Backend
3
- =begin
4
-
5
- Stores attribute translation as attribute/value pair on a shared translations
6
- table, using a polymorphic relationship between a translation class and models
7
- using the backend. By default, two tables are assumed to be present supporting
8
- string and text translations: a +mobility_text_translations+ table for text-valued translations and a
9
- +mobility_string_translations+ table for string-valued translations (the only
10
- difference being the column type of the +value+ column on the table).
11
-
12
- ==Backend Options
13
-
14
- ===+association_name+
15
-
16
- Name of association on model. Defaults to +mobility_text_translations+ (if
17
- +type+ is +:text+) or +mobility_string_translations+ (if +type+ is +:string+).
18
- If specified, ensure name does not overlap with other methods on model or with
19
- the association name used by other backends on model (otherwise one will
20
- overwrite the other).
21
-
22
- ===+type+
23
-
24
- Currently, either +:text+ or +:string+ is supported. Determines which class to
25
- use for translations, which in turn determines which table to use to store
26
- translations (by default +mobility_text_translations+ for text type,
27
- +mobility_string_translations+ for string type).
28
-
29
- ===+class_name+
30
-
31
- Class to use for translations when defining association. By default,
32
- {Mobility::ActiveRecord::TextTranslation} or
33
- {Mobility::ActiveRecord::StringTranslation} for ActiveRecord models (similar
34
- for Sequel models). If string is passed in, it will be constantized to get the
35
- class.
36
-
37
- @see Mobility::Backend::ActiveRecord::KeyValue
38
- @see Mobility::Backend::Sequel::KeyValue
39
-
40
- =end
41
- module KeyValue
42
- include OrmDelegator
43
-
44
- def self.included(backend)
45
- backend.extend ClassMethods
46
- end
47
-
48
- module ClassMethods
49
- # @!group Backend Configuration
50
- # @option options [Symbol,String] type (:text) Column type to use
51
- # @raise [ArgumentError] if type is not either :text or :string
52
- def configure(options)
53
- options[:type] = (options[:type] || :text).to_sym
54
- raise ArgumentError, "type must be one of: [text, string]" unless [:text, :string].include?(options[:type])
55
- end
56
- end
57
-
58
- # Simple cache to memoize translations as a hash so they can be fetched
59
- # quickly.
60
- class TranslationsCache
61
- # @param backend Instance of KeyValue backend to cache
62
- # @return [TranslationsCache]
63
- def initialize(backend)
64
- @cache = Hash.new { |hash, locale| hash[locale] = backend.translation_for(locale) }
65
- end
66
-
67
- # @param locale [Symbol] Locale to fetch
68
- def [](locale)
69
- @cache[locale].value
70
- end
71
-
72
- # @param locale [Symbol] Locale to set
73
- # @param value [String] Value to set
74
- def []=(locale, value)
75
- @cache[locale].value = value
76
- end
77
-
78
- # @yield [locale, translation]
79
- def each_translation &block
80
- @cache.each_value &block
81
- end
82
- end
83
- end
84
- end
85
- end