Skip to content

Commit

Permalink
Add ActiveRecord integration and specs, docs!
Browse files Browse the repository at this point in the history
  • Loading branch information
Sutto committed Jun 9, 2012
1 parent 936dc2a commit a270612
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 15 deletions.
17 changes: 16 additions & 1 deletion README.md
Expand Up @@ -321,7 +321,7 @@ The final type, similar to collection objects but it includes details about the


One of the built in features of RocketPants is the ability to handle rescuing / controlling exceptions and more importantly to handle mapping exceptions to names, messages and error codes. One of the built in features of RocketPants is the ability to handle rescuing / controlling exceptions and more importantly to handle mapping exceptions to names, messages and error codes.


This comes in useful when you wish to automatically convert exceptions such as `ActiveRecord::RecordNotFound` to a structured bit of data in the response. Namely, it makes it trivial to generate objects that follow the JSON structure of: This comes in useful when you wish to automatically convert exceptions such as `ActiveRecord::RecordNotFound` (Note: This case is handled already) to a structured bit of data in the response. Namely, it makes it trivial to generate objects that follow the JSON structure of:


```json ```json
{ {
Expand All @@ -347,6 +347,8 @@ Out of the box, the following exceptions come pre-registered and setup:
- `:invalid_version` - An invalid API version was specified. - `:invalid_version` - An invalid API version was specified.
- `:not_implemented` - The specified endpoint is not yet implemented. - `:not_implemented` - The specified endpoint is not yet implemented.
- `:not_found` - The given resource could not be found. - `:not_found` - The given resource could not be found.
- `:invalid_resource` - The given resource was invalid.
- `:bad_request` - The given request was not as expected.


Note that error also excepts a Hash of contextual options, many which will be passed through to the Rails I18N subsystem. E.g: Note that error also excepts a Hash of contextual options, many which will be passed through to the Rails I18N subsystem. E.g:


Expand All @@ -372,6 +374,19 @@ Will return something similar to:
} }
``` ```


### Build in ActiveRecord Errors

Out of the box, Rocket Pants will automatically map the following to built in errors and rescue them
as appropriate.

- `ActiveRecord::RecordNotFound` into `RocketPants::NotFound`
- `ActiveRecord::RecordNotSaved` into `RocketPants::InvalidResource (with no validation messages).`
- `ActiveRecord::RecordInvalid` into `RocketPants::InvalidResource (with messages in the "messages" key of the JSON).`

For Invalid Resource messages, the response looks roughly akin to:



## Implementing Efficient Validation ## Implementing Efficient Validation


One of the core design principles built into RocketPants is simple support for "Efficient Validation" as described in the [Rack::Cache FAQ](http://rtomayko.github.com/rack-cache/faq) - Namely, it adds simple support for object-level caching using etags with fast verification thanks to the `RocketPants::CacheMiddleware` cache middleware. One of the core design principles built into RocketPants is simple support for "Efficient Validation" as described in the [Rack::Cache FAQ](http://rtomayko.github.com/rack-cache/faq) - Namely, it adds simple support for object-level caching using etags with fast verification thanks to the `RocketPants::CacheMiddleware` cache middleware.
Expand Down
22 changes: 10 additions & 12 deletions Rakefile
Expand Up @@ -4,25 +4,23 @@ require 'rspec/core'
require 'rspec/core/rake_task' require 'rspec/core/rake_task'
require 'bundler/gem_tasks' require 'bundler/gem_tasks'


task :default => :spec

desc "Run all specs in spec directory (excluding plugin specs)" desc "Run all specs in spec directory (excluding plugin specs)"
RSpec::Core::RakeTask.new(:spec) RSpec::Core::RakeTask.new(:spec)


INTEGRATION_LIBS = %w(will_paginate kaminari active_record)

namespace :spec do namespace :spec do


namespace :integration do namespace :integration do


desc "Run the will_paginate integrate specs" INTEGRATION_LIBS.each do |lib|
RSpec::Core::RakeTask.new(:will_paginate) do |t|
t.rspec_opts = "--tag integration" desc "Run the #{lib} integrate specs"
t.pattern = "./spec/integration/will_paginate_spec.rb" RSpec::Core::RakeTask.new(lib.to_sym) do |t|
end t.rspec_opts = "--tag integration"
t.pattern = "./spec/integration/#{lib}_spec.rb"
end


desc "Run the will_paginate integrate specs"
RSpec::Core::RakeTask.new(:kaminari) do |t|
t.rspec_opts = "--tag integration"
t.pattern = "./spec/integration/kaminari_spec.rb"
end end


end end
Expand All @@ -35,4 +33,4 @@ namespace :spec do
end end
end end


task :default => ["spec:integration:will_paginate", "spec:integration:kaminari"] task :default => ([:spec] + INTEGRATION_LIBS.map { |l| "spec:integration:#{l}" })
2 changes: 1 addition & 1 deletion lib/rocket_pants/active_record.rb
Expand Up @@ -5,7 +5,7 @@ module ActiveRecordIntegration


included do included do
map_error! ActiveRecord::RecordNotFound, RocketPants::NotFound map_error! ActiveRecord::RecordNotFound, RocketPants::NotFound
map_error! ActiveRecord::RecordNotSaved, RocketPants::InvalidResource map_error!(ActiveRecord::RecordNotSaved) { RocketPants::InvalidResource.new nil }
map_error! ActiveRecord::RecordInvalid do |exception| map_error! ActiveRecord::RecordInvalid do |exception|
RocketPants::InvalidResource.new exception.record.errors RocketPants::InvalidResource.new exception.record.errors
end end
Expand Down
55 changes: 55 additions & 0 deletions spec/integration/active_record_spec.rb
@@ -0,0 +1,55 @@
require 'spec_helper'

require 'active_record'
require 'rocket_pants/active_record'

describe RocketPants::Base, 'active record integration', :integration => true, :target => 'active_record' do
include ControllerHelpers

let(:table_manager) { ReversibleData.manager_for(:fish) }

before :each do
table_manager.up!
end

after(:each) { table_manager.down! }

let(:controller_class) do
Class.new(TestController)
end

def action_is(&blk)
controller_class.send :define_method, :test_data, &blk
end

it 'should automatically map ActiveRecord::RecordNotFound' do
action_is { Fish.find(1000) }
get :test_data
content['error'].should == 'not_found'
end

it 'should automatically map ActiveRecord::RecordNotSaved' do
action_is { raise ActiveRecord::RecordNotSaved }
@action_body = lambda { Fish.new.save }
get :test_data
content['error'].should == 'invalid_resource'
content['messages'].should == nil
end

it 'should automatically map ActiveRecord::RecordInvalid' do
action_is { Fish.new.save! }
get :test_data
content['error'].should == 'invalid_resource'
messages = content['messages']
messages.should be_present
messages.keys.should =~ %w(name child_number latin_name)
messages.each_pair do |name, value|
value.should be_present
value.should be_a Array
value.should be_all { |v| v.is_a?(String) }
expected = (name == 'name' ? 1 : 2)
value.length.should == expected
end
end

end
15 changes: 14 additions & 1 deletion spec/support/models.rb
Expand Up @@ -3,4 +3,17 @@


ReversibleData.add(:users) do |t| ReversibleData.add(:users) do |t|
t.integer :age t.integer :age
end end

fish = ReversibleData.add(:fish) do |t|
t.string :name
t.string :latin_name
t.integer :child_number
end

fish.define_model do
validates :name, :child_number, :presence => true
# Yes, I know it's technically not right.
validates :latin_name, :length => {:minimum => 5}, :format => /\A(\w+) (\w+)\Z/
validates :child_number, :numericality => true
end

0 comments on commit a270612

Please sign in to comment.