mobility 0.1.6 → 0.1.7

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: 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