Remove Active Resource source files from the repository

Dear Active Resource,

It's not that I hate you or anything, but you didn't get much attention lately. There're so many alternatives out there, and I think people have made their choice to use them than you. I think it's time for you to have a big rest, peacefully in this Git repository.

I will miss you,

@sikachu.
This commit is contained in:
Prem Sichanugrist 2011-05-15 15:34:36 -04:00
parent a85714a673
commit f1637bf2bb
68 changed files with 13 additions and 7147 deletions

View File

@ -13,7 +13,7 @@ task :build => "all:build"
desc "Release all gems to gemcutter and create a tag" desc "Release all gems to gemcutter and create a tag"
task :release => "all:release" task :release => "all:release"
PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties) PROJECTS = %w(activesupport activemodel actionpack actionmailer activerecord railties)
desc 'Run all tests by default' desc 'Run all tests by default'
task :default => %w(test test:isolated) task :default => %w(test test:isolated)
@ -109,11 +109,6 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb') rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*') rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
rdoc.rdoc_files.include('activeresource/README.rdoc')
rdoc.rdoc_files.include('activeresource/CHANGELOG.md')
rdoc.rdoc_files.include('activeresource/lib/active_resource.rb')
rdoc.rdoc_files.include('activeresource/lib/active_resource/*')
rdoc.rdoc_files.include('actionpack/README.rdoc') rdoc.rdoc_files.include('actionpack/README.rdoc')
rdoc.rdoc_files.include('actionpack/CHANGELOG.md') rdoc.rdoc_files.include('actionpack/CHANGELOG.md')
rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb')
@ -157,7 +152,6 @@ task :update_versions do
"activemodel" => "ActiveModel", "activemodel" => "ActiveModel",
"actionpack" => "ActionPack", "actionpack" => "ActionPack",
"actionmailer" => "ActionMailer", "actionmailer" => "ActionMailer",
"activeresource" => "ActiveResource",
"activerecord" => "ActiveRecord", "activerecord" => "ActiveRecord",
"railties" => "Rails" "railties" => "Rails"
} }

View File

@ -2,8 +2,8 @@ require 'active_support/core_ext/module'
module ActionController module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
# the view actions to a higher logical level. Example: # a higher logical level. Example:
# #
# # routes # # routes
# resources :posts # resources :posts

View File

@ -60,7 +60,7 @@ module ActionView
# #
# ==== Relying on named routes # ==== Relying on named routes
# #
# Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will # Passing a record (like an Active Record) instead of a Hash as the options parameter will
# trigger the named route for that record. The lookup will happen on the name of the class. So passing a # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).

View File

@ -5,7 +5,7 @@ Gem::Specification.new do |s|
s.name = 'activemodel' s.name = 'activemodel'
s.version = version s.version = version
s.summary = 'A toolkit for building modeling frameworks (part of Rails).' s.summary = 'A toolkit for building modeling frameworks (part of Rails).'
s.description = 'A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
s.required_ruby_version = '>= 1.9.3' s.required_ruby_version = '>= 1.9.3'

View File

@ -1,390 +0,0 @@
## Rails 4.0.0 (unreleased) ##
* Adds support for PATCH requests. *dlee*
## Rails 3.2.2 (March 1, 2012) ##
* No changes.
## Rails 3.2.1 (January 26, 2012) ##
* Documentation fixes.
## Rails 3.2.0 (January 20, 2012) ##
* Redirect responses: 303 See Other and 307 Temporary Redirect now behave like
301 Moved Permanently and 302 Found. GH #3302.
*Jim Herz*
## Rails 3.1.4 (March 1, 2012) ##
* No changes
## Rails 3.1.3 (November 20, 2011) ##
* No changes
## Rails 3.1.2 (November 18, 2011) ##
* No changes
## Rails 3.1.1 (October 7, 2011) ##
* No changes.
## Rails 3.1.0 (August 30, 2011) ##
* The default format has been changed to JSON for all requests. If you want to continue to use XML you will need to set `self.format = :xml` in the class. eg.
class User < ActiveResource::Base self.format = :xml
end
## Rails 3.0.12 (March 1, 2012) ##
* No changes.
## Rails 3.0.11 (November 18, 2011) ##
* No changes.
## Rails 3.0.10 (August 16, 2011) ##
* No changes.
## Rails 3.0.9 (June 16, 2011) ##
* No changes.
## Rails 3.0.8 (June 7, 2011) ##
* No Changes
## Rails 3.0.7 (April 18, 2011) ##
* No changes.
## Rails 3.0.6 (April 5, 2011) ##
* No changes.
## Rails 3.0.5 (February 26, 2011) ##
* No changes.
## Rails 3.0.4 (February 8, 2011) ##
* No changes.
## Rails 3.0.3 (November 16, 2010) ##
* No changes.
## Rails 3.0.2 (November 15, 2010) ##
* No changes
## Rails 3.0.1 (October 15, 2010) ##
* No Changes, just a version bump.
## Rails 3.0.0 (August 29, 2010) ##
* JSON: set Base.include_root_in_json = true to include a root value in the JSON: {"post": {"title": ...}}. Mirrors the Active Record option. *Santiago Pastorino*
* Add support for errors in JSON format. #1956 *Fabien Jakimowicz*
* Recognizes 410 as Resource Gone. #2316 *Jordan Brough, Jatinder Singh*
* More thorough SSL support. #2370 *Roy Nicholson*
* HTTP proxy support. #2133 *Marshall Huss, Sébastien Dabet*
## 2.3.2 Final (March 15, 2009) ##
* Nothing new, just included in 2.3.2
## 2.2.1 RC2 (November 14th, 2008) ##
* Fixed that ActiveResource#post would post an empty string when it shouldn't be posting anything #525 *Paolo Angelini*
## 2.2.0 RC1 (October 24th, 2008) ##
* Add ActiveResource::Base#to_xml and ActiveResource::Base#to_json. #1011 *Rasik Pandey, Cody Fauser*
* Add ActiveResource::Base.find(:last). [#754 state:resolved] (Adrian Mugnolo)
* Fixed problems with the logger used if the logging string included %'s [#840 state:resolved] (Jamis Buck)
* Fixed Base#exists? to check status code as integer [#299 state:resolved] (Wes Oldenbeuving)
## 2.1.0 (May 31st, 2008) ##
* Fixed response logging to use length instead of the entire thing (seangeo) *#27*
* Fixed that to_param should be used and honored instead of hardcoding the id #11406 *gspiers*
* Improve documentation. *Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert*
* Use HEAD instead of GET in exists? *bscofield*
* Fix small documentation typo. Closes #10670 *Luca Guidi*
* find_or_create_resource_for handles module nesting. #10646 *xavier*
* Allow setting ActiveResource::Base#format before #site. *Rick Olson*
* Support agnostic formats when calling custom methods. Closes #10635 *joerichsen*
* Document custom methods. #10589 *Cheah Chu Yeow*
* Ruby 1.9 compatibility. *Jeremy Kemper*
## 2.0.2 (December 16th, 2007) ##
* Added more specific exceptions for 400, 401, and 403 (all descending from ClientError so existing rescues will work) #10326 *trek*
* Correct empty response handling. #10445 *seangeo*
## 2.0.1 (December 7th, 2007) ##
* Don't cache net/http object so that ActiveResource is more thread-safe. Closes #10142 *kou*
* Update XML documentation examples to include explicit type attributes. Closes #9754 *Josh Susser*
* Added one-off declarations of mock behavior [David Heinemeier Hansson]. Example:
Before:
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.xml", {}, "<person><name>David</name></person>"
end
Now:
ActiveResource::HttpMock.respond_to.get "/people/1.xml", {}, "<person><name>David</name></person>"
* Added ActiveResource.format= which defaults to :xml but can also be set to :json [David Heinemeier Hansson]. Example:
class Person < ActiveResource::Base
self.site = "http://app/"
self.format = :json
end
person = Person.find(1) # => GET http://app/people/1.json
person.name = "David"
person.save # => PUT http://app/people/1.json {name: "David"}
Person.format = :xml
person.name = "Mary"
person.save # => PUT http://app/people/1.json <person><name>Mary</name></person>
* Fix reload error when path prefix is used. #8727 *Ian Warshak*
* Remove ActiveResource::Struct because it hasn't proven very useful. Creating a new ActiveResource::Base subclass is often less code and always clearer. #8612 *Josh Peek*
* Fix query methods on resources. *Cody Fauser*
* pass the prefix_options to the instantiated record when using find without a specific id. Closes #8544 *Eloy Duran*
* Recognize and raise an exception on 405 Method Not Allowed responses. #7692 *Josh Peek*
* Handle string and symbol param keys when splitting params into prefix params and query params.
Comment.find(:all, :params => { :article_id => 5, :page => 2 }) or Comment.find(:all, :params => { 'article_id' => 5, :page => 2 })
* Added find-one with symbol [David Heinemeier Hansson]. Example: Person.find(:one, :from => :leader) # => GET /people/leader.xml
* BACKWARDS INCOMPATIBLE: Changed the finder API to be more extensible with :params and more strict usage of scopes [David Heinemeier Hansson]. Changes:
Person.find(:all, :title => "CEO") ...becomes: Person.find(:all, :params => { :title => "CEO" })
Person.find(:managers) ...becomes: Person.find(:all, :from => :managers)
Person.find("/companies/1/manager.xml") ...becomes: Person.find(:one, :from => "/companies/1/manager.xml")
* Add support for setting custom headers per Active Resource model *Rick Olson*
class Project
headers['X-Token'] = 'foo'
end
\# makes the GET request with the custom X-Token header
Project.find(:all)
* Added find-by-path options to ActiveResource::Base.find [David Heinemeier Hansson]. Examples:
employees = Person.find(:all, :from => "/companies/1/people.xml") # => GET /companies/1/people.xml
manager = Person.find("/companies/1/manager.xml") # => GET /companies/1/manager.xml
* Added support for using classes from within a single nested module [David Heinemeier Hansson]. Example:
module Highrise
class Note < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
class Comment < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
end
assert_kind_of Highrise::Comment, Note.find(1).comments.first
* Added load_attributes_from_response as a way of loading attributes from other responses than just create *David Heinemeier Hansson*
class Highrise::Task < ActiveResource::Base
def complete
load_attributes_from_response(post(:complete))
end
end
...will set "done_at" when complete is called.
* Added support for calling custom methods #6979 *rwdaigle*
Person.find(:managers) # => GET /people/managers.xml
Kase.find(1).post(:close) # => POST /kases/1/close.xml
* Remove explicit prefix_options parameter for ActiveResource::Base#initialize. *Rick Olson*
ActiveResource splits the prefix_options from it automatically.
* Allow ActiveResource::Base.delete with custom prefix. *Rick Olson*
* Add ActiveResource::Base#dup *Rick Olson*
* Fixed constant warning when fetching the same object multiple times *David Heinemeier Hansson*
* Added that saves which get a body response (and not just a 201) will use that response to update themselves *David Heinemeier Hansson*
* Disregard namespaces from the default element name, so Highrise::Person will just try to fetch from "/people", not "/highrise/people" *David Heinemeier Hansson*
* Allow array and hash query parameters. #7756 *Greg Spurrier*
* Loading a resource preserves its prefix_options. #7353 *Ryan Daigle*
* Carry over the convenience of #create from ActiveRecord. Closes #7340. *Ryan Daigle*
* Increase ActiveResource::Base test coverage. Closes #7173, #7174 *Rich Collins*
* Interpret 422 Unprocessable Entity as ResourceInvalid. #7097 *dkubb*
* Mega documentation patches. #7025, #7069 *rwdaigle*
* Base.exists?(id, options) and Base#exists? check whether the resource is found. #6970 *rwdaigle*
* Query string support. *untext, Jeremy Kemper*
# GET /forums/1/topics.xml?sort=created_at
Topic.find(:all, :forum_id => 1, :sort => 'created_at')
* Base#==, eql?, and hash methods. == returns true if its argument is identical to self or if it's an instance of the same class, is not new?, and has the same id. eql? is an alias for ==. hash delegates to id. *Jeremy Kemper*
* Allow subclassed resources to share the site info *Rick Olson, Jeremy Kemper*
d class BeastResource < ActiveResource::Base
self.site = 'http://beast.caboo.se'
end
class Forum < BeastResource
# taken from BeastResource
# self.site = 'http://beast.caboo.se'
end
class Topic < BeastResource
self.site += '/forums/:forum_id'
end
* Fix issues with ActiveResource collection handling. Closes #6291. *bmilekic*
* Use attr_accessor_with_default to dry up attribute initialization. References #6538. *Stuart Halloway*
* Add basic logging support for logging outgoing requests. *Jamis Buck*
* Add Base.delete for deleting resources without having to instantiate them first. *Jamis Buck*
* Make #save behavior mimic AR::Base#save (true on success, false on failure). *Jamis Buck*
* Add Basic HTTP Authentication to ActiveResource (closes #6305). *jonathan*
* Extracted #id_from_response as an entry point for customizing how a created resource gets its own ID.
By default, it extracts from the Location response header.
* Optimistic locking: raise ActiveResource::ResourceConflict on 409 Conflict response. *Jeremy Kemper*
# Example controller action
def update
@person.save!
rescue ActiveRecord::StaleObjectError
render :xml => @person.reload.to_xml, :status => '409 Conflict'
end
* Basic validation support *Rick Olson*
Parses the xml response of ActiveRecord::Errors#to_xml with a similar interface to ActiveRecord::Errors.
render :xml => @person.errors.to_xml, :status => '400 Validation Error'
* Deep hashes are converted into collections of resources. *Jeremy Kemper*
Person.new :name => 'Bob',
:address => { :id => 1, :city => 'Portland' },
:contacts => [{ :id => 1 }, { :id => 2 }]
Looks for Address and Contact resources and creates them if unavailable.
So clients can fetch a complex resource in a single request if you e.g.
render :xml => @person.to_xml(:include => [:address, :contacts])
in your controller action.
* Major updates *Rick Olson*
* Add full support for find/create/update/destroy
* Add support for specifying prefixes.
* Allow overriding of element_name, collection_name, and primary key
* Provide simpler HTTP mock interface for testing
# rails routing code
map.resources :posts do |post|
post.resources :comments
end
# ActiveResources
class Post < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000/"
end
class Comment < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000/posts/:post_id/"
end
@post = Post.find 5
@comments = Comment.find :all, :post_id => @post.id
@comment = Comment.new({:body => 'hello world'}, {:post_id => @post.id})
@comment.save
* Base.site= accepts URIs. 200...400 are valid response codes. PUT and POST request bodies default to ''. *Jeremy Kemper*
* Initial checkin: object-oriented client for restful HTTP resources which follow the Rails convention. *David Heinemeier Hansson*

View File

@ -1,20 +0,0 @@
Copyright (c) 2006-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,189 +0,0 @@
= Active Resource
Active Resource (ARes) connects business objects and Representational State Transfer (REST)
web services. It implements object-relational mapping for REST web services to provide transparent
proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing
in ActionController::Resources).
== Philosophy
Active Resource attempts to provide a coherent wrapper object-relational mapping for REST
web services. It follows the same philosophy as Active Record, in that one of its prime aims
is to reduce the amount of code needed to map to these resources. This is made possible
by relying on a number of code- and protocol-based conventions that make it easy for Active Resource
to infer complex relations and structures. These conventions are outlined in detail in the documentation
for ActiveResource::Base.
== Overview
Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database
tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result
received and serialized into a usable Ruby object.
== Download and installation
The latest version of Active Support can be installed with RubyGems:
% [sudo] gem install activeresource
Source code can be downloaded as part of the Rails project on GitHub
* https://github.com/rails/rails/tree/master/activeresource
=== Configuration and Usage
Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class
that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
class Person < ActiveResource::Base
self.site = "http://api.people.com:3000"
end
Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
life cycle methods that operate against a persistent store.
# Find a person with id = 1
ryan = Person.find(1)
Person.exists?(1) # => true
As you can see, the methods are quite similar to Active Record's methods for dealing with database
records. But rather than dealing directly with a database record, you're dealing with HTTP resources (which may or may not be database records).
==== Protocol
Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
built into Action Controller but will also work with any other REST service that properly implements the protocol.
REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification:
* GET requests are used for finding and retrieving resources.
* POST requests are used to create new resources.
* PUT requests are used to update existing resources.
* DELETE requests are used to delete resources.
For more information on how this protocol works with Active Resource, see the ActiveResource::Base documentation;
for more general information on REST web services, see the article here[http://en.wikipedia.org/wiki/Representational_State_Transfer].
==== Find
Find requests use the GET method and expect the XML form of whatever resource/resources is/are being requested. So,
for a request for a single element, the XML of that item is expected in response:
# Expects a response of
#
# <person><id type="integer">1</id><attribute1>value1</attribute1><attribute2>..</attribute2></person>
#
# for GET http://api.people.com:3000/people/1.xml
#
ryan = Person.find(1)
The XML document that is received is used to build a new object of type Person, with each
XML element becoming an attribute on the object.
ryan.is_a? Person # => true
ryan.attribute1 # => 'value1'
Any complex element (one that contains other elements) becomes its own object:
# With this response:
#
# <person><id>1</id><attribute1>value1</attribute1><complex><attribute2>value2</attribute2></complex></person>
#
# for GET http://api.people.com:3000/people/1.xml
#
ryan = Person.find(1)
ryan.complex # => <Person::Complex::xxxxx>
ryan.complex.attribute2 # => 'value2'
Collections can also be requested in a similar fashion
# Expects a response of
#
# <people type="array">
# <person><id type="integer">1</id><first>Ryan</first></person>
# <person><id type="integer">2</id><first>Jim</first></person>
# </people>
#
# for GET http://api.people.com:3000/people.xml
#
people = Person.all
people.first # => <Person::xxx 'first' => 'Ryan' ...>
people.last # => <Person::xxx 'first' => 'Jim' ...>
==== Create
Creating a new resource submits the XML form of the resource as the body of the request and expects
a 'Location' header in the response with the RESTful URL location of the newly created resource. The
id of the newly created resource is parsed out of the Location response header and automatically set
as the id of the ARes object.
# <person><first>Ryan</first></person>
#
# is submitted as the body on
#
# POST http://api.people.com:3000/people.xml
#
# when save is called on a new Person object. An empty response is
# is expected with a 'Location' header value:
#
# Response (201): Location: http://api.people.com:3000/people/2
#
ryan = Person.new(:first => 'Ryan')
ryan.new? # => true
ryan.save # => true
ryan.new? # => false
ryan.id # => 2
==== Update
'save' is also used to update an existing resource and follows the same protocol as creating a resource
with the exception that no response headers are needed -- just an empty response when the update on the
server side was successful.
# <person><first>Ryan</first></person>
#
# is submitted as the body on
#
# PUT http://api.people.com:3000/people/1.xml
#
# when save is called on an existing Person object. An empty response is
# is expected with code (204)
#
ryan = Person.find(1)
ryan.first # => 'Ryan'
ryan.first = 'Rizzle'
ryan.save # => true
==== Delete
Destruction of a resource can be invoked as a class and instance method of the resource.
# A request is made to
#
# DELETE http://api.people.com:3000/people/1.xml
#
# for both of these forms. An empty response with
# is expected with response code (200)
#
ryan = Person.find(1)
ryan.destroy # => true
ryan.exists? # => false
Person.delete(2) # => true
Person.exists?(2) # => false
== License
Active Support is released under the MIT license:
* http://www.opensource.org/licenses/MIT
== Support
API documentation is at
* http://api.rubyonrails.org
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
You can find more usage information in the ActiveResource::Base documentation.

View File

@ -1,66 +0,0 @@
#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
desc "Default Task"
task :default => [ :test ]
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.warning = true
t.verbose = true
}
namespace :test do
task :isolated do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib"
Dir.glob("test/**/*_test.rb").all? do |file|
sh(ruby, '-w', "-Ilib:test:#{activesupport_path}", file)
end or raise "Failures"
end
end
spec = eval(File.read('activeresource.gemspec'))
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
FileList["lib/active_resource/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
f = File.open(file_name)
while line = f.gets
lines += 1
next if line =~ /^\s*$/
next if line =~ /^\s*#/
codelines += 1
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
total_lines += lines
total_codelines += codelines
lines, codelines = 0, 0
end
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
end
# Publishing ------------------------------------------------------
desc "Release to gemcutter"
task :release => :package do
require 'rake/gemcutter'
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end

View File

@ -1,24 +0,0 @@
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'activeresource'
s.version = version
s.summary = 'REST modeling framework (part of Rails).'
s.description = 'REST on Rails. Wrap your RESTful web app with Ruby classes and work with them like Active Record models.'
s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
s.extra_rdoc_files = %w( README.rdoc )
s.rdoc_options.concat ['--main', 'README.rdoc']
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
end

View File

@ -1,70 +0,0 @@
require 'rubygems'
require 'active_resource'
require 'benchmark'
TIMES = (ENV['N'] || 10_000).to_i
# deep nested resource
attrs = {
:id => 1,
:name => 'Luis',
:age => 21,
:friends => [
{
:name => 'JK',
:age => 24,
:colors => ['red', 'green', 'blue'],
:brothers => [
{
:name => 'Mateo',
:age => 35,
:children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }]
},
{
:name => 'Felipe',
:age => 33,
:children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }]
}
]
},
{
:name => 'Eduardo',
:age => 20,
:colors => [],
:brothers => [
{
:name => 'Sebas',
:age => 23,
:children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }]
},
{
:name => 'Elsa',
:age => 19,
:children => [{ :name => 'Natacha', :age => 1 }]
},
{
:name => 'Milena',
:age => 16,
:children => []
}
]
}
]
}
class Customer < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
module Nested
class Customer < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
end
Benchmark.bm(40) do |x|
x.report('Model.new (instantiation)') { TIMES.times { Customer.new } }
x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } }
x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } }
x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } }
end

View File

@ -1,45 +0,0 @@
#--
# Copyright (c) 2006-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
require 'active_support'
require 'active_model'
require 'active_resource/version'
module ActiveResource
extend ActiveSupport::Autoload
autoload :Base
autoload :Connection
autoload :CustomMethods
autoload :Formats
autoload :HttpMock
autoload :Observing
autoload :Schema
autoload :Validations
end

File diff suppressed because it is too large Load Diff

View File

@ -1,284 +0,0 @@
require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/uri'
require 'active_support/core_ext/object/inclusion'
require 'net/https'
require 'date'
require 'time'
require 'uri'
module ActiveResource
# Class to handle connections to remote web services.
# This class is used by ActiveResource::Base to interface with REST
# services.
class Connection
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
:patch => 'Content-Type',
:delete => 'Accept',
:head => 'Accept'
}
attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options
attr_accessor :format
class << self
def requests
@@requests ||= []
end
end
# The +site+ parameter is required and will set the +site+
# attribute to the URI for the remote resource service.
def initialize(site, format = ActiveResource::Formats::JsonFormat)
raise ArgumentError, 'Missing site URI' unless site
@user = @password = nil
self.site = site
self.format = format
end
# Set URI for remote service.
def site=(site)
@site = site.is_a?(URI) ? site : URI.parse(site)
@user = URI.parser.unescape(@site.user) if @site.user
@password = URI.parser.unescape(@site.password) if @site.password
end
# Set the proxy for remote service.
def proxy=(proxy)
@proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
end
# Sets the user for remote service.
def user=(user)
@user = user
end
# Sets the password for remote service.
def password=(password)
@password = password
end
# Sets the auth type for remote service.
def auth_type=(auth_type)
@auth_type = legitimize_auth_type(auth_type)
end
# Sets the number of seconds after which HTTP requests to the remote service should time out.
def timeout=(timeout)
@timeout = timeout
end
# Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
def ssl_options=(opts={})
@ssl_options = opts
end
# Executes a GET request.
# Used to get (find) resources.
def get(path, headers = {})
with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
end
# Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
# Used to delete resources.
def delete(path, headers = {})
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
end
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def patch(path, body = '', headers = {})
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
end
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
# Used to update resources.
def put(path, body = '', headers = {})
with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
end
# Executes a POST request.
# Used to create new resources.
def post(path, body = '', headers = {})
with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
end
# Executes a HEAD request.
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
def head(path, headers = {})
with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) }
end
private
# Makes a request to the remote service.
def request(method, path, *arguments)
result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
payload[:method] = method
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
payload[:result] = http.send(method, path, *arguments)
end
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
rescue OpenSSL::SSL::SSLError => e
raise SSLError.new(e.message)
end
# Handles response and error codes from the remote service.
def handle_response(response)
case response.code.to_i
when 301, 302, 303, 307
raise(Redirection.new(response))
when 200...400
response
when 400
raise(BadRequest.new(response))
when 401
raise(UnauthorizedAccess.new(response))
when 403
raise(ForbiddenAccess.new(response))
when 404
raise(ResourceNotFound.new(response))
when 405
raise(MethodNotAllowed.new(response))
when 409
raise(ResourceConflict.new(response))
when 410
raise(ResourceGone.new(response))
when 422
raise(ResourceInvalid.new(response))
when 401...500
raise(ClientError.new(response))
when 500...600
raise(ServerError.new(response))
else
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
end
end
# Creates new Net::HTTP instance for communication with the
# remote service and resources.
def http
configure_http(new_http)
end
def new_http
if @proxy
Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
else
Net::HTTP.new(@site.host, @site.port)
end
end
def configure_http(http)
apply_ssl_options(http).tap do |https|
# Net::HTTP timeouts default to 60 seconds.
if defined? @timeout
https.open_timeout = @timeout
https.read_timeout = @timeout
end
end
end
def apply_ssl_options(http)
http.tap do |https|
# Skip config if site is already a https:// URI.
if defined? @ssl_options
http.use_ssl = true
# Default to no cert verification (WTF? FIXME)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
# All the SSL options have corresponding http settings.
@ssl_options.each { |key, value| http.send "#{key}=", value }
end
end
end
def default_header
@default_header ||= {}
end
# Builds headers for request to remote service.
def build_request_headers(headers, http_method, uri)
authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers)
end
def response_auth_header
@response_auth_header ||= ""
end
def with_auth
retried ||= false
yield
rescue UnauthorizedAccess => e
raise if retried || auth_type != :digest
@response_auth_header = e.response['WWW-Authenticate']
retried = true
retry
end
def authorization_header(http_method, uri)
if @user || @password
if auth_type == :digest
{ 'Authorization' => digest_auth_header(http_method, uri) }
else
{ 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") }
end
else
{}
end
end
def digest_auth_header(http_method, uri)
params = extract_params_from_response
request_uri = uri.path
request_uri << "?#{uri.query}" if uri.query
ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}")
ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}")
params.merge!('cnonce' => client_nonce)
request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":"))
"Digest #{auth_attributes_for(uri, request_digest, params)}"
end
def client_nonce
Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
end
def extract_params_from_response
params = {}
if response_auth_header =~ /^(\w+) (.*)/
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
end
params
end
def auth_attributes_for(uri, request_digest, params)
[
%Q(username="#{@user}"),
%Q(realm="#{params['realm']}"),
%Q(qop="#{params['qop']}"),
%Q(uri="#{uri.path}"),
%Q(nonce="#{params['nonce']}"),
%Q(nc="0"),
%Q(cnonce="#{params['cnonce']}"),
%Q(opaque="#{params['opaque']}"),
%Q(response="#{request_digest}")].join(", ")
end
def http_format_header(http_method)
{HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
end
def legitimize_auth_type(auth_type)
return :basic if auth_type.nil?
auth_type = auth_type.to_sym
auth_type.in?([:basic, :digest]) ? auth_type : :basic
end
end
end

View File

@ -1,127 +0,0 @@
require 'active_support/core_ext/object/blank'
module ActiveResource
# A module to support custom REST methods and sub-resources, allowing you to break out
# of the "default" REST methods with your own custom resource requests. For example,
# say you use Rails to expose a REST service and configure your routes with:
#
# map.resources :people, :new => { :register => :post },
# :member => { :promote => :put, :deactivate => :delete }
# :collection => { :active => :get }
#
# This route set creates routes for the following HTTP requests:
#
# POST /people/new/register.json # PeopleController.register
# PATCH/PUT /people/1/promote.json # PeopleController.promote with :id => 1
# DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
# GET /people/active.json # PeopleController.active
#
# Using this module, Active Resource can use these custom REST methods just like the
# standard methods.
#
# class Person < ActiveResource::Base
# self.site = "https://37s.sunrise.com"
# end
#
# Person.new(:name => 'Ryan').post(:register) # POST /people/new/register.json
# # => { :id => 1, :name => 'Ryan' }
#
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.json
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.json
#
# Person.get(:active) # GET /people/active.json
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
#
module CustomMethods
extend ActiveSupport::Concern
included do
class << self
alias :orig_delete :delete
# Invokes a GET to a given custom REST method. For example:
#
# Person.get(:active) # GET /people/active.json
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
#
# Person.get(:active, :awesome => true) # GET /people/active.json?awesome=true
# # => [{:id => 1, :name => 'Ryan'}]
#
# Note: the objects returned from this method are not automatically converted
# into ActiveResource::Base instances - they are ordinary Hashes. If you are expecting
# ActiveResource::Base instances, use the <tt>find</tt> class method with the
# <tt>:from</tt> option. For example:
#
# Person.find(:all, :from => :active)
def get(custom_method_name, options = {})
hashified = format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body)
derooted = Formats.remove_root(hashified)
derooted.is_a?(Array) ? derooted.map { |e| Formats.remove_root(e) } : derooted
end
def post(custom_method_name, options = {}, body = '')
connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
end
def patch(custom_method_name, options = {}, body = '')
connection.patch(custom_method_collection_url(custom_method_name, options), body, headers)
end
def put(custom_method_name, options = {}, body = '')
connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
end
def delete(custom_method_name, options = {})
# Need to jump through some hoops to retain the original class 'delete' method
if custom_method_name.is_a?(Symbol)
connection.delete(custom_method_collection_url(custom_method_name, options), headers)
else
orig_delete(custom_method_name, options)
end
end
end
end
module ClassMethods
def custom_method_collection_url(method_name, options = {})
prefix_options, query_options = split_options(options)
"#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}"
end
end
def get(method_name, options = {})
self.class.format.decode(connection.get(custom_method_element_url(method_name, options), self.class.headers).body)
end
def post(method_name, options = {}, body = nil)
request_body = body.blank? ? encode : body
if new?
connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
else
connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
end
end
def patch(method_name, options = {}, body = '')
connection.patch(custom_method_element_url(method_name, options), body, self.class.headers)
end
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
def delete(method_name, options = {})
connection.delete(custom_method_element_url(method_name, options), self.class.headers)
end
private
def custom_method_element_url(method_name, options = {})
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
end
def custom_method_new_element_url(method_name, options = {})
"#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
end
end
end

View File

@ -1,82 +0,0 @@
module ActiveResource
class ConnectionError < StandardError # :nodoc:
attr_reader :response
def initialize(response, message = nil)
@response = response
@message = message
end
def to_s
message = "Failed."
message << " Response code = #{response.code}." if response.respond_to?(:code)
message << " Response message = #{response.message}." if response.respond_to?(:message)
message
end
end
# Raised when a Timeout::Error occurs.
class TimeoutError < ConnectionError
def initialize(message)
@message = message
end
def to_s; @message ;end
end
# Raised when a OpenSSL::SSL::SSLError occurs.
class SSLError < ConnectionError
def initialize(message)
@message = message
end
def to_s; @message ;end
end
# 3xx Redirection
class Redirection < ConnectionError # :nodoc:
def to_s
response['Location'] ? "#{super} => #{response['Location']}" : super
end
end
class MissingPrefixParam < ArgumentError # :nodoc:
end
# 4xx Client Error
class ClientError < ConnectionError # :nodoc:
end
# 400 Bad Request
class BadRequest < ClientError # :nodoc:
end
# 401 Unauthorized
class UnauthorizedAccess < ClientError # :nodoc:
end
# 403 Forbidden
class ForbiddenAccess < ClientError # :nodoc:
end
# 404 Not Found
class ResourceNotFound < ClientError # :nodoc:
end
# 409 Conflict
class ResourceConflict < ClientError # :nodoc:
end
# 410 Gone
class ResourceGone < ClientError # :nodoc:
end
# 5xx Server Error
class ServerError < ConnectionError # :nodoc:
end
# 405 Method Not Allowed
class MethodNotAllowed < ClientError # :nodoc:
def allowed_methods
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
end
end
end

View File

@ -1,22 +0,0 @@
module ActiveResource
module Formats
autoload :XmlFormat, 'active_resource/formats/xml_format'
autoload :JsonFormat, 'active_resource/formats/json_format'
# Lookup the format class from a mime type reference symbol. Example:
#
# ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
# ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
def self.[](mime_type_reference)
ActiveResource::Formats.const_get(ActiveSupport::Inflector.camelize(mime_type_reference.to_s) + "Format")
end
def self.remove_root(data)
if data.is_a?(Hash) && data.keys.size == 1
data.values.first
else
data
end
end
end
end

View File

@ -1,25 +0,0 @@
require 'active_support/json'
module ActiveResource
module Formats
module JsonFormat
extend self
def extension
"json"
end
def mime_type
"application/json"
end
def encode(hash, options = nil)
ActiveSupport::JSON.encode(hash, options)
end
def decode(json)
Formats.remove_root(ActiveSupport::JSON.decode(json))
end
end
end
end

View File

@ -1,25 +0,0 @@
require 'active_support/core_ext/hash/conversions'
module ActiveResource
module Formats
module XmlFormat
extend self
def extension
"xml"
end
def mime_type
"application/xml"
end
def encode(hash, options={})
hash.to_xml(options)
end
def decode(xml)
Formats.remove_root(Hash.from_xml(xml))
end
end
end
end

View File

@ -1,332 +0,0 @@
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/object/inclusion'
module ActiveResource
class InvalidRequestError < StandardError; end #:nodoc:
# One thing that has always been a pain with remote web services is testing. The HttpMock
# class makes it easy to test your Active Resource models by creating a set of mock responses to specific
# requests.
#
# To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
# method with an attached block. The block declares a set of URIs with expected input, and the output
# each request should return. The passed in block has any number of entries in the following generalized
# format:
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
#
# * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +patch+, +put+, +delete+ or
# +head+.
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
# called.
# * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
# hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
# if your tests sends a request with identical headers.
# * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
# such as Json.
# * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
# * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
# <tt>request_headers</tt> listed above.
#
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
# +path+ and <tt>request_headers</tt>. If no match is found an +InvalidRequestError+ exception
# will be raised showing you what request it could not find a response for and also what requests and response
# pairs have been recorded so you can create a new mock for that request.
#
# ==== Example
# def setup
# @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
# ActiveResource::HttpMock.respond_to do |mock|
# mock.post "/people.json", {}, @matz, 201, "Location" => "/people/1.json"
# mock.get "/people/1.json", {}, @matz
# mock.put "/people/1.json", {}, nil, 204
# mock.delete "/people/1.json", {}, nil, 200
# end
# end
#
# def test_get_matz
# person = Person.find(1)
# assert_equal "Matz", person.name
# end
#
class HttpMock
class Responder #:nodoc:
def initialize(responses)
@responses = responses
end
[ :post, :patch, :put, :get, :delete, :head ].each do |method|
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end
module_eval <<-EOE, __FILE__, __LINE__ + 1
def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
request = Request.new(:#{method}, path, nil, request_headers)
response = Response.new(body || "", status, response_headers)
delete_duplicate_responses(request)
@responses << [request, response]
end
EOE
end
private
def delete_duplicate_responses(request)
@responses.delete_if {|r| r[0] == request }
end
end
class << self
# Returns an array of all request objects that have been sent to the mock. You can use this to check
# if your model actually sent an HTTP request.
#
# ==== Example
# def setup
# @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
# ActiveResource::HttpMock.respond_to do |mock|
# mock.get "/people/1.json", {}, @matz
# end
# end
#
# def test_should_request_remote_service
# person = Person.find(1) # Call the remote service
#
# # This request object has the same HTTP method and path as declared by the mock
# expected_request = ActiveResource::Request.new(:get, "/people/1.json")
#
# # Assert that the mock received, and responded to, the expected request from the model
# assert ActiveResource::HttpMock.requests.include?(expected_request)
# end
def requests
@@requests ||= []
end
# Returns the list of requests and their mocked responses. Look up a
# response for a request using <tt>responses.assoc(request)</tt>.
def responses
@@responses ||= []
end
# Accepts a block which declares a set of requests and responses for the HttpMock to respond to in
# the following format:
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
#
# === Example
#
# @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
# ActiveResource::HttpMock.respond_to do |mock|
# mock.post "/people.json", {}, @matz, 201, "Location" => "/people/1.json"
# mock.get "/people/1.json", {}, @matz
# mock.put "/people/1.json", {}, nil, 204
# mock.delete "/people/1.json", {}, nil, 200
# end
#
# Alternatively, accepts a hash of <tt>{Request => Response}</tt> pairs allowing you to generate
# these the following format:
#
# ActiveResource::Request.new(method, path, body, request_headers)
# ActiveResource::Response.new(body, status, response_headers)
#
# === Example
#
# Request.new(:#{method}, path, nil, request_headers)
#
# @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
#
# create_matz = ActiveResource::Request.new(:post, '/people.json', @matz, {})
# created_response = ActiveResource::Response.new("", 201, {"Location" => "/people/1.json"})
# get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
# ok_response = ActiveResource::Response.new("", 200, {})
#
# pairs = {create_matz => created_response, get_matz => ok_response}
#
# ActiveResource::HttpMock.respond_to(pairs)
#
# Note, by default, every time you call +respond_to+, any previous request and response pairs stored
# in HttpMock will be deleted giving you a clean slate to work on.
#
# If you want to override this behavior, pass in +false+ as the last argument to +respond_to+
#
# === Example
#
# ActiveResource::HttpMock.respond_to do |mock|
# mock.send(:get, "/people/1", {}, "JSON1")
# end
# ActiveResource::HttpMock.responses.length #=> 1
#
# ActiveResource::HttpMock.respond_to(false) do |mock|
# mock.send(:get, "/people/2", {}, "JSON2")
# end
# ActiveResource::HttpMock.responses.length #=> 2
#
# This also works with passing in generated pairs of requests and responses, again, just pass in false
# as the last argument:
#
# === Example
#
# ActiveResource::HttpMock.respond_to do |mock|
# mock.send(:get, "/people/1", {}, "JSON1")
# end
# ActiveResource::HttpMock.responses.length #=> 1
#
# get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
# ok_response = ActiveResource::Response.new("", 200, {})
#
# pairs = {get_matz => ok_response}
#
# ActiveResource::HttpMock.respond_to(pairs, false)
# ActiveResource::HttpMock.responses.length #=> 2
#
# # If you add a response with an existing request, it will be replaced
#
# fail_response = ActiveResource::Response.new("", 404, {})
# pairs = {get_matz => fail_response}
#
# ActiveResource::HttpMock.respond_to(pairs, false)
# ActiveResource::HttpMock.responses.length #=> 2
#
def respond_to(*args) #:yields: mock
pairs = args.first || {}
reset! if args.last.class != FalseClass
if block_given?
yield Responder.new(responses)
else
delete_responses_to_replace pairs.to_a
responses.concat pairs.to_a
Responder.new(responses)
end
end
def delete_responses_to_replace(new_responses)
new_responses.each{|nr|
request_to_remove = nr[0]
@@responses = responses.delete_if{|r| r[0] == request_to_remove}
}
end
# Deletes all logged requests and responses.
def reset!
requests.clear
responses.clear
end
end
# body? methods
{ true => %w(post patch put),
false => %w(get delete head) }.each do |has_body, methods|
methods.each do |method|
# def post(path, body, headers)
# request = ActiveResource::Request.new(:post, path, body, headers)
# self.class.requests << request
# if response = self.class.responses.assoc(request)
# response[1]
# else
# raise InvalidRequestError.new("Could not find a response recorded for #{request.to_s} - Responses recorded are: - #{inspect_responses}")
# end
# end
module_eval <<-EOE, __FILE__, __LINE__ + 1
def #{method}(path, #{'body, ' if has_body}headers)
request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
self.class.requests << request
if response = self.class.responses.assoc(request)
response[1]
else
raise InvalidRequestError.new("Could not find a response recorded for \#{request.to_s} - Responses recorded are: \#{inspect_responses}")
end
end
EOE
end
end
def initialize(site) #:nodoc:
@site = site
end
def inspect_responses #:nodoc:
self.class.responses.map { |r| r[0].to_s }.inspect
end
end
class Request
attr_accessor :path, :method, :body, :headers
def initialize(method, path, body = nil, headers = {})
@method, @path, @body, @headers = method, path, body, headers
end
def ==(req)
path == req.path && method == req.method && headers_match?(req)
end
def to_s
"<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
end
private
def headers_match?(req)
# Ignore format header on equality if it's not defined
format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method]
if headers[format_header].present? || req.headers[format_header].blank?
headers == req.headers
else
headers.dup.merge(format_header => req.headers[format_header]) == req.headers
end
end
end
class Response
attr_accessor :body, :message, :code, :headers
def initialize(body, message = 200, headers = {})
@body, @message, @headers = body, message.to_s, headers
@code = @message[0,3].to_i
resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
if resp_cls && !resp_cls.body_permitted?
@body = nil
end
self['Content-Length'] = @body.nil? ? "0" : body.size.to_s
end
# Returns true if code is 2xx,
# false otherwise.
def success?
code.in?(200..299)
end
def [](key)
headers[key]
end
def []=(key, value)
headers[key] = value
end
# Returns true if the other is a Response with an equal body, equal message
# and equal headers. Otherwise it returns false.
def ==(other)
if (other.is_a?(Response))
other.body == body && other.message == message && other.headers == headers
else
false
end
end
end
class Connection
private
silence_warnings do
def http
@http ||= HttpMock.new(@site)
end
end
end
end

View File

@ -1,15 +0,0 @@
module ActiveResource
class LogSubscriber < ActiveSupport::LogSubscriber
def request(event)
result = event.payload[:result]
info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
end
def logger
ActiveResource::Base.logger
end
end
end
ActiveResource::LogSubscriber.attach_to :active_resource

View File

@ -1,29 +0,0 @@
module ActiveResource
module Observing
extend ActiveSupport::Concern
include ActiveModel::Observing
included do
%w( create save update destroy ).each do |method|
# def create_with_notifications(*args, &block)
# notify_observers(:before_create)
# if result = create_without_notifications(*args, &block)
# notify_observers(:after_create)
# end
# result
# end
# alias_method_chain(create, :notifications)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{method}_with_notifications(*args, &block)
notify_observers(:before_#{method})
if result = #{method}_without_notifications(*args, &block)
notify_observers(:after_#{method})
end
result
end
EOS
alias_method_chain(method, :notifications)
end
end
end
end

View File

@ -1,14 +0,0 @@
require "active_resource"
require "rails"
module ActiveResource
class Railtie < Rails::Railtie
config.active_resource = ActiveSupport::OrderedOptions.new
initializer "active_resource.set_configs" do |app|
app.config.active_resource.each do |k,v|
ActiveResource::Base.send "#{k}=", v
end
end
end
end

View File

@ -1,59 +0,0 @@
require 'active_resource/exceptions'
module ActiveResource # :nodoc:
class Schema # :nodoc:
# attributes can be known to be one of these types. They are easy to
# cast to/from.
KNOWN_ATTRIBUTE_TYPES = %w( string text integer float decimal datetime timestamp time date binary boolean )
# An array of attribute definitions, representing the attributes that
# have been defined.
attr_accessor :attrs
# The internals of an Active Resource Schema are very simple -
# unlike an Active Record TableDefinition (on which it is based).
# It provides a set of convenience methods for people to define their
# schema using the syntax:
# schema do
# string :foo
# integer :bar
# end
#
# The schema stores the name and type of each attribute. That is then
# read out by the schema method to populate the schema of the actual
# resource.
def initialize
@attrs = {}
end
def attribute(name, type, options = {})
raise ArgumentError, "Unknown Attribute type: #{type.inspect} for key: #{name.inspect}" unless type.nil? || Schema::KNOWN_ATTRIBUTE_TYPES.include?(type.to_s)
the_type = type.to_s
# TODO: add defaults
#the_attr = [type.to_s]
#the_attr << options[:default] if options.has_key? :default
@attrs[name.to_s] = the_type
self
end
# The following are the attribute types supported by Active Resource
# migrations.
KNOWN_ATTRIBUTE_TYPES.each do |attr_type|
# def string(*args)
# options = args.extract_options!
# attr_names = args
#
# attr_names.each { |name| attribute(name, 'string', options) }
# end
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{attr_type.to_s}(*args)
options = args.extract_options!
attr_names = args
attr_names.each { |name| attribute(name, '#{attr_type}', options) }
end
EOV
end
end
end

View File

@ -1,172 +0,0 @@
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActiveResource
class ResourceInvalid < ClientError #:nodoc:
end
# Active Resource validation is reported to and from this object, which is used by Base#save
# to determine whether the object in a valid state to be saved. See usage example in Validations.
class Errors < ActiveModel::Errors
# Grabs errors from an array of messages (like ActiveRecord::Validations).
# The second parameter directs the errors cache to be cleared (default)
# or not (by passing true).
def from_array(messages, save_cache = false)
clear unless save_cache
humanized_attributes = Hash[@base.attributes.keys.map { |attr_name| [attr_name.humanize, attr_name] }]
messages.each do |message|
attr_message = humanized_attributes.keys.detect do |attr_name|
if message[0, attr_name.size + 1] == "#{attr_name} "
add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
end
end
self[:base] << message if attr_message.nil?
end
end
# Grabs errors from a hash of attribute => array of errors elements
# The second parameter directs the errors cache to be cleared (default)
# or not (by passing true)
#
# Unrecognized attribute names will be humanized and added to the record's
# base errors.
def from_hash(messages, save_cache = false)
clear unless save_cache
messages.each do |(key,errors)|
errors.each do |error|
if @base.attributes.keys.include?(key)
add key, error
elsif key == 'base'
self[:base] << error
else
# reporting an error on an attribute not in attributes
# format and add them to base
self[:base] << "#{key.humanize} #{error}"
end
end
end
end
# Grabs errors from a json response.
def from_json(json, save_cache = false)
decoded = ActiveSupport::JSON.decode(json) || {} rescue {}
if decoded.kind_of?(Hash) && (decoded.has_key?('errors') || decoded.empty?)
errors = decoded['errors'] || {}
if errors.kind_of?(Array)
# 3.2.1-style with array of strings
ActiveSupport::Deprecation.warn('Returning errors as an array of strings is deprecated.')
from_array errors, save_cache
else
# 3.2.2+ style
from_hash errors, save_cache
end
else
# <3.2-style respond_with - lacks 'errors' key
ActiveSupport::Deprecation.warn('Returning errors as a hash without a root "errors" key is deprecated.')
from_hash decoded, save_cache
end
end
# Grabs errors from an XML response.
def from_xml(xml, save_cache = false)
array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
from_array array, save_cache
end
end
# Module to support validation and errors with Active Resource objects. The module overrides
# Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned
# in the web service response. The module also adds an +errors+ collection that mimics the interface
# of the errors provided by ActiveModel::Errors.
#
# ==== Example
#
# Consider a Person resource on the server requiring both a +first_name+ and a +last_name+ with a
# <tt>validates_presence_of :first_name, :last_name</tt> declaration in the model:
#
# person = Person.new(:first_name => "Jim", :last_name => "")
# person.save # => false (server returns an HTTP 422 status code and errors)
# person.valid? # => false
# person.errors.empty? # => false
# person.errors.count # => 1
# person.errors.full_messages # => ["Last name can't be empty"]
# person.errors[:last_name] # => ["can't be empty"]
# person.last_name = "Halpert"
# person.save # => true (and person is now saved to the remote service)
#
module Validations
extend ActiveSupport::Concern
include ActiveModel::Validations
included do
alias_method_chain :save, :validation
end
# Validate a resource and save (POST) it to the remote web service.
# If any local validations fail - the save (POST) will not be attempted.
def save_with_validation(options={})
perform_validation = options[:validate] != false
# clear the remote validations so they don't interfere with the local
# ones. Otherwise we get an endless loop and can never change the
# fields so as to make the resource valid.
@remote_errors = nil
if perform_validation && valid? || !perform_validation
save_without_validation
true
else
false
end
rescue ResourceInvalid => error
# cache the remote errors because every call to <tt>valid?</tt> clears
# all errors. We must keep a copy to add these back after local
# validations.
@remote_errors = error
load_remote_errors(@remote_errors, true)
false
end
# Loads the set of remote errors into the object's Errors based on the
# content-type of the error-block received.
def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
case self.class.format
when ActiveResource::Formats[:xml]
errors.from_xml(remote_errors.response.body, save_cache)
when ActiveResource::Formats[:json]
errors.from_json(remote_errors.response.body, save_cache)
end
end
# Checks for errors on an object (i.e., is resource.errors empty?).
#
# Runs all the specified local validations and returns true if no errors
# were added, otherwise false.
# Runs local validations (eg those on your Active Resource model), and
# also any errors returned from the remote system the last time we
# saved.
# Remote errors can only be cleared by trying to re-save the resource.
#
# ==== Examples
# my_person = Person.create(params[:person])
# my_person.valid?
# # => true
#
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
# my_person.valid?
# # => false
#
def valid?
super
load_remote_errors(@remote_errors, true) if defined?(@remote_errors) && @remote_errors.present?
errors.empty?
end
# Returns the Errors object that holds all information about attribute error messages.
def errors
@errors ||= Errors.new(self)
end
end
end

View File

@ -1,10 +0,0 @@
module ActiveResource
module VERSION #:nodoc:
MAJOR = 4
MINOR = 0
TINY = 0
PRE = "beta"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
end

View File

@ -1,143 +0,0 @@
require File.expand_path('../../../load_paths', __FILE__)
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
require 'minitest/autorun'
require 'active_resource'
require 'active_support'
require 'active_support/test_case'
require 'setter_trap'
require 'active_support/logger'
ActiveResource::Base.logger = ActiveSupport::Logger.new("#{File.dirname(__FILE__)}/debug.log")
def setup_response
matz_hash = { 'person' => { :id => 1, :name => 'Matz' } }
@default_request_headers = { 'Content-Type' => 'application/json' }
@matz = matz_hash.to_json
@matz_xml = matz_hash.to_xml
@david = { :person => { :id => 2, :name => 'David' } }.to_json
@greg = { :person => { :id => 3, :name => 'Greg' } }.to_json
@addy = { :address => { :id => 1, :street => '12345 Street', :country => 'Australia' } }.to_json
@rick = { :person => { :name => "Rick", :age => 25 } }.to_json
@joe = { :person => { :id => 6, :name => 'Joe', :likes_hats => true }}.to_json
@people = { :people => [ { :person => { :id => 1, :name => 'Matz' } }, { :person => { :id => 2, :name => 'David' } }] }.to_json
@people_david = { :people => [ { :person => { :id => 2, :name => 'David' } }] }.to_json
@addresses = { :addresses => [{ :address => { :id => 1, :street => '12345 Street', :country => 'Australia' } }] }.to_json
# - deep nested resource -
# - Luis (Customer)
# - JK (Customer::Friend)
# - Mateo (Customer::Friend::Brother)
# - Edith (Customer::Friend::Brother::Child)
# - Martha (Customer::Friend::Brother::Child)
# - Felipe (Customer::Friend::Brother)
# - Bryan (Customer::Friend::Brother::Child)
# - Luke (Customer::Friend::Brother::Child)
# - Eduardo (Customer::Friend)
# - Sebas (Customer::Friend::Brother)
# - Andres (Customer::Friend::Brother::Child)
# - Jorge (Customer::Friend::Brother::Child)
# - Elsa (Customer::Friend::Brother)
# - Natacha (Customer::Friend::Brother::Child)
# - Milena (Customer::Friend::Brother)
#
@luis = {
:customer => {
:id => 1,
:name => 'Luis',
:friends => [{
:name => 'JK',
:brothers => [
{
:name => 'Mateo',
:children => [{ :name => 'Edith' },{ :name => 'Martha' }]
}, {
:name => 'Felipe',
:children => [{ :name => 'Bryan' },{ :name => 'Luke' }]
}
]
}, {
:name => 'Eduardo',
:brothers => [
{
:name => 'Sebas',
:children => [{ :name => 'Andres' },{ :name => 'Jorge' }]
}, {
:name => 'Elsa',
:children => [{ :name => 'Natacha' }]
}, {
:name => 'Milena',
:children => []
}
]
}]
}
}.to_json
# - resource with yaml array of strings; for ARs using serialize :bar, Array
@marty = <<-eof.strip
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<person>
<id type=\"integer\">5</id>
<name>Marty</name>
<colors type=\"yaml\">---
- \"red\"
- \"green\"
- \"blue\"
</colors>
</person>
eof
@startup_sound = {
:sound => {
:name => "Mac Startup Sound", :author => { :name => "Jim Reekes" }
}
}.to_json
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.json", {}, @matz
mock.get "/people/1.xml", {}, @matz_xml
mock.get "/people/2.xml", {}, @david
mock.get "/people/5.xml", {}, @marty
mock.get "/people/Greg.json", {}, @greg
mock.get "/people/6.json", {}, @joe
mock.get "/people/4.json", { 'key' => 'value' }, nil, 404
mock.put "/people/1.json", {}, nil, 204
mock.delete "/people/1.json", {}, nil, 200
mock.delete "/people/2.xml", {}, nil, 400
mock.get "/people/99.json", {}, nil, 404
mock.post "/people.json", {}, @rick, 201, 'Location' => '/people/5.xml'
mock.get "/people.json", {}, @people
mock.get "/people/1/addresses.json", {}, @addresses
mock.get "/people/1/addresses/1.json", {}, @addy
mock.get "/people/1/addresses/2.xml", {}, nil, 404
mock.get "/people/2/addresses.json", {}, nil, 404
mock.get "/people/2/addresses/1.xml", {}, nil, 404
mock.get "/people/Greg/addresses/1.json", {}, @addy
mock.put "/people/1/addresses/1.json", {}, nil, 204
mock.delete "/people/1/addresses/1.json", {}, nil, 200
mock.post "/people/1/addresses.json", {}, nil, 201, 'Location' => '/people/1/addresses/5'
mock.get "/people/1/addresses/99.json", {}, nil, 404
mock.get "/people//addresses.xml", {}, nil, 404
mock.get "/people//addresses/1.xml", {}, nil, 404
mock.put "/people//addresses/1.xml", {}, nil, 404
mock.delete "/people//addresses/1.xml", {}, nil, 404
mock.post "/people//addresses.xml", {}, nil, 404
mock.head "/people/1.json", {}, nil, 200
mock.head "/people/Greg.json", {}, nil, 200
mock.head "/people/99.json", {}, nil, 404
mock.head "/people/1/addresses/1.json", {}, nil, 200
mock.head "/people/1/addresses/2.json", {}, nil, 404
mock.head "/people/2/addresses/1.json", {}, nil, 404
mock.head "/people/Greg/addresses/1.json", {}, nil, 200
# customer
mock.get "/customers/1.json", {}, @luis
# sound
mock.get "/sounds/1.json", {}, @startup_sound
end
Person.user = nil
Person.password = nil
end

View File

@ -1,251 +0,0 @@
require 'abstract_unit'
class AuthorizationTest < ActiveSupport::TestCase
Response = Struct.new(:code)
def setup
@conn = ActiveResource::Connection.new('http://localhost')
@matz = { :person => { :id => 1, :name => 'Matz' } }.to_json
@david = { :person => { :id => 2, :name => 'David' } }.to_json
@authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost")
@basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
end
private
def decode(response)
@authenticated_conn.format.decode(response.body)
end
end
class BasicAuthorizationTest < AuthorizationTest
def setup
super
@authenticated_conn.auth_type = :basic
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/2.json", @basic_authorization_request_header, @david
mock.get "/people/1.json", @basic_authorization_request_header, nil, 401, { 'WWW-Authenticate' => 'i_should_be_ignored' }
mock.put "/people/2.json", @basic_authorization_request_header, nil, 204
mock.delete "/people/2.json", @basic_authorization_request_header, nil, 200
mock.post "/people/2/addresses.json", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5'
mock.head "/people/2.json", @basic_authorization_request_header, nil, 200
end
end
def test_get
david = decode(@authenticated_conn.get("/people/2.json"))
assert_equal "David", david["name"]
end
def test_post
response = @authenticated_conn.post("/people/2/addresses.json")
assert_equal "/people/1/addresses/5", response["Location"]
end
def test_put
response = @authenticated_conn.put("/people/2.json")
assert_equal 204, response.code
end
def test_delete
response = @authenticated_conn.delete("/people/2.json")
assert_equal 200, response.code
end
def test_head
response = @authenticated_conn.head("/people/2.json")
assert_equal 200, response.code
end
def test_retry_on_401_doesnt_happen_with_basic_auth
assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.json") }
assert_equal "", @authenticated_conn.send(:response_auth_header)
end
def test_raises_invalid_request_on_unauthorized_requests
assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
end
def test_authorization_header
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["david", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_with_username_but_no_password
@conn = ActiveResource::Connection.new("http://david:@localhost")
authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["david"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_with_password_but_no_username
@conn = ActiveResource::Connection.new("http://:test123@localhost")
authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_with_decoded_credentials_from_url
@conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost")
authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["my@email.com", "123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_explicitly_setting_username_and_password
@authenticated_conn = ActiveResource::Connection.new("http://@localhost")
@authenticated_conn.user = 'david'
@authenticated_conn.password = 'test123'
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["david", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_explicitly_setting_username_but_no_password
@conn = ActiveResource::Connection.new("http://@localhost")
@conn.user = "david"
authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["david"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_explicitly_setting_password_but_no_username
@conn = ActiveResource::Connection.new("http://@localhost")
@conn.password = "test123"
authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
authorization = authorization_header["Authorization"].to_s.split
assert_equal "Basic", authorization[0]
assert_equal ["david", "test123"], ::Base64.decode64(authorization[1]).split(":")[0..1]
end
def test_client_nonce_is_not_nil
assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce)
end
end
class DigestAuthorizationTest < AuthorizationTest
def setup
super
@authenticated_conn.auth_type = :digest
# Make client nonce deterministic
def @authenticated_conn.client_nonce; 'i-am-a-client-nonce' end
@nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA=="
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
mock.get "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "c064d5ba8891a25290c76c8c7d31fb7b") }, @david, 200
mock.get "/people/1.json", { 'Authorization' => request_digest_auth_header("/people/1.json", "f9c0b594257bb8422af4abd429c5bb70") }, @matz, 200
mock.put "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "50a685d814f94665b9d160fbbaa3958a") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
mock.put "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "5a75cde841122d8e0f20f8fd1f98a743") }, nil, 204
mock.delete "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "846f799107eab5ca4285b909ee299a33") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
mock.delete "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "9f5b155224edbbb69fd99d8ce094681e") }, nil, 200
mock.post "/people/2/addresses.json", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.json", "6984d405ff3d9ed07bbf747dcf16afb0") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
mock.post "/people/2/addresses.json", { 'Authorization' => request_digest_auth_header("/people/2/addresses.json", "4bda6a28dbf930b5af9244073623bd04") }, nil, 201, 'Location' => '/people/1/addresses/5'
mock.head "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "15e5ed84ba5c4cfcd5c98a36c2e4f421") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
mock.head "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "d4c6d2bcc8717abb2e2ccb8c49ee6a91") }, nil, 200
end
end
def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization']
end
def test_authorization_header_with_query_string_if_auth_type_is_digest
authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name'))
assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization']
end
def test_get_with_digest_auth_handles_initial_401_response_and_retries
response = @authenticated_conn.get("/people/2.json")
assert_equal "David", decode(response)["name"]
end
def test_post_with_digest_auth_handles_initial_401_response_and_retries
response = @authenticated_conn.post("/people/2/addresses.json")
assert_equal "/people/1/addresses/5", response["Location"]
assert_equal 201, response.code
end
def test_put_with_digest_auth_handles_initial_401_response_and_retries
response = @authenticated_conn.put("/people/2.json")
assert_equal 204, response.code
end
def test_delete_with_digest_auth_handles_initial_401_response_and_retries
response = @authenticated_conn.delete("/people/2.json")
assert_equal 200, response.code
end
def test_head_with_digest_auth_handles_initial_401_response_and_retries
response = @authenticated_conn.head("/people/2.json")
assert_equal 200, response.code
end
def test_get_with_digest_auth_caches_nonce
response = @authenticated_conn.get("/people/2.json")
assert_equal "David", decode(response)["name"]
# There is no mock for this request with a non-cached nonce.
response = @authenticated_conn.get("/people/1.json")
assert_equal "Matz", decode(response)["name"]
end
def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth
@conn.auth_type = :digest
assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") }
assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
end
private
def blank_digest_auth_header(uri, response)
%Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}")
end
def request_digest_auth_header(uri, response)
%Q(Digest username="david", realm="RailsTestApp", qop="auth", uri="#{uri}", nonce="#{@nonce}", nc="0", cnonce="i-am-a-client-nonce", opaque="ef6dfb078ba22298d366f99567814ffb", response="#{response}")
end
def response_digest_auth_header
%Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb")
end
end

View File

@ -1,101 +0,0 @@
require 'abstract_unit'
require 'fixtures/person'
require 'fixtures/street_address'
require 'active_support/core_ext/hash/conversions'
class CustomMethodsTest < ActiveSupport::TestCase
def setup
@matz = { :person => { :id => 1, :name => 'Matz' } }.to_json
@matz_deep = { :person => { :id => 1, :name => 'Matz', :other => 'other' } }.to_json
@matz_array = { :people => [{ :person => { :id => 1, :name => 'Matz' } }] }.to_json
@ryan = { :person => { :name => 'Ryan' } }.to_json
@addy = { :address => { :id => 1, :street => '12345 Street' } }.to_json
@addy_deep = { :address => { :id => 1, :street => '12345 Street', :zip => "27519" } }.to_json
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.json", {}, @matz
mock.get "/people/1/shallow.json", {}, @matz
mock.get "/people/1/deep.json", {}, @matz_deep
mock.get "/people/retrieve.json?name=Matz", {}, @matz_array
mock.get "/people/managers.json", {}, @matz_array
mock.post "/people/hire.json?name=Matz", {}, nil, 201
mock.put "/people/1/promote.json?position=Manager", {}, nil, 204
mock.put "/people/promote.json?name=Matz", {}, nil, 204, {}
mock.put "/people/sort.json?by=name", {}, nil, 204
mock.delete "/people/deactivate.json?name=Matz", {}, nil, 200
mock.delete "/people/1/deactivate.json", {}, nil, 200
mock.post "/people/new/register.json", {}, @ryan, 201, 'Location' => '/people/5.json'
mock.post "/people/1/register.json", {}, @matz, 201
mock.get "/people/1/addresses/1.json", {}, @addy
mock.get "/people/1/addresses/1/deep.json", {}, @addy_deep
mock.put "/people/1/addresses/1/normalize_phone.json?locale=US", {}, nil, 204
mock.put "/people/1/addresses/sort.json?by=name", {}, nil, 204
mock.post "/people/1/addresses/new/link.json", {}, { :address => { :street => '12345 Street' } }.to_json, 201, 'Location' => '/people/1/addresses/2.json'
end
Person.user = nil
Person.password = nil
end
def teardown
ActiveResource::HttpMock.reset!
end
def test_custom_collection_method
# GET
assert_equal([{ "id" => 1, "name" => 'Matz' }], Person.get(:retrieve, :name => 'Matz'))
# POST
assert_equal(ActiveResource::Response.new("", 201, {}), Person.post(:hire, :name => 'Matz'))
# PUT
assert_equal ActiveResource::Response.new("", 204, {}),
Person.put(:promote, {:name => 'Matz'}, 'atestbody')
assert_equal ActiveResource::Response.new("", 204, {}), Person.put(:sort, :by => 'name')
# DELETE
Person.delete :deactivate, :name => 'Matz'
# Nested resource
assert_equal ActiveResource::Response.new("", 204, {}), StreetAddress.put(:sort, :person_id => 1, :by => 'name')
end
def test_custom_element_method
# Test GET against an element URL
assert_equal Person.find(1).get(:shallow), {"id" => 1, "name" => 'Matz'}
assert_equal Person.find(1).get(:deep), {"id" => 1, "name" => 'Matz', "other" => 'other'}
# Test PUT against an element URL
assert_equal ActiveResource::Response.new("", 204, {}), Person.find(1).put(:promote, {:position => 'Manager'}, 'body')
# Test DELETE against an element URL
assert_equal ActiveResource::Response.new("", 200, {}), Person.find(1).delete(:deactivate)
# With nested resources
assert_equal StreetAddress.find(1, :params => { :person_id => 1 }).get(:deep),
{ "id" => 1, "street" => '12345 Street', "zip" => "27519" }
assert_equal ActiveResource::Response.new("", 204, {}),
StreetAddress.find(1, :params => { :person_id => 1 }).put(:normalize_phone, :locale => 'US')
end
def test_custom_new_element_method
# Test POST against a new element URL
ryan = Person.new(:name => 'Ryan')
assert_equal ActiveResource::Response.new(@ryan, 201, { 'Location' => '/people/5.json' }), ryan.post(:register)
expected_request = ActiveResource::Request.new(:post, '/people/new/register.json', @ryan)
assert_equal expected_request.body, ActiveResource::HttpMock.requests.first.body
# Test POST against a nested collection URL
addy = StreetAddress.new(:street => '123 Test Dr.', :person_id => 1)
assert_equal ActiveResource::Response.new({ :address => { :street => '12345 Street' } }.to_json,
201, { 'Location' => '/people/1/addresses/2.json' }),
addy.post(:link)
matz = Person.find(1)
assert_equal ActiveResource::Response.new(@matz, 201), matz.post(:register)
end
def test_find_custom_resources
assert_equal 'Matz', Person.find(:all, :from => :managers).first.name
end
end

View File

@ -1,52 +0,0 @@
require 'abstract_unit'
require "fixtures/person"
require "fixtures/street_address"
class BaseEqualityTest < ActiveSupport::TestCase
def setup
@new = Person.new
@one = Person.new(:id => 1)
@two = Person.new(:id => 2)
@street = StreetAddress.new(:id => 2)
end
def test_should_equal_self
assert @new == @new, '@new == @new'
assert @one == @one, '@one == @one'
end
def test_shouldnt_equal_new_resource
assert @new != @one, '@new != @one'
assert @one != @new, '@one != @new'
end
def test_shouldnt_equal_different_class
assert @two != @street, 'person != street_address with same id'
assert @street != @two, 'street_address != person with same id'
end
def test_eql_should_alias_equals_operator
assert_equal @new == @new, @new.eql?(@new)
assert_equal @new == @one, @new.eql?(@one)
assert_equal @one == @one, @one.eql?(@one)
assert_equal @one == @new, @one.eql?(@new)
assert_equal @one == @street, @one.eql?(@street)
end
def test_hash_should_be_id_hash
[@new, @one, @two, @street].each do |resource|
assert_equal resource.id.hash, resource.hash
end
end
def test_with_prefix_options
assert_equal @one == @one, @one.eql?(@one)
assert_equal @one == @one.dup, @one.eql?(@one.dup)
new_one = @one.dup
new_one.prefix_options = {:foo => 'bar'}
assert_not_equal @one, new_one
end
end

View File

@ -1,199 +0,0 @@
require 'abstract_unit'
require "fixtures/person"
require "fixtures/street_address"
require 'active_support/core_ext/hash/conversions'
module Highrise
class Note < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
class Comment < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
module Deeply
module Nested
class Note < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
class Comment < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
module TestDifferentLevels
class Note < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
end
end
end
end
class BaseLoadTest < ActiveSupport::TestCase
def setup
@matz = { :id => 1, :name => 'Matz' }
@first_address = { :address => { :id => 1, :street => '12345 Street' } }
@addresses = [@first_address, { :address => { :id => 2, :street => '67890 Street' } }]
@addresses_from_json = { :street_addresses => @addresses }
@addresses_from_json_single = { :street_addresses => [ @first_address ] }
@deep = { :id => 1, :street => {
:id => 1, :state => { :id => 1, :name => 'Oregon',
:notable_rivers => [
{ :id => 1, :name => 'Willamette' },
{ :id => 2, :name => 'Columbia', :rafted_by => @matz }],
:postal_codes => [ 97018, 1234567890 ],
:dates => [ Time.now ],
:votes => [ true, false, true ],
:places => [ "Columbia City", "Unknown" ]}}}
# List of books formated as [{timestamp_of_publication => name}, ...]
@books = {:books => [
{1009839600 => "Ruby in a Nutshell"},
{1199142000 => "The Ruby Programming Language"}
]}
@books_date = {:books => [
{Time.at(1009839600) => "Ruby in a Nutshell"},
{Time.at(1199142000) => "The Ruby Programming Language"}
]}
@person = Person.new
end
def test_load_hash_with_integers_as_keys
assert_nothing_raised{@person.load(@books)}
end
def test_load_hash_with_dates_as_keys
assert_nothing_raised{@person.load(@books_date)}
end
def test_load_expects_hash
assert_raise(ArgumentError) { @person.load nil }
assert_raise(ArgumentError) { @person.load '<person id="1"/>' }
end
def test_load_simple_hash
assert_equal Hash.new, @person.attributes
assert_equal @matz.stringify_keys, @person.load(@matz).attributes
end
def test_after_load_attributes_are_accessible
assert_equal Hash.new, @person.attributes
assert_equal @matz.stringify_keys, @person.load(@matz).attributes
assert_equal @matz[:name], @person.attributes['name']
end
def test_after_load_attributes_are_accessible_via_indifferent_access
assert_equal Hash.new, @person.attributes
assert_equal @matz.stringify_keys, @person.load(@matz).attributes
assert_equal @matz[:name], @person.attributes['name']
assert_equal @matz[:name], @person.attributes[:name]
end
def test_load_one_with_existing_resource
address = @person.load(:street_address => @first_address.values.first).street_address
assert_kind_of StreetAddress, address
assert_equal @first_address.values.first.stringify_keys, address.attributes
end
def test_load_one_with_unknown_resource
address = silence_warnings { @person.load(@first_address).address }
assert_kind_of Person::Address, address
assert_equal @first_address.values.first.stringify_keys, address.attributes
end
def test_load_collection_with_existing_resource
addresses = @person.load(@addresses_from_json).street_addresses
assert_kind_of Array, addresses
addresses.each { |address| assert_kind_of StreetAddress, address }
assert_equal @addresses.map { |a| a[:address].stringify_keys }, addresses.map(&:attributes)
end
def test_load_collection_with_unknown_resource
Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address)
assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
addresses = silence_warnings { @person.load(:addresses => @addresses).addresses }
assert Person.const_defined?(:Address), "Address should have been autocreated"
addresses.each { |address| assert_kind_of Person::Address, address }
assert_equal @addresses.map { |a| a[:address].stringify_keys }, addresses.map(&:attributes)
end
def test_load_collection_with_single_existing_resource
addresses = @person.load(@addresses_from_json_single).street_addresses
assert_kind_of Array, addresses
addresses.each { |address| assert_kind_of StreetAddress, address }
assert_equal [ @first_address.values.first ].map(&:stringify_keys), addresses.map(&:attributes)
end
def test_load_collection_with_single_unknown_resource
Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address)
assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
addresses = silence_warnings { @person.load(:addresses => [ @first_address ]).addresses }
assert Person.const_defined?(:Address), "Address should have been autocreated"
addresses.each { |address| assert_kind_of Person::Address, address }
assert_equal [ @first_address.values.first ].map(&:stringify_keys), addresses.map(&:attributes)
end
def test_recursively_loaded_collections
person = @person.load(@deep)
assert_equal @deep[:id], person.id
street = person.street
assert_kind_of Person::Street, street
assert_equal @deep[:street][:id], street.id
state = street.state
assert_kind_of Person::Street::State, state
assert_equal @deep[:street][:state][:id], state.id
rivers = state.notable_rivers
assert_kind_of Array, rivers
assert_kind_of Person::Street::State::NotableRiver, rivers.first
assert_equal @deep[:street][:state][:notable_rivers].first[:id], rivers.first.id
assert_equal @matz[:id], rivers.last.rafted_by.id
postal_codes = state.postal_codes
assert_kind_of Array, postal_codes
assert_equal 2, postal_codes.size
assert_kind_of Fixnum, postal_codes.first
assert_equal @deep[:street][:state][:postal_codes].first, postal_codes.first
assert_kind_of Numeric, postal_codes.last
assert_equal @deep[:street][:state][:postal_codes].last, postal_codes.last
places = state.places
assert_kind_of Array, places
assert_kind_of String, places.first
assert_equal @deep[:street][:state][:places].first, places.first
dates = state.dates
assert_kind_of Array, dates
assert_kind_of Time, dates.first
assert_equal @deep[:street][:state][:dates].first, dates.first
votes = state.votes
assert_kind_of Array, votes
assert_kind_of TrueClass, votes.first
assert_equal @deep[:street][:state][:votes].first, votes.first
end
def test_nested_collections_within_the_same_namespace
n = Highrise::Note.new(:comments => [{ :comment => { :name => "1" } }])
assert_kind_of Highrise::Comment, n.comments.first
end
def test_nested_collections_within_deeply_nested_namespace
n = Highrise::Deeply::Nested::Note.new(:comments => [{ :name => "1" }])
assert_kind_of Highrise::Deeply::Nested::Comment, n.comments.first
end
def test_nested_collections_in_different_levels_of_namespaces
n = Highrise::Deeply::Nested::TestDifferentLevels::Note.new(:comments => [{ :name => "1" }])
assert_kind_of Highrise::Deeply::Nested::Comment, n.comments.first
end
end

View File

@ -1,411 +0,0 @@
require 'abstract_unit'
require 'active_support/core_ext/hash/conversions'
require "fixtures/person"
require "fixtures/street_address"
########################################################################
# Testing the schema of your Active Resource models
########################################################################
class SchemaTest < ActiveModel::TestCase
def setup
setup_response # find me in abstract_unit
end
def teardown
Person.schema = nil # hack to stop test bleedthrough...
end
#####################################################
# Passing in a schema directly and returning it
####
test "schema on a new model should be empty" do
assert Person.schema.blank?, "should have a blank class schema"
assert Person.new.schema.blank?, "should have a blank instance schema"
end
test "schema should only accept a hash" do
["blahblah", ['one','two'], [:age, :name], Person.new].each do |bad_schema|
assert_raises(ArgumentError,"should only accept a hash (or nil), but accepted: #{bad_schema.inspect}") do
Person.schema = bad_schema
end
end
end
test "schema should accept a simple hash" do
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
assert_nothing_raised { Person.schema = new_schema }
assert_equal new_schema, Person.schema
end
test "schema should accept a hash with simple values" do
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
assert_nothing_raised { Person.schema = new_schema }
assert_equal new_schema, Person.schema
end
test "schema should accept all known attribute types as values" do
# I'd prefer to use here...
ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
assert_nothing_raised("should have accepted #{the_type.inspect}"){ Person.schema = {'my_key' => the_type }}
end
end
test "schema should not accept unknown values" do
bad_values = [ :oogle, :blob, 'thing']
bad_values.each do |bad_value|
assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do
Person.schema = {'key' => bad_value}
end
end
end
test "schema should accept nil and remove the schema" do
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
assert_nothing_raised { Person.schema = new_schema }
assert_equal new_schema, Person.schema # sanity check
assert_nothing_raised { Person.schema = nil }
assert_nil Person.schema, "should have nulled out the schema, but still had: #{Person.schema.inspect}"
end
test "schema should be with indifferent access" do
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
new_schema_syms = new_schema.keys
assert_nothing_raised { Person.schema = new_schema }
new_schema_syms.each do |col|
assert Person.new.respond_to?(col.to_s), "should respond to the schema's string key, but failed on: #{col.to_s}"
assert Person.new.respond_to?(col.to_sym), "should respond to the schema's symbol key, but failed on: #{col.to_sym}"
end
end
test "schema on a fetched resource should return all the attributes of that model instance" do
p = Person.find(1)
s = p.schema
assert s.present?, "should have found a non-empty schema!"
p.attributes.each do |the_attr, val|
assert s.has_key?(the_attr), "should have found attr: #{the_attr} in schema, but only had: #{s.inspect}"
end
end
test "with two instances, default schema should match the attributes of the individual instances - even if they differ" do
matz = Person.find(1)
rick = Person.find(6)
m_attrs = matz.attributes.keys.sort
r_attrs = rick.attributes.keys.sort
assert_not_equal m_attrs, r_attrs, "should have different attributes on each model"
assert_not_equal matz.schema, rick.schema, "should have had different schemas too"
end
test "defining a schema should return it when asked" do
assert Person.schema.blank?, "should have a blank class schema"
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
assert_nothing_raised {
Person.schema = new_schema
assert_equal new_schema, Person.schema, "should have saved the schema on the class"
assert_equal new_schema, Person.new.schema, "should have made the schema available to every instance"
}
end
test "defining a schema, then fetching a model should still match the defined schema" do
# sanity checks
assert Person.schema.blank?, "should have a blank class schema"
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
matz = Person.find(1)
assert !matz.schema.blank?, "should have some sort of schema on an instance variable"
assert_not_equal new_schema, matz.schema, "should not have the class-level schema until it's been added to the class!"
assert_nothing_raised {
Person.schema = new_schema
assert_equal new_schema, matz.schema, "class-level schema should override instance-level schema"
}
end
#####################################################
# Using the schema syntax
####
test "should be able to use schema" do
assert_respond_to Person, :schema, "should at least respond to the schema method"
assert_nothing_raised("Should allow the schema to take a block") do
Person.schema { }
end
end
test "schema definition should store and return attribute set" do
assert_nothing_raised do
s = nil
Person.schema do
s = self
attribute :foo, :string
end
assert_respond_to s, :attrs, "should return attributes in theory"
assert_equal({'foo' => 'string' }, s.attrs, "should return attributes in practice")
end
end
test "should be able to add attributes through schema" do
assert_nothing_raised do
s = nil
Person.schema do
s = self
attribute('foo', 'string')
end
assert s.attrs.has_key?('foo'), "should have saved the attribute name"
assert_equal 'string', s.attrs['foo'], "should have saved the attribute type"
end
end
test "should convert symbol attributes to strings" do
assert_nothing_raised do
s = nil
Person.schema do
attribute(:foo, :integer)
s = self
end
assert s.attrs.has_key?('foo'), "should have saved the attribute name as a string"
assert_equal 'integer', s.attrs['foo'], "should have saved the attribute type as a string"
end
end
test "should be able to add all known attribute types" do
assert_nothing_raised do
ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
s = nil
Person.schema do
s = self
attribute('foo', the_type)
end
assert s.attrs.has_key?('foo'), "should have saved the attribute name"
assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}"
end
end
end
test "attributes should not accept unknown values" do
bad_values = [ :oogle, :blob, 'thing']
bad_values.each do |bad_value|
s = nil
assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do
Person.schema do
s = self
attribute 'key', bad_value
end
end
assert !self.respond_to?(bad_value), "should only respond to a known attribute type, but accepted: #{bad_value.inspect}"
assert_raises(NoMethodError,"should only have methods for known attribute types, but accepted: #{bad_value.inspect}") do
Person.schema do
send bad_value, 'key'
end
end
end
end
test "should accept attribute types as the type's name as the method" do
ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
s = nil
Person.schema do
s = self
send(the_type,'foo')
end
assert s.attrs.has_key?('foo'), "should now have saved the attribute name"
assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}"
end
end
test "should accept multiple attribute names for an attribute method" do
names = ['foo','bar','baz']
s = nil
Person.schema do
s = self
string(*names)
end
names.each do |the_name|
assert s.attrs.has_key?(the_name), "should now have saved the attribute name: #{the_name}"
assert_equal 'string', s.attrs[the_name], "should have saved the attribute as a string"
end
end
#####################################################
# What a schema does for us
####
# respond_to?
test "should respond positively to attributes that are only in the schema" do
new_attr_name = :my_new_schema_attribute
new_attr_name_two = :another_new_schema_attribute
assert Person.schema.blank?, "sanity check - should have a blank class schema"
assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
assert_nothing_raised do
Person.schema = {new_attr_name.to_s => 'string'}
Person.schema { string new_attr_name_two }
end
assert_respond_to Person.new, new_attr_name, "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}"
assert_respond_to Person.new, new_attr_name_two, "should respond to the attribute from the schema, but failed on: #{new_attr_name_two}"
end
test "should not care about ordering of schema definitions" do
new_attr_name = :my_new_schema_attribute
new_attr_name_two = :another_new_schema_attribute
assert Person.schema.blank?, "sanity check - should have a blank class schema"
assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
assert_nothing_raised do
Person.schema { string new_attr_name_two }
Person.schema = {new_attr_name.to_s => 'string'}
end
assert_respond_to Person.new, new_attr_name, "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}"
assert_respond_to Person.new, new_attr_name_two, "should respond to the attribute from the schema, but failed on: #{new_attr_name_two}"
end
# method_missing effects
test "should not give method_missing for attribute only in schema" do
new_attr_name = :another_new_schema_attribute
new_attr_name_two = :another_new_schema_attribute
assert Person.schema.blank?, "sanity check - should have a blank class schema"
assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name} as a method") do
Person.new.send(new_attr_name)
end
assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name_two} as a method") do
Person.new.send(new_attr_name_two)
end
Person.schema = {new_attr_name.to_s => :float}
Person.schema { string new_attr_name_two }
assert_nothing_raised do
Person.new.send(new_attr_name)
Person.new.send(new_attr_name_two)
end
end
########
# Known attributes
#
# Attributes can be known to be attributes even if they aren't actually
# 'set' on a particular instance.
# This will only differ from 'attributes' if a schema has been set.
test "new model should have no known attributes" do
assert Person.known_attributes.blank?, "should have no known attributes"
assert Person.new.known_attributes.blank?, "should have no known attributes on a new instance"
end
test "setting schema should set known attributes on class and instance" do
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
assert_nothing_raised { Person.schema = new_schema }
assert_equal new_schema.keys.sort, Person.known_attributes.sort
assert_equal new_schema.keys.sort, Person.new.known_attributes.sort
end
test "known attributes on a fetched resource should return all the attributes of the instance" do
p = Person.find(1)
attrs = p.known_attributes
assert attrs.present?, "should have found some attributes!"
p.attributes.each do |the_attr, val|
assert attrs.include?(the_attr), "should have found attr: #{the_attr} in known attributes, but only had: #{attrs.inspect}"
end
end
test "with two instances, known attributes should match the attributes of the individual instances - even if they differ" do
matz = Person.find(1)
rick = Person.find(6)
m_attrs = matz.attributes.keys.sort
r_attrs = rick.attributes.keys.sort
assert_not_equal m_attrs, r_attrs, "should have different attributes on each model"
assert_not_equal matz.known_attributes, rick.known_attributes, "should have had different known attributes too"
end
test "setting schema then fetching should add schema attributes to the instance attributes" do
# an attribute in common with fetched instance and one that isn't
new_schema = {'age' => 'integer', 'name' => 'string',
'height' => 'float', 'bio' => 'text',
'weight' => 'decimal', 'photo' => 'binary',
'alive' => 'boolean', 'created_at' => 'timestamp',
'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
assert_nothing_raised { Person.schema = new_schema }
matz = Person.find(1)
known_attrs = matz.known_attributes
matz.attributes.keys.each do |the_attr|
assert known_attrs.include?(the_attr), "should have found instance attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}"
end
new_schema.keys.each do |the_attr|
assert known_attrs.include?(the_attr), "should have found schema attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}"
end
end
end

View File

@ -1,137 +0,0 @@
require 'abstract_unit'
require "fixtures/person"
class BaseErrorsTest < ActiveSupport::TestCase
def setup
ActiveResource::HttpMock.respond_to do |mock|
mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {'Content-Type' => 'application/xml; charset=utf-8'}
mock.post "/people.json", {}, %q({"errors":{"age":["can't be blank"],"name":["can't be blank", "must start with a letter"],"person":["quota full for today."]}}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
end
end
def test_should_mark_as_invalid
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
assert !@person.valid?
end
end
end
def test_should_parse_json_and_xml_errors
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
assert_kind_of ActiveResource::Errors, @person.errors
assert_equal 4, @person.errors.size
end
end
end
def test_should_parse_json_errors_when_no_errors_key
ActiveResource::HttpMock.respond_to do |mock|
mock.post "/people.json", {}, '{}', 422, {'Content-Type' => 'application/json; charset=utf-8'}
end
invalid_user_using_format(:json) do
assert_kind_of ActiveResource::Errors, @person.errors
assert_equal 0, @person.errors.size
end
end
def test_should_parse_errors_to_individual_attributes
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
assert @person.errors[:name].any?
assert_equal ["can't be blank"], @person.errors[:age]
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
assert_equal ["Person quota full for today."], @person.errors[:base]
end
end
end
def test_should_iterate_over_errors
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
errors = []
@person.errors.each { |attribute, message| errors << [attribute, message] }
assert errors.include?([:name, "can't be blank"])
end
end
end
def test_should_iterate_over_full_errors
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
errors = []
@person.errors.to_a.each { |message| errors << message }
assert errors.include?("Name can't be blank")
end
end
end
def test_should_format_full_errors
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
full = @person.errors.full_messages
assert full.include?("Age can't be blank")
assert full.include?("Name can't be blank")
assert full.include?("Name must start with a letter")
assert full.include?("Person quota full for today.")
end
end
end
def test_should_mark_as_invalid_when_content_type_is_unavailable_in_response_header
ActiveResource::HttpMock.respond_to do |mock|
mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {}
mock.post "/people.json", {}, %q({"errors":{"age":["can't be blank"],"name":["can't be blank", "must start with a letter"],"person":["quota full for today."]}}), 422, {}
end
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
assert !@person.valid?
end
end
end
def test_should_parse_json_string_errors_with_an_errors_key
ActiveResource::HttpMock.respond_to do |mock|
mock.post "/people.json", {}, %q({"errors":["Age can't be blank", "Name can't be blank", "Name must start with a letter", "Person quota full for today."]}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
end
assert_deprecated(/as an array/) do
invalid_user_using_format(:json) do
assert @person.errors[:name].any?
assert_equal ["can't be blank"], @person.errors[:age]
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
assert_equal ["Person quota full for today."], @person.errors[:base]
end
end
end
def test_should_parse_3_1_style_json_errors
ActiveResource::HttpMock.respond_to do |mock|
mock.post "/people.json", {}, %q({"age":["can't be blank"],"name":["can't be blank", "must start with a letter"],"person":["quota full for today."]}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
end
assert_deprecated(/without a root/) do
invalid_user_using_format(:json) do
assert @person.errors[:name].any?
assert_equal ["can't be blank"], @person.errors[:age]
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
assert_equal ["Person quota full for today."], @person.errors[:base]
end
end
end
private
def invalid_user_using_format(mime_type_reference)
previous_format = Person.format
Person.format = mime_type_reference
@person = Person.new(:name => '', :age => '')
assert_equal false, @person.save
yield
ensure
Person.format = previous_format
end
end

File diff suppressed because it is too large Load Diff

View File

@ -1,275 +0,0 @@
require 'abstract_unit'
class ConnectionTest < ActiveSupport::TestCase
ResponseCodeStub = Struct.new(:code)
RedirectResponseStub = Struct.new(:code, :Location)
def setup
@conn = ActiveResource::Connection.new('http://localhost')
matz = { :person => { :id => 1, :name => 'Matz' } }
david = { :person => { :id => 2, :name => 'David' } }
@people = { :people => [ matz, david ] }.to_json
@people_single = { 'people-single-elements' => [ matz ] }.to_json
@people_empty = { 'people-empty-elements' => [ ] }.to_json
@matz = matz.to_json
@david = david.to_json
@header = { 'key' => 'value' }.freeze
@default_request_headers = { 'Content-Type' => 'application/json' }
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/2.json", @header, @david
mock.get "/people.json", {}, @people
mock.get "/people_single_elements.json", {}, @people_single
mock.get "/people_empty_elements.json", {}, @people_empty
mock.get "/people/1.json", {}, @matz
mock.put "/people/1.json", {}, nil, 204
mock.put "/people/2.json", {}, @header, 204
mock.delete "/people/1.json", {}, nil, 200
mock.delete "/people/2.json", @header, nil, 200
mock.post "/people.json", {}, nil, 201, 'Location' => '/people/5.json'
mock.post "/members.json", {}, @header, 201, 'Location' => '/people/6.json'
mock.head "/people/1.json", {}, nil, 200
end
end
def test_handle_response
# 2xx and 3xx are valid responses.
[200, 299, 300, 399].each do |code|
expected = ResponseCodeStub.new(code)
assert_equal expected, handle_response(expected)
end
# 301 is moved permanently (redirect)
assert_redirect_raises 301
# 302 is found (redirect)
assert_redirect_raises 302
# 303 is see other (redirect)
assert_redirect_raises 303
# 307 is temporary redirect
assert_redirect_raises 307
# 400 is a bad request (e.g. malformed URI or missing request parameter)
assert_response_raises ActiveResource::BadRequest, 400
# 401 is an unauthorized request
assert_response_raises ActiveResource::UnauthorizedAccess, 401
# 403 is a forbidden request (and authorizing will not help)
assert_response_raises ActiveResource::ForbiddenAccess, 403
# 404 is a missing resource.
assert_response_raises ActiveResource::ResourceNotFound, 404
# 405 is a method not allowed error
assert_response_raises ActiveResource::MethodNotAllowed, 405
# 409 is an optimistic locking error
assert_response_raises ActiveResource::ResourceConflict, 409
# 410 is a removed resource
assert_response_raises ActiveResource::ResourceGone, 410
# 422 is a validation error
assert_response_raises ActiveResource::ResourceInvalid, 422
# 4xx are client errors.
[402, 499].each do |code|
assert_response_raises ActiveResource::ClientError, code
end
# 5xx are server errors.
[500, 599].each do |code|
assert_response_raises ActiveResource::ServerError, code
end
# Others are unknown.
[199, 600].each do |code|
assert_response_raises ActiveResource::ConnectionError, code
end
end
ResponseHeaderStub = Struct.new(:code, :message, 'Allow')
def test_should_return_allowed_methods_for_method_no_allowed_exception
begin
handle_response ResponseHeaderStub.new(405, "HTTP Failed...", "GET, POST")
rescue ActiveResource::MethodNotAllowed => e
assert_equal "Failed. Response code = 405. Response message = HTTP Failed....", e.message
assert_equal [:get, :post], e.allowed_methods
end
end
def test_initialize_raises_argument_error_on_missing_site
assert_raise(ArgumentError) { ActiveResource::Connection.new(nil) }
end
def test_site_accessor_accepts_uri_or_string_argument
site = URI.parse("http://localhost")
assert_raise(URI::InvalidURIError) { @conn.site = nil }
assert_nothing_raised { @conn.site = "http://localhost" }
assert_equal site, @conn.site
assert_nothing_raised { @conn.site = site }
assert_equal site, @conn.site
end
def test_proxy_accessor_accepts_uri_or_string_argument
proxy = URI.parse("http://proxy_user:proxy_password@proxy.local:4242")
assert_nothing_raised { @conn.proxy = "http://proxy_user:proxy_password@proxy.local:4242" }
assert_equal proxy, @conn.proxy
assert_nothing_raised { @conn.proxy = proxy }
assert_equal proxy, @conn.proxy
end
def test_timeout_accessor
@conn.timeout = 5
assert_equal 5, @conn.timeout
end
def test_get
matz = decode(@conn.get("/people/1.json"))
assert_equal "Matz", matz["name"]
end
def test_head
response = @conn.head("/people/1.json")
assert response.body.blank?
assert_equal 200, response.code
end
def test_get_with_header
david = decode(@conn.get("/people/2.json", @header))
assert_equal "David", david["name"]
end
def test_get_collection
people = decode(@conn.get("/people.json"))
assert_equal "Matz", people[0]["person"]["name"]
assert_equal "David", people[1]["person"]["name"]
end
def test_get_collection_single
people = decode(@conn.get("/people_single_elements.json"))
assert_equal "Matz", people[0]["person"]["name"]
end
def test_get_collection_empty
people = decode(@conn.get("/people_empty_elements.json"))
assert_equal [], people
end
def test_post
response = @conn.post("/people.json")
assert_equal "/people/5.json", response["Location"]
end
def test_post_with_header
response = @conn.post("/members.json", @header)
assert_equal "/people/6.json", response["Location"]
end
def test_put
response = @conn.put("/people/1.json")
assert_equal 204, response.code
end
def test_put_with_header
response = @conn.put("/people/2.json", @header)
assert_equal 204, response.code
end
def test_delete
response = @conn.delete("/people/1.json")
assert_equal 200, response.code
end
def test_delete_with_header
response = @conn.delete("/people/2.json", @header)
assert_equal 200, response.code
end
def test_timeout
@http = mock('new Net::HTTP')
@conn.expects(:http).returns(@http)
@http.expects(:get).raises(Timeout::Error, 'execution expired')
assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.json') }
end
def test_setting_timeout
http = Net::HTTP.new('')
[10, 20].each do |timeout|
@conn.timeout = timeout
@conn.send(:configure_http, http)
assert_equal timeout, http.open_timeout
assert_equal timeout, http.read_timeout
end
end
def test_accept_http_header
@http = mock('new Net::HTTP')
@conn.expects(:http).returns(@http)
path = '/people/1.xml'
@http.expects(:get).with(path, { 'Accept' => 'application/xhtml+xml' }).returns(ActiveResource::Response.new(@matz, 200, { 'Content-Type' => 'text/xhtml' }))
assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, { 'Accept' => 'application/xhtml+xml' }) }
end
def test_ssl_options_get_applied_to_http
http = Net::HTTP.new('')
@conn.site="https://secure"
@conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER}
@conn.send(:configure_http, http)
assert http.use_ssl?
assert_equal http.verify_mode, OpenSSL::SSL::VERIFY_PEER
end
def test_ssl_error
http = Net::HTTP.new('')
@conn.expects(:http).returns(http)
http.expects(:get).raises(OpenSSL::SSL::SSLError, 'Expired certificate')
assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.json') }
end
def test_auth_type_can_be_string
@conn.auth_type = 'digest'
assert_equal(:digest, @conn.auth_type)
end
def test_auth_type_defaults_to_basic
@conn.auth_type = nil
assert_equal(:basic, @conn.auth_type)
end
def test_auth_type_ignores_nonsensical_values
@conn.auth_type = :wibble
assert_equal(:basic, @conn.auth_type)
end
protected
def assert_response_raises(klass, code)
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
handle_response ResponseCodeStub.new(code)
end
end
def assert_redirect_raises(code)
assert_raise(ActiveResource::Redirection, "Expected response code #{code} to raise ActiveResource::Redirection") do
handle_response RedirectResponseStub.new(code, 'http://example.com/')
end
end
def handle_response(response)
@conn.__send__(:handle_response, response)
end
def decode(response)
@conn.format.decode(response.body)
end
end

View File

@ -1,139 +0,0 @@
require 'abstract_unit'
require "fixtures/person"
require "fixtures/customer"
require "fixtures/street_address"
require "fixtures/beast"
require "fixtures/proxy"
require 'active_support/core_ext/hash/conversions'
class FinderTest < ActiveSupport::TestCase
def setup
setup_response # find me in abstract_unit
end
def test_find_by_id
matz = Person.find(1)
assert_kind_of Person, matz
assert_equal "Matz", matz.name
assert matz.name?
end
def test_find_by_id_with_custom_prefix
addy = StreetAddress.find(1, :params => { :person_id => 1 })
assert_kind_of StreetAddress, addy
assert_equal '12345 Street', addy.street
end
def test_find_all
all = Person.find(:all)
assert_equal 2, all.size
assert_kind_of Person, all.first
assert_equal "Matz", all.first.name
assert_equal "David", all.last.name
end
def test_all
all = Person.all
assert_equal 2, all.size
assert_kind_of Person, all.first
assert_equal "Matz", all.first.name
assert_equal "David", all.last.name
end
def test_all_with_params
all = StreetAddress.all(:params => { :person_id => 1 })
assert_equal 1, all.size
assert_kind_of StreetAddress, all.first
end
def test_find_first
matz = Person.find(:first)
assert_kind_of Person, matz
assert_equal "Matz", matz.name
end
def test_first
matz = Person.first
assert_kind_of Person, matz
assert_equal "Matz", matz.name
end
def test_first_with_params
addy = StreetAddress.first(:params => { :person_id => 1 })
assert_kind_of StreetAddress, addy
assert_equal '12345 Street', addy.street
end
def test_find_last
david = Person.find(:last)
assert_kind_of Person, david
assert_equal 'David', david.name
end
def test_last
david = Person.last
assert_kind_of Person, david
assert_equal 'David', david.name
end
def test_last_with_params
addy = StreetAddress.last(:params => { :person_id => 1 })
assert_kind_of StreetAddress, addy
assert_equal '12345 Street', addy.street
end
def test_find_by_id_not_found
assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) }
assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(99, :params => {:person_id => 1}) }
end
def test_find_all_sub_objects
all = StreetAddress.find(:all, :params => { :person_id => 1 })
assert_equal 1, all.size
assert_kind_of StreetAddress, all.first
end
def test_find_all_sub_objects_not_found
assert_nothing_raised do
StreetAddress.find(:all, :params => { :person_id => 2 })
end
end
def test_find_all_by_from
ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.json", {}, @people_david }
people = Person.find(:all, :from => "/companies/1/people.json")
assert_equal 1, people.size
assert_equal "David", people.first.name
end
def test_find_all_by_from_with_options
ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.json", {}, @people_david }
people = Person.find(:all, :from => "/companies/1/people.json")
assert_equal 1, people.size
assert_equal "David", people.first.name
end
def test_find_all_by_symbol_from
ActiveResource::HttpMock.respond_to { |m| m.get "/people/managers.json", {}, @people_david }
people = Person.find(:all, :from => :managers)
assert_equal 1, people.size
assert_equal "David", people.first.name
end
def test_find_single_by_from
ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/manager.json", {}, @david }
david = Person.find(:one, :from => "/companies/1/manager.json")
assert_equal "David", david.name
end
def test_find_single_by_symbol_from
ActiveResource::HttpMock.respond_to { |m| m.get "/people/leader.json", {}, @david }
david = Person.find(:one, :from => :leader)
assert_equal "David", david.name
end
end

View File

@ -1,118 +0,0 @@
require 'abstract_unit'
require "fixtures/person"
require "fixtures/street_address"
class FormatTest < ActiveSupport::TestCase
def setup
@matz = { :id => 1, :name => 'Matz' }
@david = { :id => 2, :name => 'David' }
@programmers = [ @matz, @david ]
end
def test_http_format_header_name
[:get, :head].each do |verb|
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
assert_equal 'Accept', header_name
end
[:patch, :put, :post].each do |verb|
header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[verb]
assert_equal 'Content-Type', header_name
end
end
def test_formats_on_single_element
[ :json, :xml ].each do |format|
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david)
assert_equal @david[:name], Person.find(1).name
end
end
end
def test_formats_on_collection
[ :json, :xml ].each do |format|
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.get "/people.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@programmers)
remote_programmers = Person.find(:all)
assert_equal 2, remote_programmers.size
assert remote_programmers.find { |p| p.name == 'David' }
end
end
end
def test_formats_on_custom_collection_method
[ :json, :xml ].each do |format|
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode([@david])
remote_programmers = Person.get(:retrieve, :name => 'David')
assert_equal 1, remote_programmers.size
assert_equal @david[:id], remote_programmers[0]['id']
assert_equal @david[:name], remote_programmers[0]['name']
end
end
end
def test_formats_on_custom_element_method
[:json, :xml].each do |format|
using_format(Person, format) do
david = (format == :json ? { :person => @david } : @david)
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/2.#{format}", { 'Accept' => ActiveResource::Formats[format].mime_type }, ActiveResource::Formats[format].encode(david)
mock.get "/people/2/shallow.#{format}", { 'Accept' => ActiveResource::Formats[format].mime_type }, ActiveResource::Formats[format].encode(david)
end
remote_programmer = Person.find(2).get(:shallow)
assert_equal @david[:id], remote_programmer['id']
assert_equal @david[:name], remote_programmer['name']
end
ryan_hash = { :name => 'Ryan' }
ryan_hash = (format == :json ? { :person => ryan_hash } : ryan_hash)
ryan = ActiveResource::Formats[format].encode(ryan_hash)
using_format(Person, format) do
remote_ryan = Person.new(:name => 'Ryan')
ActiveResource::HttpMock.respond_to.post "/people.#{format}", { 'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, { 'Location' => "/people/5.#{format}" }
remote_ryan.save
remote_ryan = Person.new(:name => 'Ryan')
ActiveResource::HttpMock.respond_to.post "/people/new/register.#{format}", { 'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, { 'Location' => "/people/5.#{format}" }
assert_equal ActiveResource::Response.new(ryan, 201, { 'Location' => "/people/5.#{format}" }), remote_ryan.post(:register)
end
end
end
def test_setting_format_before_site
resource = Class.new(ActiveResource::Base)
resource.format = :json
resource.site = 'http://37s.sunrise.i:3000'
assert_equal ActiveResource::Formats[:json], resource.connection.format
end
def test_serialization_of_nested_resource
address = { :street => '12345 Street' }
person = { :name => 'Rus', :address => address}
[:json, :xml].each do |format|
encoded_person = ActiveResource::Formats[format].encode(person)
assert_match(/12345 Street/, encoded_person)
remote_person = Person.new(person.update({:address => StreetAddress.new(address)}))
assert_kind_of StreetAddress, remote_person.address
using_format(Person, format) do
ActiveResource::HttpMock.respond_to.post "/people.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, encoded_person, 201, {'Location' => "/people/5.#{format}"}
remote_person.save
end
end
end
private
def using_format(klass, mime_type_reference)
previous_format = klass.format
klass.format = mime_type_reference
yield
ensure
klass.format = previous_format
end
end

View File

@ -1,202 +0,0 @@
require 'abstract_unit'
require 'active_support/core_ext/object/inclusion'
class HttpMockTest < ActiveSupport::TestCase
setup do
@http = ActiveResource::HttpMock.new("http://example.com")
end
FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES
[:post, :patch, :put, :get, :delete, :head].each do |method|
test "responds to simple #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Response")
end
assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
end
test "adds format header by default to #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", {}, "Response")
end
assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
end
test "respond only when headers match header by default to #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", {"X-Header" => "X"}, "Response")
end
assert_equal "Response", request(method, "/people/1", "X-Header" => "X").body
assert_raise(ActiveResource::InvalidRequestError) { request(method, "/people/1") }
end
test "does not overwrite format header to #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/json"}, "Response")
end
assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
end
test "ignores format header when there is only one response to same url in a #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", {}, "Response")
end
assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
end
test "responds correctly when format header is given to #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/xml" }, "XML")
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Json")
end
assert_equal "XML", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
assert_equal "Json", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
end
test "raises InvalidRequestError if no response found for the #{method} request" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "json")
end
assert_raise(::ActiveResource::InvalidRequestError) do
request(method, "/people/1", FORMAT_HEADER[method] => "application/xml")
end
end
end
test "allows you to send in pairs directly to the respond_to method" do
matz = { :person => { :id => 1, :name => "Matz" } }.to_json
create_matz = ActiveResource::Request.new(:post, '/people.json', matz, {})
created_response = ActiveResource::Response.new("", 201, { "Location" => "/people/1.json" })
get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
ok_response = ActiveResource::Response.new(matz, 200, {})
pairs = {create_matz => created_response, get_matz => ok_response}
ActiveResource::HttpMock.respond_to(pairs)
assert_equal 2, ActiveResource::HttpMock.responses.length
assert_equal "", ActiveResource::HttpMock.responses.assoc(create_matz)[1].body
assert_equal matz, ActiveResource::HttpMock.responses.assoc(get_matz)[1].body
end
test "resets all mocked responses on each call to respond_to with a block by default" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/2", {}, "JSON2")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
end
test "resets all mocked responses on each call to respond_to by passing pairs by default" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
matz = { :person => { :id => 1, :name => "Matz" } }.to_json
get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
ok_response = ActiveResource::Response.new(matz, 200, {})
ActiveResource::HttpMock.respond_to({get_matz => ok_response})
assert_equal 1, ActiveResource::HttpMock.responses.length
end
test "allows you to add new responses to the existing responses by calling a block" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
ActiveResource::HttpMock.respond_to(false) do |mock|
mock.send(:get, "/people/2", {}, "JSON2")
end
assert_equal 2, ActiveResource::HttpMock.responses.length
end
test "allows you to add new responses to the existing responses by passing pairs" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
matz = { :person => { :id => 1, :name => "Matz" } }.to_json
get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
ok_response = ActiveResource::Response.new(matz, 200, {})
ActiveResource::HttpMock.respond_to({get_matz => ok_response}, false)
assert_equal 2, ActiveResource::HttpMock.responses.length
end
test "allows you to replace the existing reponse with the same request by calling a block" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
ActiveResource::HttpMock.respond_to(false) do |mock|
mock.send(:get, "/people/1", {}, "JSON2")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
end
test "allows you to replace the existing reponse with the same request by passing pairs" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
matz = { :person => { :id => 1, :name => "Matz" } }.to_json
get_matz = ActiveResource::Request.new(:get, '/people/1', nil)
ok_response = ActiveResource::Response.new(matz, 200, {})
ActiveResource::HttpMock.respond_to({get_matz => ok_response}, false)
assert_equal 1, ActiveResource::HttpMock.responses.length
end
test "do not replace the response with the same path but different method by calling a block" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
ActiveResource::HttpMock.respond_to(false) do |mock|
mock.send(:put, "/people/1", {}, "JSON2")
end
assert_equal 2, ActiveResource::HttpMock.responses.length
end
test "do not replace the response with the same path but different method by passing pairs" do
ActiveResource::HttpMock.respond_to do |mock|
mock.send(:get, "/people/1", {}, "JSON1")
end
assert_equal 1, ActiveResource::HttpMock.responses.length
put_matz = ActiveResource::Request.new(:put, '/people/1', nil)
ok_response = ActiveResource::Response.new("", 200, {})
ActiveResource::HttpMock.respond_to({put_matz => ok_response}, false)
assert_equal 2, ActiveResource::HttpMock.responses.length
end
def request(method, path, headers = {}, body = nil)
if method.in?([:patch, :put, :post])
@http.send(method, path, body, headers)
else
@http.send(method, path, headers)
end
end
end

View File

@ -1,32 +0,0 @@
require "abstract_unit"
require "fixtures/person"
require "active_support/log_subscriber/test_helper"
require "active_resource/log_subscriber"
require "active_support/core_ext/hash/conversions"
class LogSubscriberTest < ActiveSupport::TestCase
include ActiveSupport::LogSubscriber::TestHelper
def setup
super
@matz = { :person => { :id => 1, :name => 'Matz' } }.to_json
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.json", {}, @matz
end
ActiveResource::LogSubscriber.attach_to :active_resource
end
def set_logger(logger)
ActiveResource::Base.logger = logger
end
def test_request_notification
Person.find(1)
wait
assert_equal 2, @logger.logged(:info).size
assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0]
assert_match(/\-\-\> 200 200 33/, @logger.logged(:info)[1])
end
end

View File

@ -1,55 +0,0 @@
require 'abstract_unit'
require 'fixtures/person'
require 'active_support/core_ext/hash/conversions'
class ObservingTest < ActiveSupport::TestCase
cattr_accessor :history
class PersonObserver < ActiveModel::Observer
observe :person
%w( after_create after_destroy after_save after_update
before_create before_destroy before_save before_update).each do |method|
define_method(method) { |*| log method }
end
private
def log(method)
(ObservingTest.history ||= []) << method.to_sym
end
end
def setup
@matz = { 'person' => { :id => 1, :name => 'Matz' } }.to_json
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.json", {}, @matz
mock.post "/people.json", {}, @matz, 201, 'Location' => '/people/1.json'
mock.put "/people/1.json", {}, nil, 204
mock.delete "/people/1.json", {}, nil, 200
end
PersonObserver.instance
end
def teardown
self.history = nil
end
def test_create_fires_save_and_create_notifications
Person.create(:name => 'Rick')
assert_equal [:before_save, :before_create, :after_create, :after_save], self.history
end
def test_update_fires_save_and_update_notifications
person = Person.find(1)
person.save
assert_equal [:before_save, :before_update, :after_update, :after_save], self.history
end
def test_destroy_fires_destroy_notifications
person = Person.find(1)
person.destroy
assert_equal [:before_destroy, :after_destroy], self.history
end
end

View File

@ -1,67 +0,0 @@
require 'abstract_unit'
require 'fixtures/project'
require 'active_support/core_ext/hash/conversions'
# The validations are tested thoroughly under ActiveModel::Validations
# This test case simply makes sure that they are all accessible by
# Active Resource objects.
class ValidationsTest < ActiveModel::TestCase
VALID_PROJECT_HASH = { :name => "My Project", :description => "A project" }
def setup
@my_proj = { "person" => VALID_PROJECT_HASH }.to_json
ActiveResource::HttpMock.respond_to do |mock|
mock.post "/projects.json", {}, @my_proj, 201, 'Location' => '/projects/5.json'
end
end
def test_validates_presence_of
p = new_project(:name => nil)
assert !p.valid?, "should not be a valid record without name"
assert !p.save, "should not have saved an invalid record"
assert_equal ["can't be blank"], p.errors[:name], "should have an error on name"
p.name = "something"
assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
end
def test_fails_save!
p = new_project(:name => nil)
assert_raise(ActiveResource::ResourceInvalid) { p.save! }
end
def test_save_without_validation
p = new_project(:name => nil)
assert !p.save
assert p.save(:validate => false)
end
def test_validate_callback
# we have a callback ensuring the description is longer than three letters
p = new_project(:description => 'a')
assert !p.valid?, "should not be a valid record when it fails a validation callback"
assert !p.save, "should not have saved an invalid record"
assert_equal ["must be greater than three letters long"], p.errors[:description], "should be an error on description"
# should now allow this description
p.description = 'abcd'
assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
end
def test_client_side_validation_maximum
project = Project.new(:description => '123456789012345')
assert ! project.valid?
assert_equal ['is too long (maximum is 10 characters)'], project.errors[:description]
end
protected
# quickie helper to create a new project with all the required
# attributes.
# Pass in any params you specifically want to override
def new_project(opts = {})
Project.new(VALID_PROJECT_HASH.merge(opts))
end
end

View File

@ -1,19 +0,0 @@
# turns everything into the same object
class AddressXMLFormatter
include ActiveResource::Formats::XmlFormat
def decode(xml)
data = ActiveResource::Formats::XmlFormat.decode(xml)
# process address fields
data.each do |address|
address['city_state'] = "#{address['city']}, #{address['state']}"
end
data
end
end
class AddressResource < ActiveResource::Base
self.element_name = "address"
self.format = AddressXMLFormatter.new
end

View File

@ -1,14 +0,0 @@
class BeastResource < ActiveResource::Base
self.site = 'http://beast.caboo.se'
site.user = 'foo'
site.password = 'bar'
end
class Forum < BeastResource
# taken from BeastResource
# self.site = 'http://beast.caboo.se'
end
class Topic < BeastResource
self.site += '/forums/:forum_id'
end

View File

@ -1,3 +0,0 @@
class Customer < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end

View File

@ -1,3 +0,0 @@
class Person < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end

View File

@ -1,18 +0,0 @@
# used to test validations
class Project < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
schema do
string :email
string :name
end
validates :name, :presence => true
validates :description, :presence => false, :length => {:maximum => 10}
validate :description_greater_than_three_letters
# to test the validate *callback* works
def description_greater_than_three_letters
errors.add :description, 'must be greater than three letters long' if description.length < 3 unless description.blank?
end
end

View File

@ -1,4 +0,0 @@
class ProxyResource < ActiveResource::Base
self.site = "http://localhost"
self.proxy = "http://user:password@proxy.local:3000"
end

View File

@ -1,9 +0,0 @@
module Asset
class Sound < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
end
# to test namespacing in a module
class Author
end

View File

@ -1,4 +0,0 @@
class StreetAddress < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000/people/:person_id"
self.element_name = 'address'
end

View File

@ -1,5 +0,0 @@
class SubscriptionPlan < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
self.element_name = 'plan'
self.primary_key = :code
end

View File

@ -1,26 +0,0 @@
class SetterTrap < ActiveSupport::BasicObject
class << self
def rollback_sets(obj)
trapped = new(obj)
yield(trapped).tap { trapped.rollback_sets }
end
end
def initialize(obj)
@cache = {}
@obj = obj
end
def respond_to?(method)
@obj.respond_to?(method)
end
def method_missing(method, *args, &proc)
@cache[method] ||= @obj.send($`) if method.to_s =~ /=$/
@obj.send method, *args, &proc
end
def rollback_sets
@cache.each { |k, v| @obj.send k, v }
end
end

View File

@ -19,7 +19,6 @@ class Build
'ap' => 'actionpack', 'ap' => 'actionpack',
'am' => 'actionmailer', 'am' => 'actionmailer',
'amo' => 'activemodel', 'amo' => 'activemodel',
'ares' => 'activeresource',
'as' => 'activesupport', 'as' => 'activesupport',
'ar' => 'activerecord' 'ar' => 'activerecord'
} }

View File

@ -1,6 +1,6 @@
version = ARGV.pop version = ARGV.pop
%w( activesupport activemodel activerecord activeresource actionpack actionmailer railties ).each do |framework| %w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework|
puts "Installing #{framework}..." puts "Installing #{framework}..."
`cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem` `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem`
end end

View File

@ -20,7 +20,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version) s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version) s.add_dependency('actionpack', version)
s.add_dependency('activerecord', version) s.add_dependency('activerecord', version)
s.add_dependency('activeresource', version)
s.add_dependency('actionmailer', version) s.add_dependency('actionmailer', version)
s.add_dependency('railties', version) s.add_dependency('railties', version)
s.add_dependency('bundler', '~> 1.1') s.add_dependency('bundler', '~> 1.1')

View File

@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##
* Remove Active Resource from Rails framework. *Prem Sichangrist*
* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. *Piotr Sarnacki* * Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. *Piotr Sarnacki*
Example: Example:

View File

@ -1,120 +0,0 @@
h2. Active Resource Basics
This guide should provide you with all you need to get started managing the connection between business objects and RESTful web services. It implements a way to map web-based resources to local objects with CRUD semantics.
endprologue.
WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
h3. Introduction
Active Resource allows you to connect with RESTful web services. So, in Rails, Resource classes inherited from +ActiveResource::Base+ and live in +app/models+.
h3. Configuration and Usage
Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class
that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
<ruby>
class Person < ActiveResource::Base
self.site = "http://api.people.com:3000/"
end
</ruby>
Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
life cycle methods that operate against a persistent store.
h3. Reading and Writing Data
Active Resource make request over HTTP using a standard JSON format. It mirrors the RESTful routing built into Action Controller but will also work with any other REST service that properly implements the protocol.
h4. Read
Read requests use the GET method and expect the JSON form of whatever resource/resources is/are being requested.
<ruby>
# Find a person with id = 1
person = Person.find(1)
# Check if a person exists with id = 1
Person.exists?(1) # => true
# Get all resources of Person class
Person.all
</ruby>
h4. Create
Creating a new resource submits the JSON form of the resource as the body of the request with HTTP POST method and parse the response into Active Resource object.
<ruby>
person = Person.create(:name => 'Vishnu')
person.id # => 1
</ruby>
h4. Update
To update an existing resource, 'save' method is used. This method make a HTTP PUT request in JSON format.
<ruby>
person = Person.find(1)
person.name = 'Atrai'
person.save
</ruby>
h4. Delete
'destroy' method makes a HTTP DELETE request for an existing resource in JSON format to delete that resource.
<ruby>
person = Person.find(1)
person.destroy
</ruby>
h3. Validations
Module to support validation and errors with Active Resource objects. The module overrides Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned in the web service response. The module also adds an errors collection that mimics the interface of the errors provided by ActiveModel::Errors.
h4. Validating client side resources by overriding validation methods in base class
<ruby>
class Person < ActiveResource::Base
self.site = "http://api.people.com:3000/"
protected
def validate
errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
end
end
</ruby>
h4. Validating client side resources
Consider a Person resource on the server requiring both a first_name and a last_name with a validates_presence_of :first_name, :last_name declaration in the model:
<ruby>
person = Person.new(:first_name => "Jim", :last_name => "")
person.save # => false (server returns an HTTP 422 status code and errors)
person.valid? # => false
person.errors.empty? # => false
person.errors.count # => 1
person.errors.full_messages # => ["Last name can't be empty"]
person.errors[:last_name] # => ["can't be empty"]
person.last_name = "Halpert"
person.save # => true (and person is now saved to the remote service)
</ruby>
h4. Public instance methods
ActiveResource::Validations have three public instance methods
h5. errors()
This will return errors object that holds all information about attribute error messages
h5. save_with_validation(options=nil)
This validates the resource with any local validations written in base class and then it will try to POST if there are no errors.
h5. valid?
Runs all the local validations and will return true if no errors.

View File

@ -374,7 +374,6 @@ Rails version 4.0.0.beta
JavaScript Runtime Node.js (V8) JavaScript Runtime Node.js (V8)
Active Record version 4.0.0.beta Active Record version 4.0.0.beta
Action Pack version 4.0.0.beta Action Pack version 4.0.0.beta
Active Resource version 4.0.0.beta
Action Mailer version 4.0.0.beta Action Mailer version 4.0.0.beta
Active Support version 4.0.0.beta Active Support version 4.0.0.beta
Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport

View File

@ -439,12 +439,6 @@ config.action_mailer.observers = ["MailObserver"]
config.action_mailer.interceptors = ["MailInterceptor"] config.action_mailer.interceptors = ["MailInterceptor"]
</ruby> </ruby>
h4. Configuring Active Resource
There is a single configuration setting available on +config.active_resource+:
* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Resource. Set to +nil+ to disable logging.
h4. Configuring Active Support h4. Configuring Active Support
There are a few configuration options available in Active Support: There are a few configuration options available in Active Support:
@ -620,8 +614,6 @@ The error occurred while evaluating nil.each
*+action_mailer.compile_config_methods+* Initializes methods for the config settings specified so that they are quicker to access. *+action_mailer.compile_config_methods+* Initializes methods for the config settings specified so that they are quicker to access.
*+active_resource.set_configs+* Sets up Active Resource by using the settings in +config.active_resource+ by +send+'ing the method names as setters to +ActiveResource::Base+ and passing the values through.
*+set_load_path+* This initializer runs before +bootstrap_hook+. Adds the +vendor+, +lib+, all directories of +app+ and any paths specified by +config.load_paths+ to +$LOAD_PATH+. *+set_load_path+* This initializer runs before +bootstrap_hook+. Adds the +vendor+, +lib+, all directories of +app+ and any paths specified by +config.load_paths+ to +$LOAD_PATH+.
*+set_autoload_paths+* This initializer runs before +bootstrap_hook+. Adds all sub-directories of +app+ and paths specified by +config.autoload_paths+ to +ActiveSupport::Dependencies.autoload_paths+. *+set_autoload_paths+* This initializer runs before +bootstrap_hook+. Adds all sub-directories of +app+ and paths specified by +config.autoload_paths+ to +ActiveSupport::Dependencies.autoload_paths+.

View File

@ -116,7 +116,6 @@ need to know anything about them to continue with this guide.
* Action Mailer * Action Mailer
* Active Model * Active Model
* Active Record * Active Record
* Active Resource
* Active Support * Active Support
* Railties * Railties
@ -167,12 +166,6 @@ Active Record is the base for the models in a Rails application. It provides
database independence, basic CRUD functionality, advanced finding capabilities, database independence, basic CRUD functionality, advanced finding capabilities,
and the ability to relate models to one another, among other services. and the ability to relate models to one another, among other services.
h5. Active Resource
Active Resource provides a framework for managing the connection between
business objects and RESTful web services. It implements a way to map web-based
resources to local objects with CRUD semantics.
h5. Active Support h5. Active Support
Active Support is an extensive collection of utility classes and standard Ruby Active Support is an extensive collection of utility classes and standard Ruby

View File

@ -159,7 +159,6 @@ In a standard Rails application, there's a +Gemfile+ which declares all dependen
* actionpack (3.1.0.beta) * actionpack (3.1.0.beta)
* activemodel (3.1.0.beta) * activemodel (3.1.0.beta)
* activerecord (3.1.0.beta) * activerecord (3.1.0.beta)
* activeresource (3.1.0.beta)
* activesupport (3.1.0.beta) * activesupport (3.1.0.beta)
* arel (2.0.7) * arel (2.0.7)
* builder (3.0.0) * builder (3.0.0)
@ -491,7 +490,6 @@ require "rails"
active_record active_record
action_controller action_controller
action_mailer action_mailer
active_resource
rails/test_unit rails/test_unit
).each do |framework| ).each do |framework|
begin begin

View File

@ -4,7 +4,6 @@ require "rails"
active_record active_record
action_controller action_controller
action_mailer action_mailer
active_resource
rails/test_unit rails/test_unit
sprockets sprockets
).each do |framework| ).each do |framework|

View File

@ -7,7 +7,6 @@ require 'rails/all'
<%= comment_if :skip_active_record %>require "active_record/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie" require "action_controller/railtie"
require "action_mailer/railtie" require "action_mailer/railtie"
require "active_resource/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%> <% end -%>

View File

@ -7,7 +7,6 @@ require 'rails/all'
<%= comment_if :skip_active_record %>require "active_record/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie" require "action_controller/railtie"
require "action_mailer/railtie" require "action_mailer/railtie"
require "active_resource/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%> <% end -%>

View File

@ -23,7 +23,7 @@ module Rails
end end
def frameworks def frameworks
%w( active_record action_pack active_resource action_mailer active_support ) %w( active_record action_pack action_mailer active_support )
end end
def framework_version(framework) def framework_version(framework)
@ -83,7 +83,7 @@ module Rails
end end
# Versions of each Rails framework (Active Record, Action Pack, # Versions of each Rails framework (Active Record, Action Pack,
# Active Resource, Action Mailer, and Active Support). # Action Mailer, and Active Support).
frameworks.each do |framework| frameworks.each do |framework|
property "#{framework.titlecase} version" do property "#{framework.titlecase} version" do
framework_version(framework) framework_version(framework)

View File

@ -9,7 +9,7 @@ module Rails
# Rails and/or modify the initialization process. # Rails and/or modify the initialization process.
# #
# Every major component of Rails (Action Mailer, Action Controller, # Every major component of Rails (Action Mailer, Action Controller,
# Action View, Active Record and Active Resource) is a Railtie. Each of # Action View and Active Record) is a Railtie. Each of
# them is responsible for their own initialization. This makes Rails itself # them is responsible for their own initialization. This makes Rails itself
# absent of any component hooks, allowing other components to be used in # absent of any component hooks, allowing other components to be used in
# place of any of the Rails defaults. # place of any of the Rails defaults.

View File

@ -83,12 +83,6 @@ namespace :doc do
end end
end end
gem_path('activeresource') do |activeresource|
%w(README.rdoc CHANGELOG.md lib/active_resource.rb lib/active_resource/*).each do |file|
rdoc.rdoc_files.include("#{activeresource}/#{file}")
end
end
gem_path('activesupport') do |activesupport| gem_path('activesupport') do |activesupport|
%w(README.rdoc CHANGELOG.md lib/active_support/**/*.rb).each do |file| %w(README.rdoc CHANGELOG.md lib/active_support/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activesupport}/#{file}") rdoc.rdoc_files.include("#{activesupport}/#{file}")

View File

@ -245,8 +245,7 @@ module TestHelpers
def use_frameworks(arr) def use_frameworks(arr)
to_remove = [:actionmailer, to_remove = [:actionmailer,
:activemodel, :activemodel,
:activerecord, :activerecord] - arr
:activeresource] - arr
if to_remove.include? :activerecord if to_remove.include? :activerecord
remove_from_config "config.active_record.whitelist_attributes = true" remove_from_config "config.active_record.whitelist_attributes = true"
remove_from_config "config.active_record.dependent_restrict_raises = false" remove_from_config "config.active_record.dependent_restrict_raises = false"

View File

@ -1,4 +1,4 @@
FRAMEWORKS = %w( activesupport activemodel activerecord activeresource actionpack actionmailer railties ) FRAMEWORKS = %w( activesupport activemodel activerecord actionpack actionmailer railties )
root = File.expand_path('../../', __FILE__) root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip version = File.read("#{root}/RAILS_VERSION").strip

View File

@ -1,7 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# Example: # Example:
# tools/profile_requires activesupport/lib/active_support.rb # tools/profile_requires activesupport/lib/active_support.rb
# tools/profile_requires activeresource/examples/simple.rb
abort 'Use REE so you can profile memory and object allocation' unless GC.respond_to?(:enable_stats) abort 'Use REE so you can profile memory and object allocation' unless GC.respond_to?(:enable_stats)
ENV['NO_RELOAD'] ||= '1' ENV['NO_RELOAD'] ||= '1'