mobility 0.1.20 → 0.2.0

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