Reputation: 411
I want to filter out specific fields from ActiveRecord/ActiveModel classes when outputting JSON.
The most straightforward way to do this is just overriding as_json
, perhaps like so:
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => filter_attributes}))
end
def filter_attributes
[:password_digest, :some_attribute]
end
This works, but it's a little verbose and lends itself to not being DRY pretty fast. I thought it would be nice to just declare the filtered properties with a magical class method. For example:
class User < ActiveRecord::Base
include FilterJson
has_secure_password
filter_json :password_digest
#...
end
module FilterJson
extend ActiveSupport::Concern
module ClassMethods
def filter_json (*attributes)
(@filter_attributes ||= Set.new).merge(attributes.map(&:to_s))
end
def filter_attributes
@filter_attributes
end
end
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => self.class.filter_attributes.to_a}))
end
end
The problem with this is getting it to deal with inheritance properly. Let's say I subclass User:
class SecretiveUser < User
filter_json :some_attribute, :another_attribute
#...
end
Logically, it makes sense to filter out :some_attribute
, :another_attribute
, and also :password_digest
.
However, this will only filter the attributes declared on the class. To the desired end, I tried to call super
within filter_attributes
, but that failed. I came up with this, and it's a hack.
def filter_attributes
if superclass.respond_to?(:filter_attributes)
superclass.filter_attributes + @filter_attributes
else
@filter_attributes
end
end
This is obviously brittle and not idiomatic, but there's the "what" that I'm trying to accomplish. Can anyone think of a way to do it more correctly (and hopefully more elegantly)? Thanks!
Upvotes: 3
Views: 1939
Reputation: 35370
I think it is a safer solution to white-list attributes than to black-list them. This will prevent unwanted future attributes added to User
or SomeUser
from making it into your JSON response because you forgot to add said attributes to filter_json
.
You seem to be looking for a solution to your specific inheritance issue. I'm still going to point out active_model_serializers, as I feel it is a saner way to manage serialization.
class UserSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name
end
class SecretUserSerializer < UserSerializer
attributes :secret_attribute, :another_attribute
end
Given some SecretUser
s
you can do
SecretUserSerializer.new(s).as_json
and you'll get :id
, :first_name
, :last_name
, :secret_attribute
, and :another_attribute
. The inheritance works as expected.
Upvotes: 4