mobility 0.1.6 → 0.1.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e672887874e0e97c1862c468003721bcabfecb34
4
- data.tar.gz: afc6154a1bb62a9e3a633ea7b65a31856441ad9a
3
+ metadata.gz: 2c83db6c82d88d2972c328ea414426416b57aee7
4
+ data.tar.gz: b058744cc114446d2d641046b3d14cbc843eef8f
5
5
  SHA512:
6
- metadata.gz: 5c461d32a74de20c33103be3f038443edf0768e25fca9f654d555fbeaa2b8e1fbf7587cf8b2a39264bc602cd26278929110eb3ac71d68c2f3dda8fb3c0cbfbca
7
- data.tar.gz: e59f35065d9be082c7ecb33ec2af3097b9e782b3ae3822749cb4df38fbfb0766927171b8b171f18746b64ecb477ebaa97aecc815c8117096b9d6da8f74d2b42c
6
+ metadata.gz: 5d44c71b7381046f0fc63ea7cb7aed5506aa2860324dced1a6d58c07d3b1aa9fe9c10d337d17d42e4807a722a78faca46b2e386611b4e2a984176daaf90efdd3
7
+ data.tar.gz: 3e29ad8f1a11cb343291dbe0fa63633433e2463a40e70b85834898f808064b85fb129fba59db7588471743e4ee90c2bf276a8f50f90b8c9993cc2b1870d39107
@@ -2,30 +2,47 @@
2
2
 
3
3
  ## 0.1
4
4
 
5
+ ### 0.1.7
6
+ * Allow passing fallback locale or locales to getter method
7
+ ([#9](https://github.com/shioyama/mobility/pull/9))
8
+ * Add missing indices on key-value string/text translation tables
9
+ ([1e00e0](https://github.com/shioyama/mobility/commit/1e00e0d957478f2408fbac1ee853f829489263e2),
10
+ [574172](https://github.com/shioyama/mobility/commit/574172dc88823a35c60ff963ff9c40b7c05771d7))
11
+
12
+ ### 0.1.6
13
+ * Return accessor locales instead of Proc from default_accessor_locales
14
+ ([825f75](https://github.com/shioyama/mobility/commit/825f75de6107287a5de70db439d8aec5e4a47977))
15
+ * Fix support for locales in dirty modules
16
+ ([0b40d6](https://github.com/shioyama/mobility/commit/0b40d66ea0c816d4fb57deceff9344f5128a593f))
17
+ * Add FallthroughAccessors for use in dirty modules
18
+ ([#4](https://github.com/shioyama/mobility/pull/4))
19
+ * Only raise InvalidLocale exception if I18n.enforce_available_locales is true
20
+ ([979c36](https://github.com/shioyama/mobility/commit/979c365794d3df90a2d23ad50519ff354686a493))
21
+
5
22
  ### 0.1.5
6
- * Add `accessor_method` to default initializer ([d4a9da98cae71de2fb9ee3d29c64decef5a16010](https://github.com/shioyama/mobility/commit/d4a9da98cae71de2fb9ee3d29c64decef5a16010))
7
- * Include AR version in generated migrations ([ac3dfbbc053089b01dcc73d0b617fefaeaaa85cb](https://github.com/shioyama/mobility/commit/ac3dfbbc053089b01dcc73d0b617fefaeaaa85cb))
8
- * Add `untranslated_attributes` method ([50e97f12ea219321ef9f61792e909299f570ba23](https://github.com/shioyama/mobility/commit/50e97f12ea219321ef9f61792e909299f570ba23))
9
- * Do not require `active_support/core_ext/nil` ([39e24596482f03302542e524ca6f17275a778644](https://github.com/shioyama/mobility/commit/39e24596482f03302542e524ca6f17275a778644))
10
- * Handle false values correctly when getting and setting ([bdf6f199aaa8318a73c5aa6332aee8d7aad254f6](https://github.com/shioyama/mobility/commit/bdf6f199aaa8318a73c5aa6332aee8d7aad254f6))
11
- * Use proc to define accessor locales from `I18n.available_locales` ([3cd786814d8044ae5d64f939c3a7b5c49b322bc6](https://github.com/shioyama/mobility/commit/3cd786814d8044ae5d64f939c3a7b5c49b322bc6))
23
+ * Add `accessor_method` to default initializer ([d4a9da](https://github.com/shioyama/mobility/commit/d4a9da98cae71de2fb9ee3d29c64decef5a16010))
24
+ * Include AR version in generated migrations ([ac3dfb](https://github.com/shioyama/mobility/commit/ac3dfbbc053089b01dcc73d0b617fefaeaaa85cb))
25
+ * Add `untranslated_attributes` method ([50e97f](https://github.com/shioyama/mobility/commit/50e97f12ea219321ef9f61792e909299f570ba23))
26
+ * Do not require `active_support/core_ext/nil` ([39e245](https://github.com/shioyama/mobility/commit/39e24596482f03302542e524ca6f17275a778644))
27
+ * Handle false values correctly when getting and setting ([bdf6f1](https://github.com/shioyama/mobility/commit/bdf6f199aaa8318a73c5aa6332aee8d7aad254f6))
28
+ * Use proc to define accessor locales from `I18n.available_locales` ([3cd786](https://github.com/shioyama/mobility/commit/3cd786814d8044ae5d64f939c3a7b5c49b322bc6))
12
29
  * Do not mark attribute as changed if value is the same (fixed in [#2](https://github.com/shioyama/mobility/pull/2))
13
30
  * Pass on any args to original reload method when overriding (fixed in [#3](https://github.com/shioyama/mobility/pull/3))
14
31
 
15
32
  ### 0.1.4
16
- * Fix configuration reload issue ([#1](https://github.com/shioyama/mobility/issues/1), fixed in [478b669dae90edf9feb7c011ae93e8157dc4e2b4](https://github.com/shioyama/mobility/commit/478b669dae90edf9feb7c011ae93e8157dc4e2b4))
17
- * Code refactoring/cleanup ([e4dcc791c246e377352b9ac154d2b1c4aec8e98e](https://github.com/shioyama/mobility/commit/e4dcc791c246e377352b9ac154d2b1c4aec8e98e), [64f434ea7a46c9353c3638c58a3258f0fcb81821](https://github.com/shioyama/mobility/commit/64f434ea7a46c9353c3638c58a3258f0fcb81821), [8df2bbdead883725d2c87020f836b644b4d28e5c](https://github.com/shioyama/mobility/commit/8df2bbdead883725d2c87020f836b644b4d28e5c), [326a0977c98348dad85a927c20dd69fe5acb2a9e](https://github.com/shioyama/mobility/commit/326a0977c98348dad85a927c20dd69fe5acb2a9e))
18
- * Allow using Sequel `plugin` to include Mobility in model ([b0db7cc28a47e13c6888ef263260e8dff281543d](https://github.com/shioyama/mobility/commit/b0db7cc28a47e13c6888ef263260e8dff281543d))
33
+ * Fix configuration reload issue ([#1](https://github.com/shioyama/mobility/issues/1), fixed in [478b66](https://github.com/shioyama/mobility/commit/478b669dae90edf9feb7c011ae93e8157dc4e2b4))
34
+ * Code refactoring/cleanup ([e4dcc7](https://github.com/shioyama/mobility/commit/e4dcc791c246e377352b9ac154d2b1c4aec8e98e), [64f434](https://github.com/shioyama/mobility/commit/64f434ea7a46c9353c3638c58a3258f0fcb81821), [8df2bb](https://github.com/shioyama/mobility/commit/8df2bbdead883725d2c87020f836b644b4d28e5c), [326a09](https://github.com/shioyama/mobility/commit/326a0977c98348dad85a927c20dd69fe5acb2a9e))
35
+ * Allow using Sequel `plugin` to include Mobility in model ([b0db7c](https://github.com/shioyama/mobility/commit/b0db7cc28a47e13c6888ef263260e8dff281543d))
19
36
 
20
37
  ### 0.1.3
21
38
 
22
39
  * Add homepage to gemspec
23
40
  * Pass backend class as context to `translates`
24
- ([adf93e3c6bb314b73fbd43b221819310a1407c4d](https://github.com/shioyama/mobility/commit/adf93e3c6bb314b73fbd43b221819310a1407c4d))
41
+ ([adf93e](https://github.com/shioyama/mobility/commit/adf93e3c6bb314b73fbd43b221819310a1407c4d))
25
42
 
26
43
  ### 0.1.2
27
44
 
28
45
  * Fix issues with querying in ActiveRecord jsonb and hstore backends
29
- ([527908d9317daee6bf91e3e1a188fb64365f7bab](https://github.com/shioyama/mobility/commit/527908d9317daee6bf91e3e1a188fb64365f7bab)
46
+ ([527908](https://github.com/shioyama/mobility/commit/527908d9317daee6bf91e3e1a188fb64365f7bab)
30
47
  and
31
- [5e6addd6f01cf255f5e71666324502ace96d3eac](https://github.com/shioyama/mobility/commit/5e6addd6f01cf255f5e71666324502ace96d3eac))
48
+ [5e6add](https://github.com/shioyama/mobility/commit/5e6addd6f01cf255f5e71666324502ace96d3eac))
@@ -1,33 +1,33 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mobility (0.1.5)
4
+ mobility (0.1.6)
5
5
  i18n (>= 0.6.10)
6
6
  request_store (~> 1.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actionpack (5.0.1)
12
- actionview (= 5.0.1)
13
- activesupport (= 5.0.1)
11
+ actionpack (5.0.2)
12
+ actionview (= 5.0.2)
13
+ activesupport (= 5.0.2)
14
14
  rack (~> 2.0)
15
15
  rack-test (~> 0.6.3)
16
16
  rails-dom-testing (~> 2.0)
17
17
  rails-html-sanitizer (~> 1.0, >= 1.0.2)
18
- actionview (5.0.1)
19
- activesupport (= 5.0.1)
18
+ actionview (5.0.2)
19
+ activesupport (= 5.0.2)
20
20
  builder (~> 3.1)
21
21
  erubis (~> 2.7.0)
22
22
  rails-dom-testing (~> 2.0)
23
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
24
- activemodel (5.0.1)
25
- activesupport (= 5.0.1)
26
- activerecord (5.0.1)
27
- activemodel (= 5.0.1)
28
- activesupport (= 5.0.1)
23
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
24
+ activemodel (5.0.2)
25
+ activesupport (= 5.0.2)
26
+ activerecord (5.0.2)
27
+ activemodel (= 5.0.2)
28
+ activesupport (= 5.0.2)
29
29
  arel (~> 7.0)
30
- activesupport (5.0.1)
30
+ activesupport (5.0.2)
31
31
  concurrent-ruby (~> 1.0, >= 1.0.2)
32
32
  i18n (~> 0.7)
33
33
  minitest (~> 5.1)
@@ -36,7 +36,7 @@ GEM
36
36
  builder (3.2.3)
37
37
  byebug (9.0.6)
38
38
  coderay (1.1.1)
39
- concurrent-ruby (1.0.4)
39
+ concurrent-ruby (1.0.5)
40
40
  database_cleaner (1.5.3)
41
41
  diff-lcs (1.3)
42
42
  erubis (2.7.0)
@@ -92,9 +92,9 @@ GEM
92
92
  nokogiri (~> 1.6)
93
93
  rails-html-sanitizer (1.0.3)
94
94
  loofah (~> 2.0)
95
- railties (5.0.1)
96
- actionpack (= 5.0.1)
97
- activesupport (= 5.0.1)
95
+ railties (5.0.2)
96
+ actionpack (= 5.0.2)
97
+ activesupport (= 5.0.2)
98
98
  method_source
99
99
  rake (>= 0.8.7)
100
100
  thor (>= 0.18.1, < 2.0)
data/README.md CHANGED
@@ -56,7 +56,7 @@ Mobility](http://dejimata.com/2017/3/3/translating-with-mobility).
56
56
  Add this line to your application's Gemfile:
57
57
 
58
58
  ```ruby
59
- gem 'mobility', '~> 0.1.6'
59
+ gem 'mobility', '~> 0.1.7'
60
60
  ```
61
61
 
62
62
  To translate attributes on a model, you must include (or extend) `Mobility`,
@@ -386,7 +386,7 @@ Alternatively, just using `locale_accessors: true` will enable all locales in
386
386
 
387
387
  An alternative to using the `locale_accessors` option is to use the
388
388
  `fallthrough_accessors` option (defined in {Mobility::FallthroughAccessors})
389
- with `fallthrough_accessors: true`. This uses +method_missing+ to implicitly
389
+ with `fallthrough_accessors: true`. This uses `method_missing` to implicitly
390
390
  define the same methods as above, but supporting any locale without any method
391
391
  definitions. [Dirty tracking](#dirty) enables fallthrough locales for tracking
392
392
  attribute changes. (Both locale accessors and fallthrough locales can be used
@@ -427,6 +427,9 @@ class Post < ActiveRecord::Base
427
427
  end
428
428
  ```
429
429
 
430
+ Internally, Mobility assigns the fallbacks hash to an instance of
431
+ `I18n::Locale::Fallbacks.new`.
432
+
430
433
  By setting fallbacks for English and French to Japanese, values will fall
431
434
  through to the Japanese value if none is present for either of these locales:
432
435
 
@@ -445,17 +448,29 @@ post.title_fr
445
448
 
446
449
  You can optionally disable fallbacks to get the real value for a given locale
447
450
  (for example, to check if a value in a particular locale is set or not) by
448
- passing `fallbacks: false` to the getter method:
451
+ passing `fallback: false` (note that the key is the *singular*, not plural) to
452
+ the getter method:
449
453
 
450
454
  ```ruby
451
- post.title(fallbacks: false)
455
+ post.title(fallback: false)
452
456
  #=> nil
453
- post.title_fr(fallbacks: false)
457
+ post.title(locale: :fr, fallback: false)
454
458
  #=> nil
455
459
  ```
456
460
 
457
- (Mobility assigns the fallbacks hash to an instance of
458
- `I18n::Locale::Fallbacks.new`.)
461
+ You can also set the fallback locales for a single read by passing one or more
462
+ locales, like this:
463
+
464
+ ```ruby
465
+ post.title_fr = "mobilité: aptitude à bouger, à se déplacer, à changer, à évoluer"
466
+ post.save
467
+ post.title
468
+ #=> nil
469
+ post.title(fallback: :fr)
470
+ #=> "mobilité: aptitude à bouger, à se déplacer, à changer, à évoluer"
471
+ post.title(fallback: [:ja, :fr])
472
+ #=> "Mobility(名詞):動きやすさ、可動性"
473
+ ```
459
474
 
460
475
  For more details, see: {Mobility::Backend::Fallbacks}.
461
476
 
@@ -10,6 +10,7 @@ class CreateStringTranslations < ActiveRecord::Migration[<%= ActiveRecord::Migra
10
10
  t.timestamps
11
11
  end
12
12
  add_index :mobility_string_translations, [:translatable_id, :translatable_type, :locale, :key], unique: true, name: :index_mobility_string_translations_on_keys
13
- add_index :mobility_string_translations, [:translatable_id, :translatable_type], name: :index_mobility_string_translations_on_translatable
13
+ add_index :mobility_string_translations, [:translatable_id, :translatable_type, :key], name: :index_mobility_string_translations_on_translatable_attribute
14
+ add_index :mobility_string_translations, [:translatable_type, :key, :value, :locale], name: :index_mobility_string_translations_on_query_keys
14
15
  end
15
16
  end
@@ -10,6 +10,6 @@ class CreateTextTranslations < ActiveRecord::Migration[<%= ActiveRecord::Migrati
10
10
  t.timestamps
11
11
  end
12
12
  add_index :mobility_text_translations, [:translatable_id, :translatable_type, :locale, :key], unique: true, name: :index_mobility_text_translations_on_keys
13
- add_index :mobility_text_translations, [:translatable_id, :translatable_type], name: :index_mobility_text_translations_on_translatable
13
+ add_index :mobility_text_translations, [:translatable_id, :translatable_type, :key], name: :index_mobility_text_translations_on_translatable_attribute
14
14
  end
15
15
  end
@@ -58,10 +58,9 @@ module Mobility
58
58
 
59
59
  begin
60
60
  require "rails"
61
- autoload :InstallGenerator, "generators/mobility/install_generator"
62
61
  Loaded::Rails = true
62
+ require "mobility/rails"
63
63
  rescue LoadError
64
- class InstallGenerator; end
65
64
  Loaded::Rails = false
66
65
  end
67
66
 
@@ -76,12 +76,11 @@ On top of this, a backend will normally:
76
76
  # @!macro [new] backend_constructor
77
77
  # @param model Model on which backend is defined
78
78
  # @param [String] attribute Backend attribute
79
- # @option options [Hash] fallbacks Fallbacks hash
80
- def initialize(model, attribute, **options)
79
+ # @param [Hash] fallbacks Fallbacks hash
80
+ def initialize(model, attribute, fallbacks: nil, **options)
81
81
  @model = model
82
82
  @attribute = attribute
83
83
  @options = options
84
- fallbacks = options[:fallbacks]
85
84
  @fallbacks = I18n::Locale::Fallbacks.new(fallbacks) if fallbacks.is_a?(Hash)
86
85
  end
87
86
 
@@ -28,7 +28,7 @@ value of the translated attribute if passed to it.
28
28
  locale_accessor = Mobility.normalize_locale_accessor(attribute, locale)
29
29
  if model.changed_attributes.has_key?(locale_accessor) && model.changed_attributes[locale_accessor] == value
30
30
  model.attributes_changed_by_setter.except!(locale_accessor)
31
- elsif read(locale, options.merge(fallbacks: false)) != value
31
+ elsif read(locale, options.merge(fallback: false)) != value
32
32
  model.send(:attribute_will_change!, locale_accessor)
33
33
  end
34
34
  super
@@ -21,7 +21,7 @@ Implements the {Mobility::Backend::Column} backend for ActiveRecord models.
21
21
  =end
22
22
  class ActiveRecord::Column
23
23
  include Backend
24
- include Mobility::Backend::Column
24
+ include Backend::Column
25
25
 
26
26
  autoload :QueryMethods, 'mobility/backend/active_record/column/query_methods'
27
27
 
@@ -20,6 +20,7 @@ Implements the {Mobility::Backend::KeyValue} backend for ActiveRecord models.
20
20
  =end
21
21
  class ActiveRecord::KeyValue
22
22
  include Backend
23
+ include Backend::KeyValue
23
24
 
24
25
  autoload :QueryMethods, 'mobility/backend/active_record/key_value/query_methods'
25
26
 
@@ -51,13 +52,9 @@ Implements the {Mobility::Backend::KeyValue} backend for ActiveRecord models.
51
52
  # @option options [String,Class] class_name ({Mobility::ActiveRecord::TextTranslation}) Translation class
52
53
  # @raise [ArgumentError] if type is not either :text or :string
53
54
  def self.configure!(options)
54
- options[:type] ||= :text
55
- case type = options[:type].to_sym
56
- when :text, :string
57
- options[:class_name] ||= Mobility::ActiveRecord.const_get("#{type.capitalize}Translation")
58
- else
59
- raise ArgumentError, "type must be one of: [text, string]"
60
- end
55
+ super
56
+ type = options[:type]
57
+ options[:class_name] ||= Mobility::ActiveRecord.const_get("#{type.capitalize}Translation")
61
58
  options[:class_name] = options[:class_name].constantize if options[:class_name].is_a?(String)
62
59
  options[:association_name] ||= options[:class_name].table_name.to_sym
63
60
  %i[type association_name].each { |key| options[key] = options[key].to_sym }
@@ -1,37 +1,12 @@
1
1
  module Mobility
2
2
  module Backend
3
3
  class ActiveRecord::KeyValue::QueryMethods < ActiveRecord::QueryMethods
4
- def initialize(attributes, **options)
4
+ def initialize(attributes, association_name: nil, class_name: nil, **_)
5
5
  super
6
- association_name, translations_class = options[:association_name], options[:class_name]
7
- @association_name = association_name
8
- attributes_extractor = @attributes_extractor
9
-
10
- define_method :"join_#{association_name}" do |*attributes, **options|
11
- attributes.inject(self) do |relation, attribute|
12
- t = translations_class.arel_table.alias(:"#{attribute}_#{association_name}")
13
- m = arel_table
14
- join_type = options[:outer_join] ? Arel::Nodes::OuterJoin : Arel::Nodes::InnerJoin
15
- relation.joins(m.join(t, join_type).
16
- on(t[:key].eq(attribute).
17
- and(t[:locale].eq(Mobility.locale).
18
- and(t[:translatable_type].eq(name).
19
- and(t[:translatable_id].eq(m[:id]))))).join_sources)
20
- end
21
- end
6
+ @association_name = association_name
22
7
 
23
- define_method :where! do |opts, *rest|
24
- if i18n_keys = attributes_extractor.call(opts)
25
- opts = opts.with_indifferent_access
26
- i18n_nulls = i18n_keys.select { |key| opts[key].nil? }
27
- i18n_keys.each { |attr| opts["#{attr}_#{association_name}"] = { value: opts.delete(attr) }}
28
- super(opts, *rest).
29
- send("join_#{association_name}", *(i18n_keys - i18n_nulls)).
30
- send("join_#{association_name}", *i18n_nulls, outer_join: true)
31
- else
32
- super(opts, *rest)
33
- end
34
- end
8
+ define_join_method(association_name, class_name)
9
+ define_query_methods(association_name)
35
10
 
36
11
  attributes.each do |attribute|
37
12
  define_method :"find_by_#{attribute}" do |value|
@@ -58,6 +33,40 @@ module Mobility
58
33
  end
59
34
  relation.model.mobility_where_chain.prepend(mod)
60
35
  end
36
+
37
+ private
38
+
39
+ def define_join_method(association_name, translation_class)
40
+ define_method :"join_#{association_name}" do |*attributes, **options|
41
+ attributes.inject(self) do |relation, attribute|
42
+ t = translation_class.arel_table.alias(:"#{attribute}_#{association_name}")
43
+ m = arel_table
44
+ join_type = options[:outer_join] ? Arel::Nodes::OuterJoin : Arel::Nodes::InnerJoin
45
+ relation.joins(m.join(t, join_type).
46
+ on(t[:key].eq(attribute).
47
+ and(t[:locale].eq(Mobility.locale).
48
+ and(t[:translatable_type].eq(name).
49
+ and(t[:translatable_id].eq(m[:id]))))).join_sources)
50
+ end
51
+ end
52
+ end
53
+
54
+ def define_query_methods(association_name)
55
+ attributes_extractor = @attributes_extractor
56
+
57
+ define_method :where! do |opts, *rest|
58
+ if i18n_keys = attributes_extractor.call(opts)
59
+ opts = opts.with_indifferent_access
60
+ i18n_nulls = i18n_keys.select { |key| opts[key].nil? }
61
+ i18n_keys.each { |attr| opts["#{attr}_#{association_name}"] = { value: opts.delete(attr) }}
62
+ super(opts, *rest).
63
+ send("join_#{association_name}", *(i18n_keys - i18n_nulls)).
64
+ send("join_#{association_name}", *i18n_nulls, outer_join: true)
65
+ else
66
+ super(opts, *rest)
67
+ end
68
+ end
69
+ end
61
70
  end
62
71
  end
63
72
  end
@@ -1,16 +1,45 @@
1
1
  module Mobility
2
2
  module Backend
3
3
  class ActiveRecord::Table::QueryMethods < ActiveRecord::QueryMethods
4
- def initialize(attributes, **options)
4
+ def initialize(attributes, association_name: nil, model_class: nil, subclass_name: nil, **options)
5
5
  super
6
- association_name = options[:association_name]
7
- foreign_key = options[:foreign_key]
8
- @association_name = association_name
6
+
7
+ @association_name = association_name
8
+ @translation_class = translation_class = model_class.const_get(subclass_name)
9
+
10
+ define_join_method(association_name, translation_class, **options)
11
+ define_query_methods(association_name, translation_class, **options)
12
+
13
+ attributes.each do |attribute|
14
+ define_method :"find_by_#{attribute}" do |value|
15
+ find_by(attribute.to_sym => value)
16
+ end
17
+ end
18
+ end
19
+
20
+ def extended(relation)
21
+ super
22
+ association_name = @association_name
9
23
  attributes_extractor = @attributes_extractor
10
- translation_class = options[:model_class].const_get(options[:subclass_name])
11
- @translation_class = translation_class
12
- table_name = options[:table_name]
24
+ translation_class = @translation_class
25
+
26
+ mod = Module.new do
27
+ define_method :not do |opts, *rest|
28
+ if i18n_keys = attributes_extractor.call(opts)
29
+ opts = opts.with_indifferent_access
30
+ i18n_keys.each { |attr| opts["#{translation_class.table_name}.#{attr}"] = opts.delete(attr) }
31
+ super(opts, *rest).send("join_#{association_name}")
32
+ else
33
+ super(opts, *rest)
34
+ end
35
+ end
36
+ end
37
+ relation.model.mobility_where_chain.prepend(mod)
38
+ end
39
+
40
+ private
13
41
 
42
+ def define_join_method(association_name, translation_class, foreign_key: nil, table_name: nil, **_)
14
43
  define_method :"join_#{association_name}" do |**options|
15
44
  return self if (@__mobility_table_joined || []).include?(table_name)
16
45
  (@__mobility_table_joined ||= []) << table_name
@@ -21,6 +50,10 @@ module Mobility
21
50
  on(t[foreign_key].eq(m[:id]).
22
51
  and(t[:locale].eq(Mobility.locale))).join_sources)
23
52
  end
53
+ end
54
+
55
+ def define_query_methods(association_name, translation_class, **_)
56
+ attributes_extractor = @attributes_extractor
24
57
 
25
58
  # Note that Mobility will try to use inner/outer joins appropriate to the query,
26
59
  # so for example:
@@ -59,32 +92,6 @@ module Mobility
59
92
  super(opts, *rest)
60
93
  end
61
94
  end
62
-
63
- attributes.each do |attribute|
64
- define_method :"find_by_#{attribute}" do |value|
65
- find_by(attribute.to_sym => value)
66
- end
67
- end
68
- end
69
-
70
- def extended(relation)
71
- super
72
- association_name = @association_name
73
- attributes_extractor = @attributes_extractor
74
- translation_class = @translation_class
75
-
76
- mod = Module.new do
77
- define_method :not do |opts, *rest|
78
- if i18n_keys = attributes_extractor.call(opts)
79
- opts = opts.with_indifferent_access
80
- i18n_keys.each { |attr| opts["#{translation_class.table_name}.#{attr}"] = opts.delete(attr) }
81
- super(opts, *rest).send("join_#{association_name}")
82
- else
83
- super(opts, *rest)
84
- end
85
- end
86
- end
87
- relation.model.mobility_where_chain.prepend(mod)
88
95
  end
89
96
  end
90
97
  end
@@ -13,9 +13,14 @@ defaults to an instance of +I18n::Locale::Fallbacks+, but can be configured
13
13
  If a hash is passed to the +fallbacks+ option, a new fallbacks instance will be
14
14
  created for the model with the hash defining additional fallbacks.
15
15
 
16
- In addition, fallbacks can be disabled when reading by passing `fallbacks:
17
- false` to the reader method. This can be useful to determine the actual value
18
- of the translated attribute, including a possible +nil+ value.
16
+ In addition, fallbacks can be disabled when reading by passing <tt>fallback:
17
+ false</tt> to the reader method. This can be useful to determine the actual
18
+ value of the translated attribute, including a possible +nil+ value. You can
19
+ also pass a locale or array of locales to the +fallback+ option to use that
20
+ locale or locales that read, e.g. <tt>fallback: :fr</tt> would fetch the French
21
+ translation if the value in the current locale was +nil+, whereas <tt>fallback:
22
+ [:fr, :es]</tt> would try French, then Spanish if the value in the current
23
+ locale was +nil+.
19
24
 
20
25
  @see https://github.com/svenfuchs/i18n/wiki/Fallbacks I18n Fallbacks
21
26
 
@@ -52,28 +57,37 @@ of the translated attribute, including a possible +nil+ value.
52
57
  post.title
53
58
  #=> "bar"
54
59
 
55
- @example Disabling fallbacks when reading value
60
+ @example Passing fallback option when reading value
56
61
  class Post
57
62
  translates :title, fallbacks: true
58
63
  end
59
64
 
60
65
  I18n.default_locale = :en
61
66
  Mobility.locale = :en
62
- post = Post.new(title: "foo")
67
+ post = Post.new(title: "Mobility")
68
+ Mobility.with_locale(:fr) { post.title = "Mobilité" }
63
69
 
64
70
  Mobility.locale = :ja
65
71
  post.title
66
- #=> "foo"
67
- post.title(fallbacks: false)
72
+ #=> "Mobility"
73
+ post.title(fallback: false)
68
74
  #=> nil
75
+ post.title(fallback: :fr)
76
+ #=> "Mobilité"
69
77
  =end
70
78
  module Fallbacks
71
79
  # @!group Backend Accessors
72
80
  # @!macro backend_reader
73
- # @option options [Boolean] fallbacks +false+ to disable fallbacks on lookup
74
- def read(locale, **options)
75
- return super if options[:fallbacks] == false
76
- fallbacks[locale].detect do |locale|
81
+ # @param [Boolean,Symbol,Array] fallback
82
+ # +false+ to disable fallbacks on lookup, or a locale or array of
83
+ # locales to set fallback(s) for this lookup.
84
+ def read(locale, fallback: nil, **_)
85
+ if !options[:fallbacks].nil?
86
+ warn "You passed an option with key 'fallbacks', which will be
87
+ ignored. Did you mean 'fallback'?"
88
+ end
89
+ return super if fallback == false
90
+ (fallback ? [locale, *fallback] : fallbacks[locale]).detect do |locale|
77
91
  value = super(locale)
78
92
  break value if value.present?
79
93
  end
@@ -41,6 +41,20 @@ class.
41
41
  module KeyValue
42
42
  include OrmDelegator
43
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
+
44
58
  # Simple cache to memoize translations as a hash so they can be fetched
45
59
  # quickly.
46
60
  class TranslationsCache
@@ -9,7 +9,7 @@ Implements the {Mobility::Backend::Column} backend for Sequel models.
9
9
  =end
10
10
  class Sequel::Column
11
11
  include Backend
12
- include Mobility::Backend::Column
12
+ include Backend::Column
13
13
 
14
14
  autoload :QueryMethods, 'mobility/backend/sequel/column/query_methods'
15
15
 
@@ -11,12 +11,13 @@ Automatically includes dirty plugin in model class when enabled.
11
11
  module Sequel::Dirty
12
12
  # @!group Backend Accessors
13
13
  # @!macro backend_writer
14
+ # @param [Hash] options
14
15
  def write(locale, value, **options)
15
16
  locale_accessor = Mobility.normalize_locale_accessor(attribute, locale).to_sym
16
17
  if model.column_changes.has_key?(locale_accessor) && model.initial_values[locale_accessor] == value
17
18
  super
18
19
  [model.changed_columns, model.initial_values].each { |h| h.delete(locale_accessor) }
19
- elsif read(locale, options.merge(fallbacks: false)) != value
20
+ elsif read(locale, options.merge(fallback: false)) != value
20
21
  model.will_change_column(locale_accessor)
21
22
  super
22
23
  end
@@ -11,6 +11,7 @@ Implements the {Mobility::Backend::KeyValue} backend for Sequel models.
11
11
  =end
12
12
  class Sequel::KeyValue
13
13
  include Backend
14
+ include Backend::KeyValue
14
15
 
15
16
  autoload :QueryMethods, 'mobility/backend/sequel/key_value/query_methods'
16
17
 
@@ -42,20 +43,16 @@ Implements the {Mobility::Backend::KeyValue} backend for Sequel models.
42
43
  # @!endgroup
43
44
 
44
45
  # @!group Backend Configuration
45
- # @option options [Symbol] type (:text) Column type to use
46
+ # @option options [Symbol,String] type (:text) Column type to use
46
47
  # @option options [Symbol] associaiton_name (:mobility_text_translations) Name of association method
47
48
  # @option options [Symbol] class_name ({Mobility::Sequel::TextTranslation}) Translation class
48
49
  # @raise [CacheRequired] if cache is disabled
49
50
  # @raise [ArgumentError] if type is not either :text or :string
50
51
  def self.configure!(options)
52
+ super
51
53
  raise CacheRequired, "Cache required for Sequel::KeyValue backend" if options[:cache] == false
52
- options[:type] ||= :text
53
- case type = options[:type].to_sym
54
- when :text, :string
55
- options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translation")
56
- else
57
- raise ArgumentError, "type must be one of: [text, string]"
58
- end
54
+ type = options[:type]
55
+ options[:class_name] ||= Mobility::Sequel.const_get("#{type.capitalize}Translation")
59
56
  options[:class_name] = options[:class_name].constantize if options[:class_name].is_a?(String)
60
57
  options[:association_name] ||= options[:class_name].table_name.to_sym
61
58
  %i[type association_name].each { |key| options[key] = options[key].to_sym }
@@ -1,16 +1,27 @@
1
1
  module Mobility
2
2
  module Backend
3
3
  class Sequel::KeyValue::QueryMethods < Sequel::QueryMethods
4
- def initialize(attributes, **options)
4
+ def initialize(attributes, association_name: nil, class_name: nil, **_)
5
5
  super
6
- attributes_extractor = @attributes_extractor
7
- association_name, translations_class = options[:association_name], options[:class_name]
8
6
 
7
+ define_join_method(association_name, class_name)
8
+ define_query_methods(association_name)
9
+
10
+ attributes.each do |attribute|
11
+ define_method :"first_by_#{attribute}" do |value|
12
+ where(attribute => value).select_all(model.table_name).first
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def define_join_method(association_name, translation_class)
9
20
  define_method :"join_#{association_name}" do |*attributes, **options|
10
21
  attributes.inject(self) do |relation, attribute|
11
22
  join_type = options[:outer_join] ? :left_outer : :inner
12
23
  relation.join_table(join_type,
13
- translations_class.table_name,
24
+ translation_class.table_name,
14
25
  {
15
26
  key: attribute.to_s,
16
27
  locale: Mobility.locale.to_s,
@@ -20,9 +31,14 @@ module Mobility
20
31
  table_alias: "#{attribute}_#{association_name}")
21
32
  end
22
33
  end
34
+ end
23
35
 
36
+ def define_query_methods(association_name)
24
37
  # TODO: find a better way to do this that doesn't involve overriding
25
38
  # a private method...
39
+ #
40
+ attributes_extractor = @attributes_extractor
41
+
26
42
  define_method :_filter_or_exclude do |invert, clause, *cond, &block|
27
43
  if i18n_keys = attributes_extractor.call(cond.first)
28
44
  cond = cond.first.dup
@@ -37,11 +53,6 @@ module Mobility
37
53
  end
38
54
  private :_filter_or_exclude
39
55
 
40
- attributes.each do |attribute|
41
- define_method :"first_by_#{attribute}" do |value|
42
- where(attribute => value).select_all(model.table_name).first
43
- end
44
- end
45
56
  end
46
57
  end
47
58
  end
@@ -60,8 +60,8 @@ Sequel serialization plugin.
60
60
  plugin :serialization
61
61
  plugin :serialization_modification_detection
62
62
 
63
- attributes.each do |_attribute|
64
- attribute = _attribute.to_sym
63
+ attributes.each do |attribute_|
64
+ attribute = attribute_.to_sym
65
65
  self.serialization_map[attribute] = Serialized.serializer_for(format)
66
66
  self.deserialization_map[attribute] = Serialized.deserializer_for(format)
67
67
  end
@@ -87,13 +87,13 @@ Sequel serialization plugin.
87
87
  # Returns deserialized column value
88
88
  # @return [Hash]
89
89
  def translations
90
- _attribute = attribute.to_sym
91
- if model.deserialized_values.has_key?(_attribute)
92
- model.deserialized_values[_attribute]
90
+ attribute_ = attribute.to_sym
91
+ if model.deserialized_values.has_key?(attribute_)
92
+ model.deserialized_values[attribute_]
93
93
  elsif model.frozen?
94
- deserialize_value(_attribute, serialized_value)
94
+ deserialize_value(attribute_, serialized_value)
95
95
  else
96
- model.deserialized_values[_attribute] = deserialize_value(_attribute, serialized_value)
96
+ model.deserialized_values[attribute_] = deserialize_value(attribute_, serialized_value)
97
97
  end
98
98
  end
99
99
 
@@ -1,16 +1,21 @@
1
1
  module Mobility
2
2
  module Backend
3
3
  class Sequel::Table::QueryMethods < Sequel::QueryMethods
4
- def initialize(attributes, **options)
4
+ def initialize(attributes, association_name: nil, model_class: nil, subclass_name: nil, **options)
5
5
  super
6
- association_name = options[:association_name]
7
- @association_name = association_name
8
- foreign_key = options[:foreign_key]
9
- attributes_extractor = @attributes_extractor
10
- translation_class = options[:model_class].const_get(options[:subclass_name])
11
- @translation_class = translation_class
12
- table_name = options[:table_name]
6
+ translation_class = model_class.const_get(subclass_name)
7
+
8
+ define_join_method(association_name, translation_class, **options)
9
+ define_query_methods(association_name, translation_class, **options)
10
+
11
+ attributes.each do |attribute|
12
+ define_method :"first_by_#{attribute}" do |value|
13
+ where(attribute => value).select_all(model.table_name).first
14
+ end
15
+ end
16
+ end
13
17
 
18
+ def define_join_method(association_name, translation_class, table_name: nil, foreign_key: nil, **_)
14
19
  define_method :"join_#{association_name}" do |**options|
15
20
  return self if (@__mobility_table_joined || []).include?(table_name)
16
21
  (@__mobility_table_joined ||= []) << table_name
@@ -22,6 +27,10 @@ module Mobility
22
27
  foreign_key => ::Sequel[model.table_name][:id]
23
28
  })
24
29
  end
30
+ end
31
+
32
+ def define_query_methods(association_name, translation_class, **_)
33
+ attributes_extractor = @attributes_extractor
25
34
 
26
35
  # See note in AR Table QueryMethods class about limitations of
27
36
  # query methods on translated attributes when searching on nil values.
@@ -36,12 +45,6 @@ module Mobility
36
45
  super(invert, clause, *cond, &block)
37
46
  end
38
47
  end
39
-
40
- attributes.each do |attribute|
41
- define_method :"first_by_#{attribute}" do |value|
42
- where(attribute => value).select_all(model.table_name).first
43
- end
44
- end
45
48
  end
46
49
  end
47
50
  end
@@ -0,0 +1,2 @@
1
+ require "rails/generators"
2
+ require_relative "../generators/rails/mobility/install_generator"
@@ -1,3 +1,3 @@
1
1
  module Mobility
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
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.6
4
+ version: 0.1.7
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-15 00:00:00.000000000 Z
11
+ date: 2017-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: request_store
@@ -137,9 +137,9 @@ files:
137
137
  - LICENSE.txt
138
138
  - README.md
139
139
  - Rakefile
140
- - lib/generators/mobility/install_generator.rb
141
- - lib/generators/mobility/templates/create_string_translations.rb
142
- - lib/generators/mobility/templates/create_text_translations.rb
140
+ - lib/generators/rails/mobility/install_generator.rb
141
+ - lib/generators/rails/mobility/templates/create_string_translations.rb
142
+ - lib/generators/rails/mobility/templates/create_text_translations.rb
143
143
  - lib/mobility.rb
144
144
  - lib/mobility/active_model.rb
145
145
  - lib/mobility/active_model/attribute_methods.rb
@@ -203,6 +203,7 @@ files:
203
203
  - lib/mobility/fallthrough_accessors.rb
204
204
  - lib/mobility/instance_methods.rb
205
205
  - lib/mobility/orm.rb
206
+ - lib/mobility/rails.rb
206
207
  - lib/mobility/sequel.rb
207
208
  - lib/mobility/sequel/backend_resetter.rb
208
209
  - lib/mobility/sequel/column_changes.rb