diff --git a/lib/mongoid/contextual/mongo.rb b/lib/mongoid/contextual/mongo.rb index 6d78f4084d..14c20bb449 100644 --- a/lib/mongoid/contextual/mongo.rb +++ b/lib/mongoid/contextual/mongo.rb @@ -338,7 +338,7 @@ def pluck(*fields) normalized_select = fields.inject({}) do |hash, f| db_fn = klass.database_field_name(f) normalized_field_names.push(db_fn) - hash[klass.cleanse_localized_field_names(f)] = true + hash[klass.database_field_name(klass.cleanse_localized_field_names(f))] = true hash end @@ -942,7 +942,7 @@ def extract_value(attrs, field_name) # _translations field. if obj.nil? & tr = meth.match(/(.*)_translations\z/)&.captures&.first is_translation = true - meth = tr + meth = klass.database_field_name(tr) end # 1. If curr is an array fetch from all elements in the array. diff --git a/spec/mongoid/association/referenced/has_many/enumerable_spec.rb b/spec/mongoid/association/referenced/has_many/enumerable_spec.rb index 5d49883d30..639fb00168 100644 --- a/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +++ b/spec/mongoid/association/referenced/has_many/enumerable_spec.rb @@ -2230,4 +2230,317 @@ end end end + + describe '#pluck' do + let!(:parent) do + Company.create! + end + + context 'when the field is aliased' do + let!(:expensive) do + parent.products.create!(price: 100000) + end + + let!(:cheap) do + parent.products.create!(price: 1) + end + + context 'when using alias_attribute' do + + let(:plucked) do + parent.products.pluck(:price) + end + + it 'uses the aliases' do + expect(plucked).to eq([ 100000, 1 ]) + end + end + end + + context 'when plucking a localized field' do + with_default_i18n_configs + + before do + I18n.locale = :en + p = parent.products.create!(name: 'english-text') + I18n.locale = :de + p.name = 'deutsch-text' + p.save! + end + + context 'when plucking the entire field' do + let(:plucked) do + parent.products.all.pluck(:name) + end + + let(:plucked_translations) do + parent.products.all.pluck(:name_translations) + end + + let(:plucked_translations_both) do + parent.products.all.pluck(:name_translations, :name) + end + + it 'returns the demongoized translations' do + expect(plucked.first).to eq('deutsch-text') + end + + it 'returns the full translations hash to _translations' do + expect(plucked_translations.first).to eq({'de'=>'deutsch-text', 'en'=>'english-text'}) + end + + it 'returns both' do + expect(plucked_translations_both.first).to eq([{'de'=>'deutsch-text', 'en'=>'english-text'}, 'deutsch-text']) + end + end + + context 'when plucking a specific locale' do + + let(:plucked) do + parent.products.all.pluck(:'name.de') + end + + it 'returns the specific translations' do + expect(plucked.first).to eq('deutsch-text') + end + end + + context 'when plucking a specific locale from _translations field' do + + let(:plucked) do + parent.products.all.pluck(:'name_translations.de') + end + + it 'returns the specific translations' do + expect(plucked.first).to eq('deutsch-text') + end + end + + context 'when fallbacks are enabled with a locale list' do + require_fallbacks + + before do + I18n.fallbacks[:he] = [ :en ] + end + + let(:plucked) do + parent.products.all.pluck(:name).first + end + + it 'correctly uses the fallback' do + I18n.locale = :en + parent.products.create!(name: 'english-text') + I18n.locale = :he + expect(plucked).to eq 'english-text' + end + end + + context 'when the localized field is aliased' do + before do + I18n.locale = :en + p = parent.products.create!(name: 'ACME Rocket Skates', tagline: 'english-text') + I18n.locale = :de + p.tagline = 'deutsch-text' + p.save! + end + + context 'when plucking the entire field' do + let(:plucked) do + parent.products.all.pluck(:tagline) + end + + let(:plucked_unaliased) do + parent.products.all.pluck(:tl) + end + + let(:plucked_translations) do + parent.products.all.pluck(:tagline_translations) + end + + let(:plucked_translations_both) do + parent.products.all.pluck(:tagline_translations, :tagline) + end + + it 'returns the demongoized translations' do + expect(plucked.first).to eq('deutsch-text') + end + + it 'returns the demongoized translations when unaliased' do + expect(plucked_unaliased.first).to eq('deutsch-text') + end + + it 'returns the full translations hash to _translations' do + expect(plucked_translations.first).to eq({ 'de' => 'deutsch-text', 'en' => 'english-text' }) + end + + it 'returns both' do + expect(plucked_translations_both.first).to eq([{ 'de' => 'deutsch-text', 'en' => 'english-text' }, 'deutsch-text']) + end + end + + context 'when plucking a specific locale' do + + let(:plucked) do + parent.products.all.pluck(:'tagline.de') + end + + it 'returns the specific translations' do + expect(plucked.first).to eq('deutsch-text') + end + end + + context 'when plucking a specific locale from _translations field' do + + let(:plucked) do + parent.products.all.pluck(:'tagline_translations.de') + end + + it 'returns the specific translations' do + expect(plucked.first).to eq('deutsch-text') + end + end + + context 'when fallbacks are enabled with a locale list' do + require_fallbacks + + before do + I18n.fallbacks[:he] = [:en] + end + + let(:plucked) do + parent.products.all.pluck(:tagline).first + end + + it 'correctly uses the fallback' do + I18n.locale = :en + parent.products.create!(tagline: 'english-text') + I18n.locale = :he + expect(plucked).to eq 'english-text' + end + end + end + + context 'when the localized field is embedded' do + with_default_i18n_configs + + before do + s = Seo.new + I18n.locale = :en + s.name = 'english-text' + I18n.locale = :de + s.name = 'deutsch-text' + + parent.products.create!(name: 'ACME Tunnel Paint', seo: s) + end + + let(:plucked) do + parent.products.pluck('seo.name').first + end + + let(:plucked_translations) do + parent.products.pluck('seo.name_translations').first + end + + let(:plucked_translations_field) do + parent.products.pluck('seo.name_translations.en').first + end + + it 'returns the translation for the current locale' do + expect(plucked).to eq('deutsch-text') + end + + it 'returns the full _translation hash' do + expect(plucked_translations).to eq({ 'en' => 'english-text', 'de' => 'deutsch-text' }) + end + + it 'returns the translation for the requested locale' do + expect(plucked_translations_field).to eq('english-text') + end + end + end + + context 'when the localized field is embedded and aliased' do + with_default_i18n_configs + + before do + s = Seo.new + I18n.locale = :en + s.description = 'english-text' + I18n.locale = :de + s.description = 'deutsch-text' + + parent.products.create!(name: 'ACME Tunnel Paint', seo: s) + end + + let(:plucked) do + parent.products.pluck('seo.description').first + end + + let(:plucked_unaliased) do + parent.products.pluck('seo.desc').first + end + + let(:plucked_translations) do + parent.products.pluck('seo.description_translations').first + end + + let(:plucked_translations_field) do + parent.products.pluck('seo.description_translations.en').first + end + + it 'returns the translation for the current locale' do + I18n.with_locale(:ja) do + expect(plucked).to eq('京都') + end + end + + it 'returns the translation for the current locale when unaliased' do + I18n.with_locale(:ja) do + expect(plucked_unaliased).to eq('京都') + end + end + + it 'returns the full _translation hash' do + expect(plucked_translations).to eq({ 'en' => 'Kyoto', 'ja' => '京都' }) + end + + it 'returns the translation for the requested locale' do + expect(plucked_translations_field).to eq('Kyoto') + end + end + + # TODO: Finish these + # context 'when plucking an embedded field' do + # let(:label) { Label.new(sales: '1E2') } + # let!(:band) { Band.create!(label: label) } + # + # let(:plucked) { Band.where(_id: band.id).pluck('label.sales') } + # + # it 'demongoizes the field' do + # expect(plucked).to eq([ BigDecimal('1E2') ]) + # end + # end + # + # context 'when plucking an embeds_many field' do + # let(:label) { Label.new(sales: '1E2') } + # let!(:band) { Band.create!(labels: [label]) } + # + # let(:plucked) { Band.where(_id: band.id).pluck('labels.sales') } + # + # it 'demongoizes the field' do + # expect(plucked.first).to eq([ BigDecimal('1E2') ]) + # end + # end + # + # context 'when plucking a nonexistent embedded field' do + # let(:label) { Label.new(sales: '1E2') } + # let!(:band) { Band.create!(label: label) } + # + # let(:plucked) { Band.where(_id: band.id).pluck('label.qwerty') } + # + # it 'returns nil' do + # expect(plucked.first).to eq(nil) + # end + # end + end end diff --git a/spec/mongoid/criteria_spec.rb b/spec/mongoid/criteria_spec.rb index 24832248bc..c71be3bd1b 100644 --- a/spec/mongoid/criteria_spec.rb +++ b/spec/mongoid/criteria_spec.rb @@ -1957,6 +1957,91 @@ end end + context 'when the localized field is aliased' do + before do + I18n.locale = :en + p = Product.create!(name: 'ACME Rocket Skates', tagline: 'english-text') + I18n.locale = :de + p.tagline = 'deutsch-text' + p.save! + end + + context 'when plucking the entire field' do + let(:plucked) do + Product.all.pluck(:tagline) + end + + let(:plucked_unaliased) do + Product.all.pluck(:tl) + end + + let(:plucked_translations) do + Product.all.pluck(:tagline_translations) + end + + let(:plucked_translations_both) do + Product.all.pluck(:tagline_translations, :tagline) + end + + it 'returns the demongoized translations' do + expect(plucked.first).to eq('deutsch-text') + end + + it 'returns the demongoized translations when unaliased' do + expect(plucked_unaliased.first).to eq('deutsch-text') + end + + it 'returns the full translations hash to _translations' do + expect(plucked_translations.first).to eq({ 'de' => 'deutsch-text', 'en' => 'english-text' }) + end + + it 'returns both' do + expect(plucked_translations_both.first).to eq([{ 'de' => 'deutsch-text', 'en' => 'english-text' }, 'deutsch-text']) + end + end + + context 'when plucking a specific locale' do + + let(:plucked) do + Product.all.pluck(:'tagline.de') + end + + it 'returns the specific translations' do + expect(plucked.first).to eq('deutsch-text') + end + end + + context 'when plucking a specific locale from _translations field' do + + let(:plucked) do + Product.all.pluck(:'tagline_translations.de') + end + + it 'returns the specific translations' do + expect(plucked.first).to eq('deutsch-text') + end + end + + context 'when fallbacks are enabled with a locale list' do + require_fallbacks + + before do + I18n.fallbacks[:he] = [:en] + end + + let(:plucked) do + Product.all.pluck(:tagline).first + end + + it 'correctly uses the fallback' do + I18n.locale = :en + Product.create!(tagline: 'english-text') + I18n.locale = :he + expect(plucked).to eq 'english-text' + end + end + end + context "when the localized field is embedded" do with_default_i18n_configs @@ -1996,6 +2081,56 @@ end end + context 'when the localized field is embedded and aliased' do + with_default_i18n_configs + + before do + p = Passport.new + I18n.locale = :en + p.birthplace = 'Kyoto' + I18n.locale = :ja + p.birthplace = '京都' + + Person.create!(passport: p, employer_id: 12345) + end + + let(:plucked) do + Person.where(employer_id: 12345).pluck('pass.birthplace').first + end + + let(:plucked_unaliased) do + Person.where(employer_id: 12345).pluck('pass.bp').first + end + + let(:plucked_translations) do + Person.where(employer_id: 12345).pluck('pass.birthplace_translations').first + end + + let(:plucked_translations_field) do + Person.where(employer_id: 12345).pluck('pass.birthplace_translations.en').first + end + + it 'returns the translation for the current locale' do + I18n.with_locale(:ja) do + expect(plucked).to eq('京都') + end + end + + it 'returns the translation for the current locale when unaliased' do + I18n.with_locale(:ja) do + expect(plucked_unaliased).to eq('京都') + end + end + + it 'returns the full _translation hash' do + expect(plucked_translations).to eq({ 'en' => 'Kyoto', 'ja' => '京都' }) + end + + it 'returns the translation for the requested locale' do + expect(plucked_translations_field).to eq('Kyoto') + end + end + context 'when plucking a field to be demongoized' do let(:plucked) do diff --git a/spec/support/models/company.rb b/spec/support/models/company.rb index 5838e28f07..6d08f25b9a 100644 --- a/spec/support/models/company.rb +++ b/spec/support/models/company.rb @@ -5,4 +5,6 @@ class Company include Mongoid::Document embeds_many :staffs + + has_many :products end diff --git a/spec/support/models/passport.rb b/spec/support/models/passport.rb index f1c11ae6d9..78c88f4995 100644 --- a/spec/support/models/passport.rb +++ b/spec/support/models/passport.rb @@ -8,6 +8,7 @@ class Passport field :country, type: String field :exp, as: :expiration_date, type: Date field :name, localize: true + field :bp, as: :birthplace, localize: true field :localized_translations, localize: true embedded_in :person, autobuild: true diff --git a/spec/support/models/product.rb b/spec/support/models/product.rb index 9d9c96576b..30f2808009 100644 --- a/spec/support/models/product.rb +++ b/spec/support/models/product.rb @@ -18,4 +18,6 @@ class Product validates :website, format: { with: URI.regexp, allow_blank: true } embeds_one :seo, as: :seo_tags, cascade_callbacks: true, autobuild: true + + belongs_to :company end diff --git a/spec/support/models/seo.rb b/spec/support/models/seo.rb index 51ba402cdc..e6d8b23540 100644 --- a/spec/support/models/seo.rb +++ b/spec/support/models/seo.rb @@ -5,6 +5,8 @@ class Seo include Mongoid::Document include Mongoid::Timestamps field :title, type: String + field :name, type: String, localize: true + field :desc, as: :description, type: String, localize: true embedded_in :seo_tags, polymorphic: true end