mobility 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db71989aceb090cc6bc7fba2a4bfed78ae0ccc23
4
- data.tar.gz: fa3c53b2540e63638c7e35b2fa43731b7c00ed90
3
+ metadata.gz: 1d5bbeab96e1792e6240d3f1854e302c5845689b
4
+ data.tar.gz: 0a821c40d9b1c3e6202a92e6194307fb0698c12c
5
5
  SHA512:
6
- metadata.gz: 9cef93cb7bc0a451a3eaf7ea18b284258894a99c5214b5933f9ff54752bf525900736c35850c6a7e3a712e116e5b3c0a721b230eeefff3fbf7cab452ed6daa66
7
- data.tar.gz: 0b682ce576e80ba2834f80fe91b19d0027277b06fea27f1fa8eca9e3aa91fc2bba627b0bfea8fe9b495bae5cd19b5ee2eb0b697a6dd592283ecc3e73453ea494
6
+ metadata.gz: 8cc59378dff919de4592e35b1a73e11ab808616ed1b6a9cdc84737e447a97a50d2503d348a41d3c8645f129a0f65554705a5696041c89950e5e7dd3b98b4f357
7
+ data.tar.gz: ed3e03d952e333bd04e4d366b0a9e6d523981e8270c1e5d7d36213e15806383ca3c6c0cd245e72b304420e725bcfbe400f4e3a28b32d64394a7ba29bf4f3004b
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## 0.1
4
4
 
5
+ ### 0.1.12
6
+ * Extract presence filter into `Mobility::Backend::Presence` class
7
+ ([7d654](https://github.com/shioyama/mobility/commit/7d65479c832ca154a45a548b64d27016486d34df),
8
+ [e42ee6](https://github.com/shioyama/mobility/commit/e42ee6123197594f3a8d694bff68c2ef4044562e))
9
+ * Get suffix methods from ActiveModel (for compatibility with Rails 4.2)
10
+ ([9685d1](https://github.com/shioyama/mobility/commit/9685d182f285bddd2f5739a655f7c9e18998a5a1))
11
+ * Destroy all translations after model is destroyed (KeyValue backend)
12
+ ([#15](https://github.com/shioyama/mobility/pull/15))
13
+ * Refactor to remove `mobility_get`, `mobility_set`. `mobility_present?` models
14
+ from model class ([#16](https://github.com/shioyama/mobility/pull/16))
15
+
5
16
  ### 0.1.11
6
17
  * Add backend-specific translations generator (`rails generate
7
18
  mobility:translations`)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mobility (0.1.10)
4
+ mobility (0.1.11)
5
5
  i18n (>= 0.6.10)
6
6
  request_store (~> 1.0)
7
7
 
@@ -29,9 +29,10 @@ GEM
29
29
  guard-compat (~> 1.1)
30
30
  rspec (>= 2.99.0, < 4.0)
31
31
  i18n (0.8.1)
32
- listen (3.0.8)
32
+ listen (3.1.5)
33
33
  rb-fsevent (~> 0.9, >= 0.9.4)
34
34
  rb-inotify (~> 0.9, >= 0.9.7)
35
+ ruby_dep (~> 1.2)
35
36
  lumberjack (1.0.11)
36
37
  method_source (0.8.2)
37
38
  mysql2 (0.3.21)
@@ -39,7 +40,7 @@ GEM
39
40
  notiffany (0.1.1)
40
41
  nenv (~> 0.1)
41
42
  shellany (~> 0.0)
42
- pg (0.19.0)
43
+ pg (0.20.0)
43
44
  pry (0.10.4)
44
45
  coderay (~> 1.1.0)
45
46
  method_source (~> 0.8.1)
@@ -68,7 +69,7 @@ GEM
68
69
  diff-lcs (>= 1.2.0, < 2.0)
69
70
  rspec-support (~> 3.5.0)
70
71
  rspec-support (3.5.0)
71
- sequel (4.42.1)
72
+ ruby_dep (1.5.0)
72
73
  shellany (0.0.1)
73
74
  slop (3.6.0)
74
75
  sqlite3 (1.3.13)
@@ -89,7 +90,6 @@ DEPENDENCIES
89
90
  rake (~> 10.0)
90
91
  rspec (~> 3.0)
91
92
  rspec-its (~> 1.2.0)
92
- sequel (>= 4.0.0, < 5.0)
93
93
  sqlite3
94
94
  yard (~> 0.9.0)
95
95
 
data/README.md CHANGED
@@ -37,7 +37,8 @@ platforms planned.
37
37
  For a detailed introduction to Mobility, see [Translating with
38
38
  Mobility](http://dejimata.com/2017/3/3/translating-with-mobility). See also the
39
39
  [Roadmap](https://github.com/shioyama/mobility/wiki/Roadmap) for what's in the
40
- works for future releases.
40
+ works for future releases, and other pages of the [wiki][wiki] for more detail
41
+ on usage.
41
42
 
42
43
  Installation
43
44
  ------------
@@ -45,7 +46,7 @@ Installation
45
46
  Add this line to your application's Gemfile:
46
47
 
47
48
  ```ruby
48
- gem 'mobility', '~> 0.1.11'
49
+ gem 'mobility', '~> 0.1.12'
49
50
  ```
50
51
 
51
52
  To translate attributes on a model, include (or extend) `Mobility`, then call
@@ -97,10 +98,10 @@ You can include `Mobility` just like in ActiveRecord, or you can use the
97
98
  `mobility` plugin, which does the same thing:
98
99
 
99
100
  ```ruby
100
- class Post < ::Sequel::Model
101
+ class Word < ::Sequel::Model
101
102
  plugin :mobility
102
- translates :title, type: :string
103
- translates :content, type: :text
103
+ translates :name, type: :string
104
+ translates :meaning, type: :text
104
105
  end
105
106
  ```
106
107
 
@@ -108,7 +109,7 @@ Otherwise everything is (almost) identical to AR, with the exception that there
108
109
  is no equivalent to a Rails generator (so you will need to create the migration
109
110
  for any translation table(s) yourself, using Rails generators as a reference).
110
111
 
111
- The models in examples below all inherit from `ActiveRecord::Base`, but
112
+ The models in examples below all inherit from `ApplicationRecord`, but
112
113
  everything works exactly the same if the parent class is `Sequel::Model`.
113
114
 
114
115
  Usage
@@ -117,10 +118,12 @@ Usage
117
118
  ### <a name="quickstart"></a>Getting Started
118
119
 
119
120
  Once the install generator has been run to generate translation tables, using
120
- Mobility is as easy as adding a few lines to any class you want to translate:
121
+ Mobility is as easy as adding a few lines to any class you want to translate.
122
+ Simply pass one or more attribute names to the `translates` method with a hash
123
+ of options, like this:
121
124
 
122
125
  ```ruby
123
- class Word < ActiveRecord::Base
126
+ class Word < ApplicationRecord
124
127
  include Mobility
125
128
  translates :name, type: :string
126
129
  translates :meaning, type: :text
@@ -221,7 +224,7 @@ accessors" in Mobility, and they can be defined by passing a `locale_accessors`
221
224
  option when defining translated attributes on the model class:
222
225
 
223
226
  ```ruby
224
- class Word < ActiveRecord::Base
227
+ class Word < ApplicationRecord
225
228
  include Mobility
226
229
  translates :name, type: :string, locale_accessors: [:en, :ja]
227
230
  end
@@ -261,7 +264,7 @@ precedence if defined for a given locale.)
261
264
  For example, if we define `Word` this way:
262
265
 
263
266
  ```ruby
264
- class Word < ActiveRecord::Base
267
+ class Word < ApplicationRecord
265
268
  include Mobility
266
269
  translates :name, type: :string, fallthrough_accessors: true
267
270
  end
@@ -366,7 +369,7 @@ pass a hash with fallbacks for each locale as an option when defining
366
369
  translated attributes on a class:
367
370
 
368
371
  ```ruby
369
- class Word < ActiveRecord::Base
372
+ class Word < ApplicationRecord
370
373
  include Mobility
371
374
  translates :name, type: :string, fallbacks: { de: :ja, fr: :ja }
372
375
  translates :meaning, type: :text, fallbacks: { de: :ja, fr: :ja }
@@ -451,7 +454,7 @@ First, enable dirty tracking (note that this is a persisted AR model, although
451
454
  dirty tracking is not specific to AR and works for non-persisted models as well):
452
455
 
453
456
  ```ruby
454
- class Post < ActiveRecord::Base
457
+ class Post < ApplicationRecord
455
458
  include Mobility
456
459
  translates :title, type: :string, dirty: true
457
460
  end
@@ -525,7 +528,7 @@ generally only be disabled when debugging; this can be done by passing `cache:
525
528
  false` when defining an attribute, like this:
526
529
 
527
530
  ```ruby
528
- class Word < ActiveRecord::Base
531
+ class Word < ApplicationRecord
529
532
  include Mobility
530
533
  translates :name, type: :string, cache: false
531
534
  end
@@ -546,7 +549,7 @@ Mobility-specific query method overrides.
546
549
  So assuming a model:
547
550
 
548
551
  ```ruby
549
- class Post < ActiveRecord::Base
552
+ class Post < ApplicationRecord
550
553
  include Mobility
551
554
  translates :title, type: :string
552
555
  translates :content, type: :text
@@ -590,7 +593,7 @@ If you would prefer to avoid the `i18n` scope everywhere, define it as a
590
593
  default scope on your model:
591
594
 
592
595
  ```ruby
593
- class Post < ActiveRecord::Base
596
+ class Post < ApplicationRecord
594
597
  include Mobility
595
598
  translates :title, type: :string
596
599
  translates :content, type: :text
@@ -617,7 +620,7 @@ configuration, or you can set it explicitly when defining a translated
617
620
  attribute, like this:
618
621
 
619
622
  ```ruby
620
- class Word < ActiveRecord::Base
623
+ class Word < ApplicationRecord
621
624
  translates :name, backend: :table
622
625
  end
623
626
  ```
@@ -626,7 +629,8 @@ This would set the `name` attribute to use the `Table` backend (see below).
626
629
  The `type` option (`type: :string` or `type: :text`) is missing here because
627
630
  this is an option specific to the KeyValue backend (specifying which shared
628
631
  table to store translations on). Backends have their own specific options; see
629
- the API documentation for which options are available for each.
632
+ the [Wiki][wiki] and [API documentation][api] for which options are available
633
+ for each.
630
634
 
631
635
  Everything else described above (fallbacks, dirty tracking, locale accessors,
632
636
  caching, querying, etc) is the same regardless of which backend you use.
@@ -648,12 +652,14 @@ rails generate mobility:translations post title:string content:text
648
652
 
649
653
  This will generate the `post_translations` table with columns `title` and
650
654
  `content`, and all other necessary columns and indices. For more details see
651
- the API documentation on the [`Mobility::Backend::Table`
655
+ the [Table
656
+ Backend](https://github.com/shioyama/mobility/wiki/Table-Backend) page of the
657
+ wiki and API documentation on the [`Mobility::Backend::Table`
652
658
  class](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Table).
653
659
 
654
660
  ### Column Backend (like Traco)
655
661
 
656
- The `Column` backend stores translationsi as columns with locale suffixes on
662
+ The `Column` backend stores translations as columns with locale suffixes on
657
663
  the model table. For an attribute `title`, these would be of the form
658
664
  `title_en`, `title_fr`, etc.
659
665
 
@@ -664,7 +670,9 @@ Use the `mobility:translations` generator to add columns for locales in
664
670
  rails generate mobility:translations post title:string content:text
665
671
  ```
666
672
 
667
- For more details, see the API documentation on the [`Mobility::Backend::Column`
673
+ For more details, see the [Column
674
+ Backend](https://github.com/shioyama/mobility/wiki/Column-Backend) page of the
675
+ wiki and API documentation on the [`Mobility::Backend::Column`
668
676
  class](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Column).
669
677
 
670
678
  ### PostgreSQL-specific Backends
@@ -672,7 +680,9 @@ class](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Column).
672
680
  Mobility also supports jsonb and Hstore storage options, if you are using
673
681
  PostgreSQL as your database. To use this option, create column(s) on the model
674
682
  table for each translated attribute, and set your backend to `:jsonb` or
675
- `:hstore`. Other details are covered in the API documentation
683
+ `:hstore`. Other details are covered in the [Postgres
684
+ Backend](https://github.com/shioyama/mobility/wiki/Postgres-Backends) page of
685
+ the wiki and in the API documentation
676
686
  ([`Mobility::Backend::Jsonb`](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Jsonb)
677
687
  and
678
688
  [`Mobility::Backend::Hstore`](http://www.rubydoc.info/gems/mobility/Mobility/Backend/Hstore)).
@@ -703,8 +713,9 @@ class MyClass
703
713
  end
704
714
  ```
705
715
 
706
- For details on how to define a backend class, see the [API documentation on the
707
- `Mobility::Backend`
716
+ For details on how to define a backend class, see the [Introduction to Mobility
717
+ Backends](https://github.com/shioyama/mobility/wiki/Introduction-to-Mobility-Backends)
718
+ page of the wiki and the [API documentation on the `Mobility::Backend`
708
719
  module](http://www.rubydoc.info/gems/mobility/Mobility/Backend).
709
720
 
710
721
  ### Testing Backends
data/lib/mobility.rb CHANGED
@@ -37,6 +37,7 @@ module Mobility
37
37
  autoload :BackendResetter, "mobility/backend_resetter"
38
38
  autoload :Configuration, "mobility/configuration"
39
39
  autoload :FallthroughAccessors, "mobility/fallthrough_accessors"
40
+ autoload :LocaleAccessors, "mobility/locale_accessors"
40
41
  autoload :InstanceMethods, "mobility/instance_methods"
41
42
  autoload :Translates, "mobility/translates"
42
43
  autoload :Wrapper, "mobility/wrapper"
@@ -115,16 +115,18 @@ with other backends.
115
115
  # @param [Array<String>] attributes_ Attributes to define backend for
116
116
  # @param [Hash] options_ Backend options hash
117
117
  # @option options_ [Class] model_class Class of model
118
- # @option options_ [Boolean, Array<Symbol>] locale_accessors Enable locale
119
- # accessors or specify locales for which accessors should be defined on
120
- # this model backend. Will default to +true+ if +dirty+ option is +true+.
121
118
  # @option options_ [Boolean] cache (true) Enable cache for this model backend
122
- # @option options_ [Boolean, Hash] fallbacks Enable fallbacks or specify
123
- # fallbacks for this model backend
124
119
  # @option options_ [Boolean] dirty Enable dirty tracking for this model
125
120
  # backend
121
+ # @option options_ [Boolean, Hash] fallbacks Enable fallbacks or specify
122
+ # fallbacks for this model backend
126
123
  # @option options_ [Boolean] fallthrough_accessors Enable fallthrough
127
124
  # locale accessors for this model backend
125
+ # @option options_ [Boolean, Array<Symbol>] locale_accessors Enable locale
126
+ # accessors or specify locales for which accessors should be defined on
127
+ # this model backend. Will default to +true+ if +dirty+ option is +true+.
128
+ # @option options_ [Boolean] presence (true) Enable presence filter on
129
+ # reads and writes
128
130
  # @raise [ArgumentError] if method is not reader, writer or accessor
129
131
  def initialize(method, *attributes_, **options_)
130
132
  raise ArgumentError, "method must be one of: reader, writer, accessor" unless %i[reader writer accessor].include?(method)
@@ -137,7 +139,7 @@ with other backends.
137
139
  if (options[:dirty] && options[:fallthrough_accessors] != false)
138
140
  options[:fallthrough_accessors] = true
139
141
  end
140
- include FallthroughAccessors.new(attributes) if options[:fallthrough_accessors]
142
+ include FallthroughAccessors.new(*attributes) if options[:fallthrough_accessors]
141
143
 
142
144
  @backend_class.configure!(options) if @backend_class.respond_to?(:configure!)
143
145
 
@@ -145,25 +147,12 @@ with other backends.
145
147
 
146
148
  @accessor_locales = options[:locale_accessors]
147
149
  @accessor_locales = Mobility.config.default_accessor_locales if @accessor_locales == true
150
+ include LocaleAccessors.new(*attributes, locales: @accessor_locales) if @accessor_locales
148
151
 
149
152
  attributes.each do |attribute|
150
153
  define_backend(attribute)
151
-
152
- if %i[accessor reader].include?(method)
153
- define_method attribute do |**options|
154
- mobility_get(attribute, options)
155
- end
156
-
157
- define_method "#{attribute}?" do |**options|
158
- mobility_present?(attribute, options)
159
- end
160
- end
161
-
162
- define_method "#{attribute}=" do |value, **options|
163
- mobility_set(attribute, value, **options)
164
- end if %i[accessor writer].include?(method)
165
-
166
- define_locale_accessors(attribute, @accessor_locales) if @accessor_locales
154
+ define_reader(attribute) if %i[accessor reader].include?(method)
155
+ define_writer(attribute) if %i[accessor writer].include?(method)
167
156
  end
168
157
  end
169
158
 
@@ -188,6 +177,7 @@ with other backends.
188
177
  backend_class.include(Backend::Cache) unless options[:cache] == false
189
178
  backend_class.include(Backend::Dirty.for(options[:model_class])) if options[:dirty]
190
179
  backend_class.include(Backend::Fallbacks) unless options[:fallbacks] == false
180
+ backend_class.include(Backend::Presence) unless options[:presence] == false
191
181
  end
192
182
 
193
183
  def define_backend(attribute)
@@ -198,18 +188,22 @@ with other backends.
198
188
  end
199
189
  end
200
190
 
201
- def define_locale_accessors(attribute, locales)
202
- locales.each do |locale|
203
- normalized_locale = Mobility.normalize_locale(locale)
204
- define_method "#{attribute}_#{normalized_locale}" do |**options|
205
- mobility_get(attribute, options.merge(locale: locale))
206
- end
207
- define_method "#{attribute}_#{normalized_locale}?" do |**options|
208
- mobility_present?(attribute, options.merge(locale: locale))
209
- end
210
- define_method "#{attribute}_#{normalized_locale}=" do |value, **options|
211
- mobility_set(attribute, value, locale: locale)
212
- end
191
+ def define_reader(attribute)
192
+ define_method attribute do |locale: Mobility.locale, **options|
193
+ Mobility.enforce_available_locales!(locale)
194
+ mobility_backend_for(attribute).read(locale.to_sym, options)
195
+ end
196
+
197
+ define_method "#{attribute}?" do |locale: Mobility.locale, **options|
198
+ Mobility.enforce_available_locales!(locale)
199
+ mobility_backend_for(attribute).read(locale.to_sym, options).present?
200
+ end
201
+ end
202
+
203
+ def define_writer(attribute)
204
+ define_method "#{attribute}=" do |value, locale: Mobility.locale, **options|
205
+ Mobility.enforce_available_locales!(locale)
206
+ mobility_backend_for(attribute).write(locale.to_sym, value, options)
213
207
  end
214
208
  end
215
209
 
@@ -60,6 +60,7 @@ On top of this, a backend will normally:
60
60
  autoload :KeyValue, 'mobility/backend/key_value'
61
61
  autoload :Null, 'mobility/backend/null'
62
62
  autoload :OrmDelegator, 'mobility/backend/orm_delegator'
63
+ autoload :Presence, 'mobility/backend/presence'
63
64
  autoload :Sequel, 'mobility/backend/sequel'
64
65
  autoload :Serialized, 'mobility/backend/serialized'
65
66
  autoload :Table, 'mobility/backend/table'
@@ -47,11 +47,11 @@ value of the translated attribute if passed to it.
47
47
  def setup_model(model_class, attributes, **options)
48
48
  super
49
49
  model_class.class_eval do
50
- %w[changed? change was will_change! previously_changed? previous_change].each do |suffix|
50
+ Mobility::Backend::ActiveModel::Dirty.method_suffixes.each do |suffix|
51
51
  attributes.each do |attribute|
52
52
  class_eval <<-EOM, __FILE__, __LINE__ + 1
53
- def #{attribute}_#{suffix}
54
- attribute_#{suffix}(Mobility.normalize_locale_accessor("#{attribute}"))
53
+ def #{attribute}#{suffix}
54
+ attribute#{suffix}(Mobility.normalize_locale_accessor("#{attribute}"))
55
55
  end
56
56
  EOM
57
57
  end
@@ -80,6 +80,18 @@ value of the translated attribute if passed to it.
80
80
  model_class.include restore_methods
81
81
  end
82
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
+ begin
90
+ Class.new do
91
+ include ::ActiveModel::Dirty
92
+ end.attribute_method_matchers.map { |m| m.suffix }
93
+ end.select { |m| m =~ /\A_/ }
94
+ end
83
95
  end
84
96
  end
85
97
  end
@@ -43,7 +43,7 @@ or locales.)
43
43
  # @!group Backend Accessors
44
44
  # @!macro backend_writer
45
45
  def write(locale, value, **_)
46
- model.write_attribute(column(locale), value)
46
+ model.send(:write_attribute, column(locale), value)
47
47
  end
48
48
 
49
49
  # @!group Backend Configuration
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module Mobility
2
4
  module Backend
3
5
  =begin
@@ -54,7 +56,7 @@ Implements the {Mobility::Backend::KeyValue} backend for ActiveRecord models.
54
56
  def self.configure!(options)
55
57
  super
56
58
  type = options[:type]
57
- options[:class_name] ||= Mobility::ActiveRecord.const_get("#{type.capitalize}Translation")
59
+ options[:class_name] ||= Mobility::ActiveRecord.const_get("#{type.capitalize}Translation".freeze)
58
60
  options[:class_name] = options[:class_name].constantize if options[:class_name].is_a?(String)
59
61
  options[:association_name] ||= options[:class_name].table_name.to_sym
60
62
  %i[type association_name].each { |key| options[key] = options[key].to_sym }
@@ -77,7 +79,6 @@ Implements the {Mobility::Backend::KeyValue} backend for ActiveRecord models.
77
79
  has_many association_name, ->{ where key: association_attributes },
78
80
  as: :translatable,
79
81
  class_name: translations_class,
80
- dependent: :destroy,
81
82
  inverse_of: :translatable,
82
83
  autosave: true
83
84
  before_save do
@@ -85,6 +86,7 @@ Implements the {Mobility::Backend::KeyValue} backend for ActiveRecord models.
85
86
  send(association_name).destroy(translation)
86
87
  end
87
88
  end
89
+ after_destroy :mobility_destroy_key_value_translations
88
90
 
89
91
  mod = Module.new do
90
92
  define_method :i18n do
@@ -92,6 +94,16 @@ Implements the {Mobility::Backend::KeyValue} backend for ActiveRecord models.
92
94
  end
93
95
  end
94
96
  extend mod
97
+
98
+ private
99
+
100
+ # Clean up *all* leftover translations of this model, only once.
101
+ def mobility_destroy_key_value_translations
102
+ [:string, :text].freeze.each do |type|
103
+ Mobility::ActiveRecord.const_get("#{type.capitalize}Translation".freeze).
104
+ where(translatable: self).destroy_all
105
+ end
106
+ end unless private_instance_methods(false).include?(:mobility_destroy_key_value_translations)
95
107
  end
96
108
 
97
109
  # @!group Cache Methods
@@ -95,11 +95,12 @@ locale was +nil+.
95
95
  # @param [Boolean,Symbol,Array] fallback
96
96
  # +false+ to disable fallbacks on lookup, or a locale or array of
97
97
  # locales to set fallback(s) for this lookup.
98
- def read(locale, fallback: nil, **options)
98
+ def read(locale, **options)
99
99
  if !options[:fallbacks].nil?
100
100
  warn "You passed an option with key 'fallbacks', which will be
101
101
  ignored. Did you mean 'fallback'?"
102
102
  end
103
+ fallback = options.delete(:fallback)
103
104
  return super if fallback == false || (fallback.nil? && fallbacks.nil?)
104
105
  (fallback ? [locale, *fallback] : fallbacks[locale]).detect do |fallback_locale|
105
106
  value = super(fallback_locale, **options)
@@ -0,0 +1,30 @@
1
+ module Mobility
2
+ module Backend
3
+ =begin
4
+
5
+ Applies presence filter to values fetched from backend and to values set on
6
+ backend. Included by default, but can be disabled with presence: false option.
7
+
8
+ =end
9
+ module Presence
10
+ # @group Backend Accessors
11
+ # @!macro backend_reader
12
+ # @param [Boolean] presence
13
+ # *false* to disable presence filter.
14
+ def read(locale, **options)
15
+ return super if options.delete(:presence) == false
16
+ value = super
17
+ value == false ? value : value.presence
18
+ end
19
+
20
+ # @group Backend Accessors
21
+ # @!macro backend_writer
22
+ # @param [Boolean] presence
23
+ # *false* to disable presence filter.
24
+ def write(locale, value, **options)
25
+ return super if options.delete(:presence) == false
26
+ super(locale, value == false ? value : value.presence, **options)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module Mobility
2
4
  module Backend
3
5
  =begin
@@ -52,7 +54,7 @@ Implements the {Mobility::Backend::KeyValue} backend for Sequel models.
52
54
  super
53
55
  raise CacheRequired, "Cache required for Sequel::KeyValue backend" if options[:cache] == false
54
56
  type = options[:type]
55
- options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translation")
57
+ options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translation".freeze)
56
58
  options[:class_name] = options[:class_name].constantize if options[:class_name].is_a?(String)
57
59
  options[:association_name] ||= options[:class_name].table_name.to_sym
58
60
  %i[type association_name].each { |key| options[key] = options[key].to_sym }
@@ -77,8 +79,6 @@ options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translati
77
79
  clearer: proc { send(:"#{association_name}_dataset").update(translatable_id: nil, translatable_type: nil) },
78
80
  class: translations_class
79
81
 
80
- plugin :association_dependencies, association_name => :destroy
81
-
82
82
  callback_methods = Module.new do
83
83
  define_method :before_save do
84
84
  super()
@@ -99,6 +99,22 @@ options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translati
99
99
  extend extension
100
100
 
101
101
  include Mobility::Sequel::ColumnChanges.new(attributes)
102
+
103
+ private
104
+
105
+ # Clean up *all* leftover translations of this model, only once.
106
+ def self.mobility_key_value_callbacks_module
107
+ @mobility_key_value_destroy_callbacks_module ||= Module.new do
108
+ def after_destroy
109
+ super
110
+ [:string, :text].freeze.each do |type|
111
+ Mobility::Sequel.const_get("#{type.capitalize}Translation".freeze).
112
+ where(translatable_id: id, translatable_type: self.class.name).destroy
113
+ end
114
+ end
115
+ end
116
+ end unless respond_to?(:mobility_key_value_callbacks_module, true)
117
+ include mobility_key_value_callbacks_module
102
118
  end
103
119
 
104
120
  # @!group Cache Methods
@@ -34,7 +34,7 @@ model class is generated.
34
34
 
35
35
  =end
36
36
  class FallthroughAccessors < Module
37
- # @param [Array<String>] Array of attributes
37
+ # @param [String] One or more attributes
38
38
  def initialize(*attributes)
39
39
  method_name_regex = /\A(#{attributes.join('|'.freeze)})_([a-z]{2}(_[a-z]{2})?)(=?|\??)\z/.freeze
40
40
 
@@ -11,26 +11,5 @@ Instance methods attached to all model classes when model includes or extends
11
11
  def mobility_backend_for(attribute)
12
12
  send(Backend.method_name(attribute))
13
13
  end
14
-
15
- private
16
-
17
- def mobility_get(*args)
18
- value = mobility_read(*args)
19
- value == false ? value : value.presence
20
- end
21
-
22
- def mobility_present?(*args)
23
- mobility_read(*args).present?
24
- end
25
-
26
- def mobility_set(attribute, value, locale: Mobility.locale, **options)
27
- Mobility.enforce_available_locales!(locale)
28
- mobility_backend_for(attribute).write(locale.to_sym, value == false ? value : value.presence, **options)
29
- end
30
-
31
- def mobility_read(attribute, locale: Mobility.locale, **options)
32
- Mobility.enforce_available_locales!(locale)
33
- mobility_backend_for(attribute).read(locale.to_sym, options)
34
- end
35
14
  end
36
15
  end
@@ -0,0 +1,55 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Mobility
4
+ =begin
5
+
6
+ Defines methods for a set of locales to access translated attributes in those
7
+ locales directly with a method call, using a suffix including the locale:
8
+
9
+ article.title_pt_br
10
+
11
+ If no locales are passed as an option to the initializer,
12
+ +I18n.available_locales+ will be used by default.
13
+
14
+ @example
15
+ class Post
16
+ def title
17
+ "title in #{Mobility.locale}"
18
+ end
19
+ include Mobility::LocaleAccessors.new("title", locales: [:en, :fr])
20
+ end
21
+
22
+ Mobility.locale = :en
23
+ post = Post.new
24
+ post.title
25
+ #=> "title in en"
26
+ post.title_fr
27
+ #=> "title in fr"
28
+
29
+ =end
30
+ class LocaleAccessors < Module
31
+ # @param [String] One or more attributes
32
+ # @param [Array<Symbol>] Locales
33
+ def initialize(*attributes, locales: I18n.available_locales)
34
+ warning_message = "locale passed as option to locale accessor will be ignored".freeze
35
+
36
+ attributes.each do |attribute|
37
+ locales.each do |locale|
38
+ normalized_locale = Mobility.normalize_locale(locale)
39
+ define_method "#{attribute}_#{normalized_locale}" do |**options|
40
+ warn warning_message if options.delete(:locale)
41
+ Mobility.with_locale(locale) { send(attribute, options) }
42
+ end
43
+ define_method "#{attribute}_#{normalized_locale}?" do |**options|
44
+ warn warning_message if options.delete(:locale)
45
+ Mobility.with_locale(locale) { send("#{attribute}?", options) }
46
+ end
47
+ define_method "#{attribute}_#{normalized_locale}=" do |value, **options|
48
+ warn warning_message if options.delete(:locale)
49
+ Mobility.with_locale(locale) { send("#{attribute}=", value, options) }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -11,18 +11,18 @@ is called.
11
11
  def initialize(attributes)
12
12
  @attributes = attributes
13
13
 
14
- define_method :mobility_set do |attribute, value, locale: Mobility.locale|
15
- if attributes.include?(attribute)
16
- column = attribute.to_sym
17
- column_with_locale = :"#{attribute}_#{Mobility.normalize_locale(locale)}"
18
- if mobility_get(attribute) != value
14
+ attributes.each do |attribute|
15
+ define_method "#{attribute}=" do |value, **options|
16
+ if send(attribute) != value
17
+ locale = options[:locale] || Mobility.locale
18
+ column = attribute.to_sym
19
+ column_with_locale = :"#{attribute}_#{Mobility.normalize_locale(locale)}"
19
20
  @changed_columns << column_with_locale if !changed_columns.include?(column_with_locale)
20
21
  @changed_columns << column if !changed_columns.include?(column)
21
22
  end
23
+ super(value, **options)
22
24
  end
23
- super(attribute, value, locale: locale)
24
25
  end
25
- private :mobility_set
26
26
  end
27
27
  end
28
28
  end
@@ -1,3 +1,3 @@
1
1
  module Mobility
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.12"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobility
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Salzberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-26 00:00:00.000000000 Z
11
+ date: 2017-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: request_store
@@ -186,6 +186,7 @@ files:
186
186
  - lib/mobility/backend/key_value.rb
187
187
  - lib/mobility/backend/null.rb
188
188
  - lib/mobility/backend/orm_delegator.rb
189
+ - lib/mobility/backend/presence.rb
189
190
  - lib/mobility/backend/sequel.rb
190
191
  - lib/mobility/backend/sequel/column.rb
191
192
  - lib/mobility/backend/sequel/column/query_methods.rb
@@ -210,6 +211,7 @@ files:
210
211
  - lib/mobility/core_ext/string.rb
211
212
  - lib/mobility/fallthrough_accessors.rb
212
213
  - lib/mobility/instance_methods.rb
214
+ - lib/mobility/locale_accessors.rb
213
215
  - lib/mobility/orm.rb
214
216
  - lib/mobility/rails.rb
215
217
  - lib/mobility/sequel.rb