Skip to content

Commit

Permalink
:only support when encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
Flip Sasser committed Oct 16, 2012
1 parent 5c658a0 commit b958977
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 55 deletions.
1 change: 1 addition & 0 deletions .rvmrc
@@ -0,0 +1 @@
rvm use 1.9.3@attr_encodable --create
3 changes: 3 additions & 0 deletions CHANGES.md
@@ -0,0 +1,3 @@
# 0.1.0
- Added support for `:only` like you'd expect. Now passing `:only` will exclude all other whitelisted attributes and, as always, includes inline-support for `:method` and `:include`.
- Removed accidental redis requirement from the Gemspec.
24 changes: 24 additions & 0 deletions Guardfile
@@ -0,0 +1,24 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'rspec', :version => 2 do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }

# Rails example
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
watch('config/routes.rb') { "spec/routing" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }

# Capybara request specs
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }

# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end

5 changes: 2 additions & 3 deletions Rakefile
Expand Up @@ -50,13 +50,12 @@ Jeweler::Tasks.new do |gemspec|
gemspec.summary = "An attribute black- or white-list for ActiveRecord serialization" gemspec.summary = "An attribute black- or white-list for ActiveRecord serialization"
gemspec.files = Dir["{lib}/**/*", "LICENSE", "README.md"] gemspec.files = Dir["{lib}/**/*", "LICENSE", "README.md"]
gemspec.description = %{ gemspec.description = %{
attr_encodable enables you to set up defaults for what is included or excluded when you serialize an ActiveRecord object. This is especially useful for protecting private attributes when building a public API. attr_encodable enables you to set up defaults for what is included or excluded when you serialize an ActiveRecord object. This is especially useful
for protecting private attributes when building a public API.
} }
gemspec.email = "flip@x451.com" gemspec.email = "flip@x451.com"
gemspec.homepage = "http://github.com/Plinq/attr_encodable" gemspec.homepage = "http://github.com/Plinq/attr_encodable"
gemspec.authors = ["Flip Sasser"] gemspec.authors = ["Flip Sasser"]
gemspec.test_files = Dir["{spec}/**/*"] gemspec.test_files = Dir["{spec}/**/*"]
gemspec.add_development_dependency 'rcov', '>= 0.9.9'
gemspec.add_development_dependency 'rspec', '>= 2.0' gemspec.add_development_dependency 'rspec', '>= 2.0'
gemspec.add_dependency 'redis', '>= 2.1.1'
end end
24 changes: 8 additions & 16 deletions attr_encodable.gemspec
Expand Up @@ -4,16 +4,14 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-


Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = %q{attr_encodable} s.name = "attr_encodable"
s.version = "0.0.5" s.version = "0.1.0"


s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Flip Sasser"] s.authors = ["Flip Sasser"]
s.date = %q{2011-07-14} s.date = "2012-10-16"
s.description = %q{ s.description = "\n attr_encodable enables you to set up defaults for what is included or excluded when you serialize an ActiveRecord object. This is especially useful\n for protecting private attributes when building a public API.\n "
attr_encodable enables you to set up defaults for what is included or excluded when you serialize an ActiveRecord object. This is especially useful for protecting private attributes when building a public API. s.email = "flip@x451.com"
}
s.email = %q{flip@x451.com}
s.extra_rdoc_files = [ s.extra_rdoc_files = [
"LICENSE", "LICENSE",
"README.md" "README.md"
Expand All @@ -23,28 +21,22 @@ Gem::Specification.new do |s|
"README.md", "README.md",
"lib/attr_encodable.rb" "lib/attr_encodable.rb"
] ]
s.homepage = %q{http://github.com/Plinq/attr_encodable} s.homepage = "http://github.com/Plinq/attr_encodable"
s.require_paths = ["lib"] s.require_paths = ["lib"]
s.rubygems_version = %q{1.6.2} s.rubygems_version = "1.8.24"
s.summary = %q{An attribute black- or white-list for ActiveRecord serialization} s.summary = "An attribute black- or white-list for ActiveRecord serialization"
s.test_files = ["spec/attr_encodable_spec.rb"] s.test_files = ["spec/attr_encodable_spec.rb"]


if s.respond_to? :specification_version then if s.respond_to? :specification_version then
s.specification_version = 3 s.specification_version = 3


if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_development_dependency(%q<rcov>, [">= 0.9.9"])
s.add_development_dependency(%q<rspec>, [">= 2.0"]) s.add_development_dependency(%q<rspec>, [">= 2.0"])
s.add_runtime_dependency(%q<redis>, [">= 2.1.1"])
else else
s.add_dependency(%q<rcov>, [">= 0.9.9"])
s.add_dependency(%q<rspec>, [">= 2.0"]) s.add_dependency(%q<rspec>, [">= 2.0"])
s.add_dependency(%q<redis>, [">= 2.1.1"])
end end
else else
s.add_dependency(%q<rcov>, [">= 0.9.9"])
s.add_dependency(%q<rspec>, [">= 2.0"]) s.add_dependency(%q<rspec>, [">= 2.0"])
s.add_dependency(%q<redis>, [">= 2.1.1"])
end end
end end


63 changes: 34 additions & 29 deletions lib/attr_encodable.rb
Expand Up @@ -80,48 +80,53 @@ def unencodable_attributes


module InstanceMethods module InstanceMethods
def serializable_hash(options = {}) def serializable_hash(options = {})
options ||= {}
original_except = if options[:except]
options[:except] = Array(options[:except]).map(&:to_sym)
else
options[:except] = []
end

# Convert :only to :except
if options && options[:only] if options && options[:only]
# We DON'T want to fuck with :only and :except showing up in the same call. This is a disaster. options[:except].push *self.class.default_attributes - Array(options.delete(:only).map(&:to_sym))
super end

# This is a little bit confusing. ActiveRecord's default behavior is to apply the :except arguments you pass
# in to any :include options UNLESS it's overridden on the :include option. In the event that we have some
# *default* excepts that come from Encodable, we want to ignore those and pass only whatever the original
# :except options from the user were on down to the :include guys.
inherited_except = original_except - self.class.default_attributes
case options[:include]
when Array, Symbol
# Convert includes arrays or singleton symbols into a hash with our original_except scope
includes = Array(options[:include])
options[:include] = Hash[*includes.map{|association| [association, {:except => inherited_except}]}.flatten]
else else
options ||= {} options[:include] ||= {}
original_except = if options[:except] end
options[:except] = Array(options[:except]).map(&:to_sym) # Exclude the black-list
else options[:except].push *self.class.unencodable_attributes
options[:except] = [] # Include any default :include or :methods arguments that were passed in earlier
end self.class.default_attributes.each do |attribute, as|
# This is a little bit confusing. ActiveRecord's default behavior is to apply the :except arguments you pass unless options[:except].include?(attribute)
# in to any :include options UNLESS it's overridden on the :include option. In the event that we have some
# *default* excepts that come from Encodable, we want to ignore those and pass only whatever the original
# :except options from the user were on down to the :include guys.
inherited_except = original_except - self.class.default_attributes
case options[:include]
when Array, Symbol
# Convert includes arrays or singleton symbols into a hash with our original_except scope
includes = Array(options[:include])
options[:include] = Hash[*includes.map{|association| [association, {:except => inherited_except}]}.flatten]
else
options[:include] ||= {}
end
# Exclude the black-list
options[:except].push *self.class.unencodable_attributes
# Include any default :include or :methods arguments that were passed in earlier
self.class.default_attributes.each do |attribute, as|
if association = self.class.reflect_on_association(attribute) if association = self.class.reflect_on_association(attribute)
options[:include][attribute] = {:except => inherited_except} options[:include][attribute] = {:except => inherited_except}
elsif respond_to?(attribute) && !self.class.column_names.include?(attribute.to_s) elsif respond_to?(attribute) && !self.class.column_names.include?(attribute.to_s)
options[:methods] ||= Array(options[:methods]).compact options[:methods] ||= Array(options[:methods]).compact
options[:methods].push attribute options[:methods].push attribute
end end
end end
as_json = super(options) end
unless self.class.renamed_encoded_attributes.empty? as_json = super(options)
self.class.renamed_encoded_attributes.each do |attribute, as| unless self.class.renamed_encoded_attributes.empty?
self.class.renamed_encoded_attributes.each do |attribute, as|
if as_json.has_key?(attribute) || as_json.has_key?(attribute.to_s)
as_json[as.to_s] = as_json.delete(attribute) || as_json.delete(attribute.to_s) as_json[as.to_s] = as_json.delete(attribute) || as_json.delete(attribute.to_s)
end end
end end
as_json
end end
as_json
end end
end end
end end
Expand Down
26 changes: 19 additions & 7 deletions spec/attr_encodable_spec.rb
@@ -1,4 +1,5 @@
require "lib/attr_encodable" require File.join(File.dirname(__FILE__), '..', 'lib', 'attr_encodable')#{}"../lib/attr_encodable"
require 'active_support'


describe Encodable do describe Encodable do
it "should automatically extend ActiveRecord::Base" do it "should automatically extend ActiveRecord::Base" do
Expand Down Expand Up @@ -38,7 +39,7 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end
:first_name => "flip", :first_name => "flip",
:last_name => "sasser", :last_name => "sasser",
:email => "flip@foobar.com", :email => "flip@foobar.com",
:encrypted_password => ActiveSupport::SecureRandom.hex(30), :encrypted_password => SecureRandom.hex(30),
:developer => true, :developer => true,
:admin => true, :admin => true,
:password_set => true, :password_set => true,
Expand Down Expand Up @@ -69,15 +70,15 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end


describe "at the parent model level" do describe "at the parent model level" do
it "should not mess with to_json unless when attr_encodable and attr_unencodable are not set" do it "should not mess with to_json unless when attr_encodable and attr_unencodable are not set" do
@user.as_json == @user.attributes @user.as_json.should == @user.attributes
end end


it "should not mess with :include options" do it "should not mess with :include options" do
@user.as_json(:include => :permissions) == @user.attributes.merge(:permissions => @user.permissions.as_json) @user.as_json(:include => :permissions).should == @user.attributes.merge(:permissions => @user.permissions.as_json)
end end


it "should not mess with :methods options" do it "should not mess with :methods options" do
@user.as_json(:methods => :foobar) == @user.attributes.merge(:foobar => "baz") @user.as_json(:methods => :foobar).should == @user.attributes.merge(:foobar => "baz")
end end


it "should allow me to whitelist attributes" do it "should allow me to whitelist attributes" do
Expand All @@ -90,6 +91,7 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end
@user.as_json.should == @user.attributes.except('login', 'first_name', 'last_name') @user.as_json.should == @user.attributes.except('login', 'first_name', 'last_name')
end end



# Of note is the INSANITY of ActiveRecord in that it applies :only / :except to :include as well. Which is # Of note is the INSANITY of ActiveRecord in that it applies :only / :except to :include as well. Which is
# obviously insane. Similarly, it doesn't allow :methods to come along when :only is specified. Good god, what # obviously insane. Similarly, it doesn't allow :methods to come along when :only is specified. Good god, what
# a shame. # a shame.
Expand All @@ -109,13 +111,13 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end
end end
end end


describe "at the child model level when the paren model has attr_encodable set" do describe "at the child model level when the parent model has attr_encodable set" do
before :each do before :each do
User.attr_encodable :login, :first_name, :last_name User.attr_encodable :login, :first_name, :last_name
end end


it "should not mess with to_json unless when attr_encodable and attr_unencodable are not set on the child, but are on the parent" do it "should not mess with to_json unless when attr_encodable and attr_unencodable are not set on the child, but are on the parent" do
@user.permissions.as_json == @user.permissions.map(&:attributes) @user.permissions.as_json.should == @user.permissions.map(&:attributes)
end end


it "should not mess with :include options" do it "should not mess with :include options" do
Expand Down Expand Up @@ -153,6 +155,16 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end
@user.as_json.should == @user.attributes.slice('login', 'first_name', 'id').merge(:foobar => "baz") @user.as_json.should == @user.attributes.slice('login', 'first_name', 'id').merge(:foobar => "baz")
end end


it "should allow me to only request certain whitelisted attributes and methods" do
User.attr_encodable :login, :first_name, :last_name, :foobar
@user.as_json(:only => [:login, :foobar]).should == {'login' => 'flipsasser', :foobar => 'baz'}
end

it "should allow me to use :only with aliased methods and attributes" do
User.attr_encodable :login => :login_eh, :first_name => :foist, :last_name => :last, :foobar => :baz
@user.as_json(:only => [:login, :foobar]).should == {'login_eh' => 'flipsasser', 'baz' => 'baz'}
end

describe "reassigning" do describe "reassigning" do
it "should let me reassign attributes" do it "should let me reassign attributes" do
User.attr_encodable :id => :identifier User.attr_encodable :id => :identifier
Expand Down

0 comments on commit b958977

Please sign in to comment.