I'm building an API with Rails 3, using devise to handle some of the authentication.
I commonly use the respond_with method to return xml/json for various resources.
Eg GET /groups.xml will route to
def index
  respond_with Group.all
end
This works fine across my site for various resources, and returns nicely formatted json or xml containing all the attributes of each group.
However, when I call GET /users.xml, it only responds with a limited subset of the each user's attributes. It turns out that only attributes defined in attr_assessible will be returned here - I suspect this is a "feature" of devise, because it's not the case for any other model.
Can an开发者_运维问答yone enlighten me?
Edit: This is sort of fixed in Devise 1.4.2. See below for details
Your suspicion is correct. The Devise Authenticatable module overrides #to_xml and #to_json to first check if the class responds to the #accessible_attributes method, and if it does then output is restricted to only those attributes returned by #accessible_attributes. The code from authenticatable.rb is here:
  %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
You'll notice that this code merges the result of #accessible_attributes into any passed-in options. As such, you can specify an :only option, such as:
.to_xml(:only => [:field, :field, :field])
This will override the Devise-imposed restriction and produce xml output that includes only the fields you specify. You will need to include every field you want exposed, since once you use :only you'll trump the normal operation.
I don't think you'll be able to continue to use the respond_with shortcut in your controller in this case, because you'll need to specify the xml output directly. You'll probably have to fall back to an old-school respond_to block:
respond_to do |format|
  format.xml { render :xml => @users.to_xml(:only => [:field, :field, :field]) }
  format.html
end
As you already discovered, you could also just add the additional fields you want exposed via attr_accessible in the model class. However, this will have the added side-effect of making these fields mass-assignable and you may not necessarily want that in this situation.
Older versions ( < 1.4.2) of Devise performed a monkeypatch on the to_json and to_xml methods, overwriting the :only => [] option with the attributes defined in attr_accessible. Annoying.
This has now been changed, so that serializable_hash is overwritten instead, and any :only => [:attribute] options set in to_json or to_xml are persisted.
In my case, I ended up monkeypatching to_json myself, and adding a method api_accessible to all ActiveRecord models.
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
This means that you can now define a list of attributes (and methods!) that will be exposed by default when calling to_json. Respond_with also uses to_json, so it works well for APIs.
Eg, 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
 
         
                                         
                                         
                                         
                                        ![Interactive visualization of a graph in python [closed]](https://www.devze.com/res/2023/04-10/09/92d32fe8c0d22fb96bd6f6e8b7d1f457.gif) 
                                         
                                         
                                         
                                         加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论