Respond_with с использованием Devise и attr_accessible в Rails 3

Я создаю API с Rails 3, используя devise для обработки части аутентификации.

Обычно я использую метод response_with для возврата xml/json для различных ресурсов.

Например, GET /groups.xml направит к

def index
  respond_with Group.all
end

Это отлично работает на моем сайте для различных ресурсов и возвращает красиво отформатированные json или xml, содержащие все атрибуты каждой группы.

Однако, когда я вызываю GET /users.xml, он отвечает только ограниченным подмножеством атрибутов каждого пользователя. Оказывается, здесь будут возвращены только атрибуты, определенные в attr_assessible - я подозреваю, что это "особенность" devise, потому что это не относится ни к какой другой модели.

Кто-нибудь может просветить меня?

Изменить: это исправлено в Devise 1.4.2. Подробнее см. ниже


person tomblomfield    schedule 25.06.2011    source источник


Ответы (2)


Ваше подозрение правильно. Модуль Devise Authenticatable переопределяет #to_xml и #to_json, чтобы сначала проверить, отвечает ли класс на метод #accessible_attributes, и если да, то вывести ограничен только теми атрибутами, которые возвращаются #accessible_attributes. Код из authenticationtable.rb находится здесь:

  %w(to_xml to_json).each do |method|
    class_eval <<-RUBY, __FILE__, __LINE__
      def #{method}(options={})
        if self.class.respond_to?(:accessible_attributes)
          options = { :only => self.class.accessible_attributes.to_a }.merge(options || {})
          super(options)
        else
          super
        end
      end
    RUBY
  end

Вы заметите, что этот код объединяет результат #accessible_attributes с любыми переданными параметрами. Таким образом, вы можете указать параметр :only, например:

.to_xml(:only => [:field, :field, :field])

Это переопределит ограничение, наложенное Devise, и создаст вывод xml, который включает только указанные вами поля. Вам нужно будет включить каждое поле, которое вы хотите открыть, поскольку, как только вы используете :only, вы превзойдете обычную операцию.

Я не думаю, что в этом случае вы сможете продолжать использовать ярлык respond_with в своем контроллере, потому что вам нужно будет напрямую указать вывод xml. Возможно, вам придется вернуться к блоку respond_to старой школы:

respond_to do |format|
  format.xml { render :xml => @users.to_xml(:only => [:field, :field, :field]) }
  format.html
end

Как вы уже обнаружили, вы также можете просто добавить дополнительные поля, которые вы хотите открыть, через attr_accessible в классе модели. Тем не менее, это будет иметь дополнительный побочный эффект, заключающийся в том, что эти поля станут массово назначаемыми, и вы можете не обязательно этого хотеть в этой ситуации.

person Yardboy    schedule 25.06.2011
comment
Спасибо. Это действительно очень раздражает, что они не предоставляют способ отключить это поведение. Довольно нелепо предположить, что attr_accessible — это то же самое, что сказать, что я хочу выставить эти и только эти атрибуты в своем API. - person tomblomfield; 08.07.2011

В более ранних версиях (‹ 1.4.2) Devise выполнялся обезьяний патч для методов to_json и to_xml, перезаписывая параметр :only => [] атрибутами, определенными в attr_accessible. Раздражающий.

Теперь это было изменено, так что вместо этого serializable_hash перезаписывается, а любые параметры :only => [:attribute], установленные в to_json или to_xml, сохраняются.

В моем случае я сам исправил to_json и добавил метод api_accessible ко всем моделям ActiveRecord.

class ActiveRecord::Base

  def to_json(options = {}, &block)
    if self.class.respond_to?(:api_attributes)
      super(build_serialize_options(options), &block)
    else
      super(options, &block)
    end
  end

  class << self
    attr_reader :api_attributes
    def api_accessible(*args)
      @api_attributes ||= []
      @api_attributes += args
    end
  end

  private

    def build_serialize_options(options)
      return options if self.class.api_attributes.blank?
      methods = self.class.instance_methods - self.class.attribute_names.map(&:to_sym)
      api_methods = self.class.api_attributes.select { |m| methods.include?(m) }
      api_attrs = self.class.api_attributes - api_methods
      options.merge!(only: api_attrs) if api_attrs.present?
      options.merge!(methods: api_methods) if api_methods.present?
      return options
    end

end

Это означает, что теперь вы можете определить список атрибутов (и методов!), которые будут отображаться по умолчанию при вызове to_json. Respond_with также использует to_json, поэтому он хорошо работает для API.

Например, user.rb

class User < ActiveRecord::Base

 devise :database_authenticatable, :registerable, :confirmable,
         :recoverable, :rememberable, :trackable, :validatable

  #Setup accessible (or protected) attributes for your model
  attr_accessible :email,
                  :password,
                  :password_confirmation,
                  :remember_me,
                  :first_name,
                  :last_name,


  api_accessible :id,
                 :name,
                 :created_at,
                 :first_name,
                 :last_name,
                 :some_model_method_also_works
end
person tomblomfield    schedule 08.07.2011