简体   繁体   中英

Shortcut for plucking two attributes from an ActiveRecord object?

Is there a shorter way to do the following (

@user.employees.map { |e| { id: e.id, name: e.name } }
# => [{ id: 1, name: 'Pete' }, { id: 2, name: 'Fred' }]

User has_many employees. Both classes inherit from ActiveRecord::Base .

Two things I don't like about the above

  1. It loads employees into memory before mapping,
  2. It's verbose (subjective I guess).

Is there a better way?

UPDATE:

see @jamesharker's solution: from ActiveRecord >= 4, pluck accepts multiple arguments:

@user.employees.pluck(:id, :name)

PREVIOUS ANSWER:

for a single column in rails >= 3.2, you can do :

@user.employees.pluck(:name)

... but as you have to pluck two attributes, you can do :

@user.employees.select([:id, :name]).map {|e| {id: e.id, name: e.name} } 
# or map &:attributes, maybe

if you really need lower-level operation, just look at the source of #pluck , that uses select_all

在ActiveRecord> = 4中, pluck接受多个参数,因此该示例将变为:

@user.employees.pluck(:id, :name)

如果你被卡住了Rails 3,您可以添加这个 .pluck_all扩展: http://meltingice.net/2013/06/11/pluck-multiple-columns-rails/

Add this monkey patch which provides the multi columns pluck functionality in Rails 3.

# config/initializers/pluck_all.rb

if Rails.version[0] == '3'
  ActiveRecord::Relation.class_eval do
    def pluck(*args)
      args.map! do |column_name|
        if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
          "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
        else
          column_name.to_s
        end
      end

      relation = clone
      relation.select_values = args
      klass.connection.select_all(relation.arel).map! do |attributes|
        initialized_attributes = klass.initialize_attributes(attributes)
        attributes.map do |key, attr|
          klass.type_cast_attribute(key, initialized_attributes)
        end
      end
    end
  end
end

Rename the method from pluck to pluck_all if you dont want to override the original pluck functionality

In terms of making a rails 3 method that behaves the same as the Rails 4 pluck with multiple columns. This outputs a similar array (rather than a hashed key value collection). This should save a bit of pain if you ever come to upgrade and want to clean up the code.

module ActiveRecord
  class Relation
    def pluck_all(*args)
      args.map! do |column_name|
        if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
          "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
        else
          column_name.to_s
        end
      end

      relation = clone
      relation.select_values = args
      klass.connection.select_all(relation.arel).map! do |attributes|
        initialized_attributes = klass.initialize_attributes(attributes)
        attributes.map do |key, attribute|
          klass.type_cast_attribute(key, initialized_attributes)
        end
      end
    end
  end
end

Standing on the shoulders of giants and all

The pluck_all method worked well until I'm going to upgrade from Rails 3.2 to Rails 4.

Here is a gem pluck_all to solve this, making pluck_all method support not only in Rails 3 but in Rails 4 and Rails 5. Hope this will help those who are going to upgrade rails version.

Another option is to:

@user.employees.select(:id, :name).as_json
#=> [{"id" => 1, "name" => "Pete"}, {"id" => 2, "name" => "Fred"}]

I can imagine that you'd rather have symbolized keys. If that's the case use the #symbolize_keys method.

@user.employees.select(:id, :name).as_json.map(&:symbolize_keys)
#=> [{id: 1, name: "Pete"}, {id: 2, name: "Fred"}]

See: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM