Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial check-in of Active Resourse

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4492 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 99d268c8534ad398c6c60a4978ef94699cbb8ada 1 parent a552651
@dhh dhh authored
View
1  activeresource/README
@@ -0,0 +1 @@
+= Active Resource -- Object-oriented REST services
View
137 activeresource/Rakefile
@@ -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
38 activeresource/lib/active_resource.rb
@@ -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
99 activeresource/lib/active_resource/base.rb
@@ -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
86 activeresource/lib/active_resource/connection.rb
@@ -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
7 activeresource/lib/active_resource/struct.rb
@@ -0,0 +1,7 @@
+module ActiveResource
+ class Struct
+ def self.create
+ Class.new(Base)
+ end
+ end
+end
View
9 activeresource/lib/active_resource/version.rb
@@ -0,0 +1,9 @@
+module ActiveResource
+ module VERSION #:nodoc:
+ MAJOR = 0
+ MINOR = 5
+ TINY = 0
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+end
View
8 activeresource/test/abstract_unit.rb
@@ -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
60 activeresource/test/base_test.rb
@@ -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
11 activeresource/test/connection_test.rb
@@ -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
3  activeresource/test/fixtures/person.rb
@@ -0,0 +1,3 @@
+class Person < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000/"
+end
View
84 activeresource/test/http_mock.rb
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.