Permalink
Browse files

:only support when encoding

  • Loading branch information...
1 parent 5c658a0 commit b958977b559d70e0a9aa15b20167fbd9ec169c44 Flip Sasser committed Oct 16, 2012
Showing with 91 additions and 55 deletions.
  1. +1 −0 .rvmrc
  2. +3 −0 CHANGES.md
  3. +24 −0 Guardfile
  4. +2 −3 Rakefile
  5. +8 −16 attr_encodable.gemspec
  6. +34 −29 lib/attr_encodable.rb
  7. +19 −7 spec/attr_encodable_spec.rb
View
@@ -0,0 +1 @@
+rvm use 1.9.3@attr_encodable --create
View
@@ -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.
View
@@ -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
+
View
@@ -50,13 +50,12 @@ Jeweler::Tasks.new do |gemspec|
gemspec.summary = "An attribute black- or white-list for ActiveRecord serialization"
gemspec.files = Dir["{lib}/**/*", "LICENSE", "README.md"]
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.homepage = "http://github.com/Plinq/attr_encodable"
gemspec.authors = ["Flip Sasser"]
gemspec.test_files = Dir["{spec}/**/*"]
- gemspec.add_development_dependency 'rcov', '>= 0.9.9'
gemspec.add_development_dependency 'rspec', '>= 2.0'
- gemspec.add_dependency 'redis', '>= 2.1.1'
end
@@ -4,16 +4,14 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
- s.name = %q{attr_encodable}
- s.version = "0.0.5"
+ s.name = "attr_encodable"
+ s.version = "0.1.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Flip Sasser"]
- s.date = %q{2011-07-14}
- s.description = %q{
- 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 = %q{flip@x451.com}
+ s.date = "2012-10-16"
+ 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 "
+ s.email = "flip@x451.com"
s.extra_rdoc_files = [
"LICENSE",
"README.md"
@@ -23,28 +21,22 @@ Gem::Specification.new do |s|
"README.md",
"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.rubygems_version = %q{1.6.2}
- s.summary = %q{An attribute black- or white-list for ActiveRecord serialization}
+ s.rubygems_version = "1.8.24"
+ s.summary = "An attribute black- or white-list for ActiveRecord serialization"
s.test_files = ["spec/attr_encodable_spec.rb"]
if s.respond_to? :specification_version then
s.specification_version = 3
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_runtime_dependency(%q<redis>, [">= 2.1.1"])
else
- s.add_dependency(%q<rcov>, [">= 0.9.9"])
s.add_dependency(%q<rspec>, [">= 2.0"])
- s.add_dependency(%q<redis>, [">= 2.1.1"])
end
else
- s.add_dependency(%q<rcov>, [">= 0.9.9"])
s.add_dependency(%q<rspec>, [">= 2.0"])
- s.add_dependency(%q<redis>, [">= 2.1.1"])
end
end
View
@@ -80,48 +80,53 @@ def unencodable_attributes
module InstanceMethods
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]
- # We DON'T want to fuck with :only and :except showing up in the same call. This is a disaster.
- super
+ options[:except].push *self.class.default_attributes - Array(options.delete(:only).map(&:to_sym))
+ 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
- options ||= {}
- original_except = if options[:except]
- options[:except] = Array(options[:except]).map(&:to_sym)
- else
- options[:except] = []
- 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
- 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|
+ 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|
+ unless options[:except].include?(attribute)
if association = self.class.reflect_on_association(attribute)
options[:include][attribute] = {:except => inherited_except}
elsif respond_to?(attribute) && !self.class.column_names.include?(attribute.to_s)
options[:methods] ||= Array(options[:methods]).compact
options[:methods].push attribute
end
end
- as_json = super(options)
- unless self.class.renamed_encoded_attributes.empty?
- self.class.renamed_encoded_attributes.each do |attribute, as|
+ end
+ as_json = super(options)
+ 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)
end
end
- as_json
end
+ as_json
end
end
end
@@ -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
it "should automatically extend ActiveRecord::Base" do
@@ -38,7 +39,7 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end
:first_name => "flip",
:last_name => "sasser",
:email => "flip@foobar.com",
- :encrypted_password => ActiveSupport::SecureRandom.hex(30),
+ :encrypted_password => SecureRandom.hex(30),
:developer => true,
:admin => true,
:password_set => true,
@@ -69,15 +70,15 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; end
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
- @user.as_json == @user.attributes
+ @user.as_json.should == @user.attributes
end
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
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
it "should allow me to whitelist attributes" do
@@ -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')
end
+
# 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
# a shame.
@@ -109,13 +111,13 @@ class ::User < ActiveRecord::Base; has_many :permissions; def foobar; "baz"; 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
User.attr_encodable :login, :first_name, :last_name
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
- @user.permissions.as_json == @user.permissions.map(&:attributes)
+ @user.permissions.as_json.should == @user.permissions.map(&:attributes)
end
it "should not mess with :include options" do
@@ -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")
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
it "should let me reassign attributes" do
User.attr_encodable :id => :identifier

0 comments on commit b958977

Please sign in to comment.