There can be a business or security requirement that certain fields on a model should never be exposed in an API. One approach to this is to tell the developers not to ever put the field in the API output, but I prefer to protect them at the model level because that's where the requirement actually is. The goal of this is to have a simple way to specify fields that should be prevented from being exposed. This is one approach and I would love feedback both on the implementation and on the approach. Ideas and criticism are very welcome. ### Implementation - Extend ActiveRecord::Base # Provide a system for specifying private attributes that shouldn't be exposed module ActiveRecord class Base class << self # Instead of setting the instance variable when this is called, we only check # if it's defined. It's only set when attr_private is called. This allows us # to know if the model has ever set any private attributes or not def private_attributes instance_variable_defined?('@private_attributes') ? @private_attributes : [] end def is_private_attribute?(name) private_attributes.include?(name.to_sym) end protected # Set the @private_attributes variable with an array of attribute symbols def attr_private(*args) (@private_attributes ||= []).push(*args.collect { |a| a.to_sym }).uniq! end # Specify public attributes, which conversely privatizes the other attributes # If private attributes have previously been declared, attr_public can override # the setting. If it is the first time, make all the attributes private unless # they are in the args. def attr_public(*args) if instance_variable_defined?('@private_attributes') && !@private_attributes.empty? @private_attributes.delete_if { |n| args.include?(n.to_sym) } else attr_private(*attribute_names.reject { |n| args.include?(n.to_sym) }) end end end # Run the to_xml options through a filter def to_xml(options={}) super(secure_private_options(options)) end # Run the serializable_hash options through a filter def serializable_hash(options={}) super(secure_private_options(options)) end protected # Filter the options to make sure private attributes aren't included def secure_private_options(options={}) (options[:except] ||= []).push(*self.class.private_attributes) options[:only].delete_if { |n| self.class.is_private_attribute?(n) } if options.has_key?(:only) options[:methods].delete_if { |n| self.class.is_private_attribute?(n) } if options.has_key?(:methods) options end end end ### Example of use in a third party library - RABL # Modify Rabl's builder to check if a method is private before exposing it module Rabl class Builder protected # Don't output the def attribute(name, options={}) unless @_object.class.respond_to?(:is_private_attribute?) && @_object.class.is_private_attribute?(name) @_result[options[:as] || name] = data_object_attribute(name) if @_object && @_object.respond_to?(name) end end end end ## Example of general use on a model instance # An example of usage. # Fields: category_id, name, description, supplier # # Let's assume we never want supplier to be exposed. # class Product < ActiveRecord::Base attr_private :supplier end Product.first.to_json #=> { "category_id": 1, "name": "Bucky Balls", "description": "Awesome Magnets" } Product.is_private_attribute?(:supplier) #=> true Product.is_private_attribute?(:category_id) #=> false Product.private_attributes #=> [ :supplier ] # You can also use attr_public, which takes the attributes_names and makes all of them private except the items listed in the attr_public arguments ## Additional notes * You could support role based access control by turning the underlying instance variable into a hash keyed to the role instead of a single array. * The attributes could still be exposed by having them called directly, which allows for usage in cases where you actually WANT them exposed (like perhaps an admin panel). ## Questions 1. As you see in the `secure_private_options` method, I check the passed in methods to make sure they aren't on the blacklist. This is fine for using attr_private to declare methods that shouldn't called, but I can't figure out how to set it so that `attr_public` can automatically include all getter attribute methods as well as just the attributes. For example, if I have a custom method called `full_name` that I want to protect, it would be nice if `attr_public` would automatically protect it. 2. Is there something in `ActiveRecord` or Rails that provides for this functionality already? 3. What use cases am I not thinking about? A gist of this code can be found [here](https://gist.github.com/3806392).