1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Initial check-in of Active Resourse

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4492 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
David Heinemeier Hansson 2006-06-25 14:44:22 +00:00
parent a55265132b
commit 99d268c853
12 changed files with 543 additions and 0 deletions

1
activeresource/README Normal file
View file

@ -0,0 +1 @@
= Active Resource -- Object-oriented REST services

137
activeresource/Rakefile Normal file
View file

@ -0,0 +1,137 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/rubyforgepublisher'
require File.join(File.dirname(__FILE__), 'lib', 'active_resource', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'activeresource'
PKG_VERSION = ActiveResource::VERSION::STRING + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RELEASE_NAME = "REL #{PKG_VERSION}"
RUBY_FORGE_PROJECT = "activerecord"
RUBY_FORGE_USER = "webster132"
PKG_FILES = FileList[
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
].exclude(/\bCVS\b|~$/)
desc "Default Task"
task :default => [ :test ]
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/*_test.rb'
t.verbose = true
}
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Resource -- Object-oriented REST services"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
rdoc.rdoc_files.include('dev-utils/*.rb')
}
# Create compressed packages
dist_dirs = [ "lib", "test", "examples", "dev-utils" ]
spec = Gem::Specification.new do |s|
s.name = PKG_NAME
s.version = PKG_VERSION
s.summary = "Implements the ActiveRecord pattern for ORM."
s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.}
s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG" ]
dist_dirs.each do |dir|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD)
s.files.delete "test/fixtures/fixture_database.sqlite"
s.files.delete "test/fixtures/fixture_database_2.sqlite"
s.files.delete "test/fixtures/fixture_database.sqlite3"
s.files.delete "test/fixtures/fixture_database_2.sqlite3"
s.require_path = 'lib'
s.autorequire = 'active_record'
s.has_rdoc = true
s.extra_rdoc_files = %w( README )
s.rdoc_options.concat ['--main', 'README']
s.author = "David Heinemeier Hansson"
s.email = "david@loudthinking.com"
s.homepage = "http://www.rubyonrails.org"
s.rubyforge_project = "activerecord"
end
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true
end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
for file_name in FileList["lib/active_record/**/*.rb"]
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 "Publish the beta gem"
task :pgem => [:package] do
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
end
desc "Publish the release files to RubyForge."
task :release => [ :package ] do
`rubyforge login`
for ext in %w( gem tgz zip )
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
puts release_command
system(release_command)
end
end

View file

@ -0,0 +1,38 @@
#--
# Copyright (c) 2006 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.
#++
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
unless defined?(ActiveSupport)
begin
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
require 'active_support'
rescue LoadError
require 'rubygems'
require_gem 'activesupport'
end
end
require 'active_resource/base'
require 'active_resource/struct'

View file

@ -0,0 +1,99 @@
require 'active_resource/connection'
module ActiveResource
class Base
class << self
def site=(site)
@@site = URI.parse(site)
end
def site
@@site
end
def connection(refresh = false)
@connection = Connection.new(site) if refresh || @connection.nil?
@connection
end
def element_name
self.to_s.underscore
end
def collection_name
element_name.pluralize
end
def element_path(id)
"/#{collection_name}/#{id}.xml"
end
def collection_path
"/#{collection_name}.xml"
end
def find(*arguments)
scope = arguments.slice!(0)
case scope
when Fixnum
# { :person => person1 }
new(connection.get(element_path(scope)).values.first)
when :all
# { :people => { :person => [ person1, person2 ] } }
connection.get(collection_path).values.first.values.first.collect { |element| new(element) }
when :first
find(:all, *arguments).first
end
end
end
attr_accessor :attributes
def initialize(attributes = {})
@attributes = attributes
end
def id
attributes["id"]
end
def id=(id)
attributes["id"] = id
end
def save
update
end
def destroy
connection.delete(self.class.element_path(id))
end
def to_xml
attributes.to_xml(:root => self.class.element_name)
end
protected
def connection(refresh = false)
self.class.connection(refresh)
end
def update
connection.put(self.class.element_path(id), to_xml)
end
def method_missing(method_symbol, *arguments)
method_name = method_symbol.to_s
case method_name.last
when "="
attributes[method_name.first(-1)] = arguments.first
when "?"
# TODO
else
attributes[method_name] || super
end
end
end
end

View file

@ -0,0 +1,86 @@
require 'net/https'
require 'date'
require 'time'
require 'uri'
module ActiveResource
class ConnectionError < StandardError
attr_reader :response
def initialize(response, message = nil)
@response = response
@message = message
end
def to_s
"Failed with #{response.code}"
end
end
class ClientError < ConnectionError
end
class ServerError < ConnectionError
end
class ResourceNotFound < ClientError
end
class Connection
attr_accessor :uri
class << self
def requests
@@requests ||= []
end
end
def initialize(site)
@site = site
end
def get(path)
Hash.create_from_xml(request(:get, path).body)
end
def delete(path)
request(:delete, path)
end
def put(path, body)
request(:put, path, body)
end
def post(path, body)
request(:post, path, body)
end
private
def request(method, *arguments)
response = http.send(method, *arguments)
case response.code.to_i
when 200...300
response
when 404
raise(ResourceNotFound.new(response))
when 400...500
raise(ClientError.new(response))
when 500...600
raise(ServerError.new(response))
else
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
end
end
def http
unless @http
@http = Net::HTTP.new(@site.host, @site.port)
@http.use_ssl = @site.is_a?(URI::HTTPS)
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @http.use_ssl
end
@http
end
end
end

View file

@ -0,0 +1,7 @@
module ActiveResource
class Struct
def self.create
Class.new(Base)
end
end
end

View file

@ -0,0 +1,9 @@
module ActiveResource
module VERSION #:nodoc:
MAJOR = 0
MINOR = 5
TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
end

View file

@ -0,0 +1,8 @@
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/.')
require 'active_resource'
require 'test/unit'
require 'active_support/breakpoint'
require "#{File.dirname(__FILE__)}/http_mock"

View file

@ -0,0 +1,60 @@
require "#{File.dirname(__FILE__)}/abstract_unit"
require "fixtures/person"
class BaseTest < Test::Unit::TestCase
def setup
ActiveResource::HttpMock.respond_to(
ActiveResource::Request.new(:get, "/people/1.xml") => ActiveResource::Response.new("<person><name>Matz</name><id type='integer'>1</id></person>"),
ActiveResource::Request.new(:get, "/people/2.xml") => ActiveResource::Response.new("<person><name>David</name><id type='integer'>2</id></person>"),
ActiveResource::Request.new(:put, "/people/1.xml") => ActiveResource::Response.new({}, 200),
ActiveResource::Request.new(:delete, "/people/1.xml") => ActiveResource::Response.new({}, 200),
ActiveResource::Request.new(:delete, "/people/2.xml") => ActiveResource::Response.new({}, 400),
ActiveResource::Request.new(:post, "/people.xml") => ActiveResource::Response.new({}, 200),
ActiveResource::Request.new(:get, "/people/99.xml") => ActiveResource::Response.new({}, 404),
ActiveResource::Request.new(:get, "/people.xml") => ActiveResource::Response.new(
"<people><person><name>Matz</name><id type='integer'>1</id></person><person><name>David</name><id type='integer'>2</id></person></people>"
)
)
end
def test_collection_name
assert_equal "people", Person.collection_name
end
def test_find_by_id
matz = Person.find(1)
assert_kind_of Person, matz
assert_equal "Matz", matz.name
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_find_first
matz = Person.find(:first)
assert_kind_of Person, matz
assert_equal "Matz", matz.name
end
def test_find_by_id_not_found
assert_raises(ActiveResource::ResourceNotFound) { Person.find(99) }
end
def test_update
matz = Person.find(:first)
matz.name = "David"
assert_kind_of Person, matz
assert_equal "David", matz.name
matz.save
end
def test_destroy
assert Person.find(1).destroy
assert_raises(ActiveResource::ClientError) { Person.find(2).destroy }
end
end

View file

@ -0,0 +1,11 @@
require "#{File.dirname(__FILE__)}/abstract_unit"
require "fixtures/person"
class ConnectionTest < Test::Unit::TestCase
def setup
end
def test_something
true
end
end

View file

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

View file

@ -0,0 +1,84 @@
require 'active_resource/connection'
module ActiveResource
class HttpMock
class << self
def requests
@@requests ||= []
end
def responses
@@responses ||= {}
end
def respond_to(pairs)
reset!
pairs.each do |(path, response)|
responses[path] = response
end
end
def reset!
requests.clear
responses.clear
end
end
for method in [ :post, :put, :get, :delete ]
module_eval <<-EOE
def #{method}(*arguments)
request = ActiveResource::Request.new(:#{method}, *arguments)
self.class.requests << request
self.class.responses[request] || raise("No response recorded for: \#{request}")
end
EOE
end
def initialize(site)
@site = site
end
end
class Request
attr_accessor :path, :method, :body
def initialize(method, path, body = nil)
@method, @path, @body = method, path, body
end
def ==(other_request)
other_request.hash == hash
end
def eql?(other_request)
self == other_request
end
def to_s
"<#{method.to_s.upcase}: #{path} (#{body})>"
end
def hash
"#{path}#{method}".hash
end
end
class Response
attr_accessor :body, :code
def initialize(body, code = 200)
@body, @code = body, code
end
def success?
(200..299).include?(code)
end
end
class Connection
private
def http
@http ||= HttpMock.new(@site)
end
end
end