diff --git a/lib/mobility/plugins/fallbacks.rb b/lib/mobility/plugins/fallbacks.rb index 68523fead..a66acf7fc 100644 --- a/lib/mobility/plugins/fallbacks.rb +++ b/lib/mobility/plugins/fallbacks.rb @@ -139,15 +139,30 @@ def initialize(fallbacks_option) def define_read(fallbacks) define_method :read do |locale, fallback: true, **options| - return super(locale, options) if !fallback || options[:locale] - - locales = fallback == true ? fallbacks[locale] : [locale, *fallback] - locales.each do |fallback_locale| - value = super(fallback_locale, options) - return value if Util.present?(value) + value = super(locale, options) + return value if !fallback || options[:locale] || Util.present?(value) + + fallback_locales = + Util.array_wrap( + if fallback == true + if fallbacks.is_a?(Proc) + model.instance_exec(&fallbacks) + else + fallbacks[locale] + end + elsif fallback.is_a?(Proc) + model.instance_exec(&fallback) + else + fallback + end + ) + + fallback_locales.each do |fallback_locale| + fallback_value = super(fallback_locale.to_sym, options) + return fallback_value if Util.present?(fallback_value) end - super(locale, options) + value end end @@ -156,6 +171,8 @@ def convert_option_to_fallbacks(option) Mobility.new_fallbacks(option) elsif option == true Mobility.new_fallbacks + elsif option.is_a?(Proc) + option else Hash.new { [] } end diff --git a/lib/mobility/util.rb b/lib/mobility/util.rb index b570743a4..ca5eb9869 100644 --- a/lib/mobility/util.rb +++ b/lib/mobility/util.rb @@ -109,6 +109,17 @@ def presence(object) object if present?(object) end + # taken from https://github.com/rails/rails/blob/6-0-stable/activesupport/lib/active_support/core_ext/array/wrap.rb + def array_wrap(object) + if object.nil? + [] + elsif object.respond_to?(:to_ary) + object.to_ary || [object] + else + [object] + end + end + private # Calls caller method on object if defined, otherwise yields to block diff --git a/spec/mobility/plugins/fallbacks_spec.rb b/spec/mobility/plugins/fallbacks_spec.rb index 3fb474bf7..e195c1cfd 100644 --- a/spec/mobility/plugins/fallbacks_spec.rb +++ b/spec/mobility/plugins/fallbacks_spec.rb @@ -14,14 +14,25 @@ def read(locale, **options) "title" => { :'de-DE' => "foo", :ja => "フー", - :'pt' => "" + :'pt' => "", + :'cz' => "cz-foo" } }[attribute][locale] end end Class.new(backend_subclass).include(described_class.new(fallbacks)) end - let(:object) { (stub_const 'MobilityModel', Class.new).include(Mobility).new } + let(:object) do + (stub_const 'MobilityModel', Class.new) + .include(Mobility) + .include( + Module.new do + def fallback_locale_method + 'de-DE' + end + end + ).new + end subject { backend_class.new(object, "title") } context "fallbacks is a hash" do @@ -55,6 +66,158 @@ def read(locale, **options) expect(subject.read(:"en-US", fallback: :ja)).to eq("フー") end + it "uses array of locales passed in as value of fallback options when present" do + expect(subject.read(:"en-US", fallback: [:pl, :'cz'])).to eq("cz-foo") + end + + it "passes options to getter in fallback locale" do + expect(subject.read(:'en-US', bar: true)).to eq("bar") + end + + it "does not modify options passed in" do + options = { fallback: false } + subject.read(:"en-US", options) + expect(options).to eq({ fallback: false }) + end + end + + context "fallbacks is a proc returning a locale" do + let(:fallbacks) { proc { 'de-DE' } } + + it "returns value when value is not nil" do + expect(subject.read(:ja)).to eq("フー") + end + + it "falls through to fallback locale when value is nil" do + expect(subject.read(:"en-US")).to eq("foo") + end + + it "falls through to fallback locale when value is blank" do + expect(subject.read(:pt)).to eq("foo") + end + + it "falls through to fallback locale when fallback: true option is passed" do + expect(subject.read(:"en-US", fallback: true)).to eq("foo") + end + + it "uses locale passed in as value of fallback option when present" do + expect(subject.read(:"en-US", fallback: :ja)).to eq("フー") + end + + it "uses array of locales passed in as value of fallback options when present" do + expect(subject.read(:"en-US", fallback: [:pl, :'de-DE'])).to eq("foo") + end + + it "passes options to getter in fallback locale" do + expect(subject.read(:'en-US', bar: true)).to eq("bar") + end + + it "does not modify options passed in" do + options = { fallback: false } + subject.read(:"en-US", options) + expect(options).to eq({ fallback: false }) + end + end + + context "fallbacks is a proc returning nothing" do + let(:fallbacks) { proc {} } + + it "returns value when value is not nil" do + expect(subject.read(:ja)).to eq("フー") + end + + it "returns original value when value is nil" do + expect(subject.read(:"en-US")).to eq(nil) + end + + it "returns original value when value is blank" do + expect(subject.read(:pt)).to eq("") + end + + it "returns original value when value when fallback: true option is passed" do + expect(subject.read(:"en-US", fallback: true)).to eq(nil) + end + + it "uses locale passed in as value of fallback option when present" do + expect(subject.read(:"en-US", fallback: :ja)).to eq("フー") + end + + it "uses array of locales passed in as value of fallback options when present" do + expect(subject.read(:"en-US", fallback: [:pl, :'de-DE'])).to eq("foo") + end + + it "passes options to getter in fallback locale" do + expect(subject.read(:'en-US', bar: true)).to eq("bar") + end + + it "does not modify options passed in" do + options = { fallback: false } + subject.read(:"en-US", options) + expect(options).to eq({ fallback: false }) + end + end + + context "fallbacks is a proc returning an array" do + let(:fallbacks) { proc { [:pl, :'de-DE'] } } + + it "returns value when value is not nil" do + expect(subject.read(:ja)).to eq("フー") + end + + it "returns fallback value when value is nil" do + expect(subject.read(:"en-US")).to eq("foo") + end + + it "returns fallback value when value is blank" do + expect(subject.read(:pt)).to eq("foo") + end + + it "returns fallback value when value when fallback: true option is passed" do + expect(subject.read(:"en-US", fallback: true)).to eq("foo") + end + + it "uses locale passed in as value of fallback option when present" do + expect(subject.read(:"en-US", fallback: :ja)).to eq("フー") + end + + it "uses array of locales passed in as value of fallback options when present" do + expect(subject.read(:"en-US", fallback: [:pl, :'de-DE'])).to eq("foo") + end + + it "passes options to getter in fallback locale" do + expect(subject.read(:'en-US', bar: true)).to eq("bar") + end + + it "does not modify options passed in" do + options = { fallback: false } + subject.read(:"en-US", options) + expect(options).to eq({ fallback: false }) + end + end + + context "fallbacks is a proc calling a model method" do + let(:fallbacks) { proc { fallback_locale_method } } + + it "returns value when value is not nil" do + expect(subject.read(:ja)).to eq("フー") + end + + it "falls through to fallback locale when value is nil" do + expect(subject.read(:"en-US")).to eq("foo") + end + + it "falls through to fallback locale when value is blank" do + expect(subject.read(:pt)).to eq("foo") + end + + it "falls through to fallback locale when fallback: true option is passed" do + expect(subject.read(:"en-US", fallback: true)).to eq("foo") + end + + it "uses locale passed in as value of fallback option when present" do + expect(subject.read(:"en-US", fallback: :ja)).to eq("フー") + end + it "uses array of locales passed in as value of fallback options when present" do expect(subject.read(:"en-US", fallback: [:pl, :'de-DE'])).to eq("foo") end