mirror of
https://github.com/github/rails.git
synced 2026-01-12 08:08:31 -05:00
Compare commits
162 Commits
github25
...
2-3-github
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
529749e01b | ||
|
|
e9b3b09e08 | ||
|
|
1992a86b2e | ||
|
|
443815c4fe | ||
|
|
52abaed4b3 | ||
|
|
5b0053b2a8 | ||
|
|
1b98a0d72f | ||
|
|
1f59a8dfe8 | ||
|
|
e1011ea095 | ||
|
|
730e6a273c | ||
|
|
aa1b6d1284 | ||
|
|
5f6c95e29e | ||
|
|
7403667b89 | ||
|
|
1a45ec57bf | ||
|
|
9070fbcffe | ||
|
|
364b534815 | ||
|
|
14da203564 | ||
|
|
f46a4bab08 | ||
|
|
198aa6ef99 | ||
|
|
b3ae51c9fc | ||
|
|
1e6e438f6e | ||
|
|
2b01f832a3 | ||
|
|
1e5fda763e | ||
|
|
7c3d4ec43c | ||
|
|
7343ed7b05 | ||
|
|
2a70c9691d | ||
|
|
a141d9de0d | ||
|
|
74492f43a8 | ||
|
|
c2894170bf | ||
|
|
057aed6e18 | ||
|
|
02fc012b42 | ||
|
|
4fdaf21b28 | ||
|
|
35b871fbcd | ||
|
|
a5697840d6 | ||
|
|
d0e554d231 | ||
|
|
d38b7664cc | ||
|
|
e4cd9caf02 | ||
|
|
89e4514704 | ||
|
|
0a0d975f51 | ||
|
|
62daf4cb6f | ||
|
|
24711e1e29 | ||
|
|
cf8f36930c | ||
|
|
d622643e47 | ||
|
|
3f0241a613 | ||
|
|
38a7432590 | ||
|
|
1220d3c3ed | ||
|
|
3d72818356 | ||
|
|
221477dc21 | ||
|
|
975155c110 | ||
|
|
2931987892 | ||
|
|
e3290b98dd | ||
|
|
20088080a5 | ||
|
|
24e348489d | ||
|
|
ba4f4f8a01 | ||
|
|
ccf254b6cb | ||
|
|
3766b1b377 | ||
|
|
d3f87776a3 | ||
|
|
18c7c1f753 | ||
|
|
f63b0340ff | ||
|
|
7224ee1419 | ||
|
|
0c52ae6df3 | ||
|
|
f8b7cd2df7 | ||
|
|
c73ba86136 | ||
|
|
98fa5dd465 | ||
|
|
fa41bedf6b | ||
|
|
0a8282c557 | ||
|
|
d4a4facfcc | ||
|
|
dd4146854a | ||
|
|
cedf026a14 | ||
|
|
7ac3b0fa4f | ||
|
|
31cd7ea26d | ||
|
|
df387ab385 | ||
|
|
0118959601 | ||
|
|
83448c7de5 | ||
|
|
8f99d00868 | ||
|
|
987b61bd1d | ||
|
|
f05e54a9f3 | ||
|
|
b9918117bb | ||
|
|
42f85d118d | ||
|
|
acb182d094 | ||
|
|
6e0fcb788d | ||
|
|
fed4fafa8a | ||
|
|
f699184047 | ||
|
|
55d6a9f2df | ||
|
|
e5bebc01a8 | ||
|
|
a019f07a39 | ||
|
|
d13866d75d | ||
|
|
dfa2f469a4 | ||
|
|
bf0d43bb77 | ||
|
|
72cebbcb59 | ||
|
|
379dd9071c | ||
|
|
a743f17dbd | ||
|
|
25b896611d | ||
|
|
b988837359 | ||
|
|
890aff3b9d | ||
|
|
c0124ba8f3 | ||
|
|
455cd8c060 | ||
|
|
5d322ad957 | ||
|
|
3b6b4578c4 | ||
|
|
981016be60 | ||
|
|
3c1e01068b | ||
|
|
e42c679e43 | ||
|
|
5c4dfa63f7 | ||
|
|
c394fd82fa | ||
|
|
49933594c1 | ||
|
|
94fae25703 | ||
|
|
05cb9e6854 | ||
|
|
1a5734e0b5 | ||
|
|
24e5712294 | ||
|
|
8f6bafc333 | ||
|
|
c717a84b5d | ||
|
|
d537304b20 | ||
|
|
ca90ecf2cb | ||
|
|
4bb1d3ef20 | ||
|
|
3b7754c950 | ||
|
|
75638c576b | ||
|
|
76884dd7f7 | ||
|
|
29a72262aa | ||
|
|
76c5bf4f4b | ||
|
|
416b7171b8 | ||
|
|
e82a3ba2a0 | ||
|
|
8837faac73 | ||
|
|
20b12c3b42 | ||
|
|
0cf06787af | ||
|
|
5efad05b11 | ||
|
|
00521f5118 | ||
|
|
a086a33fd4 | ||
|
|
15678eac1c | ||
|
|
2e21cced12 | ||
|
|
fb86dada29 | ||
|
|
aa4dfa6937 | ||
|
|
ca7a53cbe9 | ||
|
|
1ddf5592e4 | ||
|
|
425a5d5e2e | ||
|
|
c8d7945ae4 | ||
|
|
6db8e71ad8 | ||
|
|
0e7a8ce464 | ||
|
|
a4274b33f7 | ||
|
|
9645f8be89 | ||
|
|
b2c42ec341 | ||
|
|
84d39ae996 | ||
|
|
35813faf54 | ||
|
|
ca03813864 | ||
|
|
8a78d5922a | ||
|
|
3770f13b97 | ||
|
|
755a361548 | ||
|
|
422b3d0dcb | ||
|
|
c96caaae9a | ||
|
|
050be61caf | ||
|
|
4baefa4de9 | ||
|
|
26fce88209 | ||
|
|
cb507570a1 | ||
|
|
9a2d6cad23 | ||
|
|
bf96f35248 | ||
|
|
21bae614ee | ||
|
|
1d6053f5bf | ||
|
|
f90bfeb930 | ||
|
|
bca938dae2 | ||
|
|
4579aa2767 | ||
|
|
0a522af512 | ||
|
|
ca6a64758b | ||
|
|
8573f7f86b |
@@ -5,3 +5,5 @@ gem install sqlite3 -v=1.3.7
|
||||
gem install rack -v=1.4.5
|
||||
gem install erubis -v=2.7.0
|
||||
gem install json -v=1.8.0
|
||||
gem install i18n -v=0.6.9
|
||||
gem install builder -v=3.2.2
|
||||
|
||||
1
RAILS_VERSION
Normal file
1
RAILS_VERSION
Normal file
@@ -0,0 +1 @@
|
||||
2.3.14.github47
|
||||
6
README.md
Normal file
6
README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# GitHub Rails
|
||||
|
||||
This is GitHub's fork of Rails 2.3.
|
||||
|
||||
Please note that this fork is **unsupported**. It is not guaranteed to receive security patches or remain stable. **Use at your own risk.**
|
||||
|
||||
7
Rakefile
7
Rakefile
@@ -3,7 +3,7 @@ require 'rdoc/task'
|
||||
|
||||
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
|
||||
|
||||
PROJECTS = %w(activesupport railties actionpack actionmailer activeresource activerecord)
|
||||
PROJECTS = %w(activesupport railties actionpack actionmailer activerecord)
|
||||
|
||||
Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path|
|
||||
require version_path
|
||||
@@ -48,11 +48,6 @@ RDoc::Task.new do |rdoc|
|
||||
rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
|
||||
|
||||
rdoc.rdoc_files.include('activeresource/README')
|
||||
rdoc.rdoc_files.include('activeresource/CHANGELOG')
|
||||
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_files.include('actionpack/CHANGELOG')
|
||||
rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
|
||||
|
||||
@@ -23,8 +23,6 @@ task :default => [ :test ]
|
||||
Rake::TestTask.new { |t|
|
||||
t.libs << "test"
|
||||
t.pattern = 'test/*_test.rb'
|
||||
t.verbose = true
|
||||
t.warning = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
16
actionmailer/actionmailer.gemspec
Normal file
16
actionmailer/actionmailer.gemspec
Normal file
@@ -0,0 +1,16 @@
|
||||
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).chomp
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'actionmailer'
|
||||
s.version = version
|
||||
s.summary = 'Service layer for easy email delivery and testing.'
|
||||
s.description = 'Makes it trivial to test and deliver emails sent from a single service layer.'
|
||||
|
||||
s.author = 'David Heinemeier Hansson'
|
||||
s.email = 'david@loudthinking.com'
|
||||
s.homepage = 'http://www.rubyonrails.org'
|
||||
|
||||
s.require_path = 'lib'
|
||||
|
||||
s.add_dependency 'actionpack', "= #{version}"
|
||||
end
|
||||
@@ -30,16 +30,12 @@ Rake::TestTask.new(:test_action_pack) do |t|
|
||||
# make sure we include the tests in alphabetical order as on some systems
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files = Dir.glob( "test/[cftv]*/**/*_test.rb" ).sort
|
||||
|
||||
t.verbose = true
|
||||
#t.warning = true
|
||||
end
|
||||
|
||||
desc 'ActiveRecord Integration Tests'
|
||||
Rake::TestTask.new(:test_active_record_integration) do |t|
|
||||
t.libs << "test"
|
||||
t.test_files = Dir.glob("test/activerecord/*_test.rb")
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
|
||||
|
||||
17
actionpack/actionpack.gemspec
Normal file
17
actionpack/actionpack.gemspec
Normal file
@@ -0,0 +1,17 @@
|
||||
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).chomp
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'actionpack'
|
||||
s.version = version
|
||||
s.summary = 'Web-flow and rendering framework putting the VC in MVC.'
|
||||
s.description = 'Eases web-request routing, handling, and response as a half-way front, half-way page controller. Implemented with specific emphasis on enabling easy unit/integration testing that doesn\'t require a browser.'
|
||||
|
||||
s.author = 'David Heinemeier Hansson'
|
||||
s.email = 'david@loudthinking.com'
|
||||
s.homepage = 'http://www.rubyonrails.org'
|
||||
|
||||
s.require_path = 'lib'
|
||||
|
||||
s.add_dependency 'activesupport', "= #{version}"
|
||||
s.add_dependency 'rack', '~> 1.4'
|
||||
end
|
||||
@@ -38,7 +38,7 @@ module ActionController
|
||||
# TODO: Review explicit to see if they will automatically be handled by
|
||||
# the initilizer if they are really needed.
|
||||
def self.load_all!
|
||||
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||
end
|
||||
|
||||
autoload :Base, 'action_controller/base'
|
||||
@@ -99,10 +99,6 @@ module ActionController
|
||||
autoload :CookieStore, 'action_controller/session/cookie_store'
|
||||
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
autoload :CgiRequest, 'action_controller/cgi_process'
|
||||
autoload :CGIHandler, 'action_controller/cgi_process'
|
||||
end
|
||||
|
||||
autoload :Mime, 'action_controller/mime_type'
|
||||
|
||||
@@ -964,13 +964,6 @@ module ActionController #:nodoc:
|
||||
render_for_text(@template.render(options), options[:status])
|
||||
end
|
||||
|
||||
elsif options[:update]
|
||||
@template.send(:_evaluate_assigns_and_ivars)
|
||||
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
|
||||
response.content_type = Mime::JS
|
||||
render_for_text(generator.to_s, options[:status])
|
||||
|
||||
elsif options[:nothing]
|
||||
render_for_text(nil, options[:status])
|
||||
|
||||
@@ -1280,8 +1273,7 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def initialize_template_class(response)
|
||||
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
||||
response.template.helpers.send :include, self.class.master_helper_module
|
||||
response.template = self.class.master_helper_class.new(self.class.view_paths, {}, self)
|
||||
response.redirected_to = nil
|
||||
@performed_render = @performed_redirect = false
|
||||
end
|
||||
@@ -1328,14 +1320,18 @@ module ActionController #:nodoc:
|
||||
render
|
||||
end
|
||||
|
||||
CVE_2014_0310 = Class.new(StandardError)
|
||||
|
||||
def perform_action
|
||||
# CVE-2014-0130 protection
|
||||
if action_name.include? "/"
|
||||
raise CVE_2014_0310
|
||||
end
|
||||
|
||||
if action_methods.include?(action_name)
|
||||
send(action_name)
|
||||
default_render unless performed?
|
||||
elsif RUBY_VERSION == "1.9.3" && respond_to?(:method_missing)
|
||||
method_missing action_name.intern
|
||||
default_render unless performed?
|
||||
elsif RUBY_VERSION >= "2.0.0" && respond_to?(:method_missing, true) && !method(:method_missing).private?
|
||||
elsif defined?(self.method_missing) # returns "method" if method_missing is public or protected, but not if it's private
|
||||
method_missing action_name.intern
|
||||
default_render unless performed?
|
||||
else
|
||||
|
||||
@@ -87,7 +87,6 @@ module ActionController #:nodoc:
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(log_message)
|
||||
response.headers["X-Runtime"] = "%.0f" % ms
|
||||
else
|
||||
perform_action_without_benchmark
|
||||
end
|
||||
|
||||
@@ -39,9 +39,9 @@ module ActionController #:nodoc:
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.safe_concat(cache.html_safe)
|
||||
else
|
||||
pos = buffer.length
|
||||
pos = buffer.bytesize
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
write_fragment(name, buffer.byteslice(pos..-1), options)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
require 'action_controller/cgi_ext/stdinput'
|
||||
require 'action_controller/cgi_ext/query_extension'
|
||||
require 'action_controller/cgi_ext/cookie'
|
||||
|
||||
class CGI #:nodoc:
|
||||
include ActionController::CgiExt::Stdinput
|
||||
|
||||
class << self
|
||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
require 'delegate'
|
||||
require 'cgi'
|
||||
require 'cgi/cookie'
|
||||
|
||||
CGI.module_eval { remove_const "Cookie" }
|
||||
|
||||
@@ -24,7 +26,7 @@ class CGI #:nodoc:
|
||||
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
|
||||
# +false+). Secure cookies are only transmitted to HTTPS servers.
|
||||
# * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
|
||||
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
|
||||
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
|
||||
#
|
||||
# These keywords correspond to attributes of the cookie object.
|
||||
def initialize(name = '', *value)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
require 'cgi'
|
||||
|
||||
class CGI #:nodoc:
|
||||
module QueryExtension
|
||||
# Remove the old initialize_query method before redefining it.
|
||||
remove_method :initialize_query
|
||||
|
||||
# Neuter CGI parameter parsing.
|
||||
def initialize_query
|
||||
# Fix some strange request environments.
|
||||
env_table['REQUEST_METHOD'] ||= 'GET'
|
||||
|
||||
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
|
||||
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
|
||||
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
|
||||
@params = {}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
require 'cgi'
|
||||
|
||||
module ActionController
|
||||
module CgiExt
|
||||
# Publicize the CGI's internal input stream so we can lazy-read
|
||||
# request.body. Make it writable so we don't have to play $stdin games.
|
||||
module Stdinput
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
remove_method :stdinput
|
||||
attr_accessor :stdinput
|
||||
end
|
||||
|
||||
base.alias_method_chain :initialize, :stdinput
|
||||
end
|
||||
|
||||
def initialize_with_stdinput(type = nil, stdinput = $stdin)
|
||||
@stdinput = stdinput
|
||||
@stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding)
|
||||
initialize_without_stdinput(type || 'query')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,77 +0,0 @@
|
||||
require 'action_controller/cgi_ext'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class CGIHandler
|
||||
module ProperStream
|
||||
def each
|
||||
while line = gets
|
||||
yield line
|
||||
end
|
||||
end
|
||||
|
||||
def read(*args)
|
||||
if args.empty?
|
||||
super || ""
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.dispatch_cgi(app, cgi, out = $stdout)
|
||||
env = cgi.__send__(:env_table)
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
|
||||
cgi.stdinput.extend ProperStream
|
||||
|
||||
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||
|
||||
env.update({
|
||||
"rack.version" => [0,1],
|
||||
"rack.input" => cgi.stdinput,
|
||||
"rack.errors" => $stderr,
|
||||
"rack.multithread" => false,
|
||||
"rack.multiprocess" => true,
|
||||
"rack.run_once" => false,
|
||||
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
||||
})
|
||||
|
||||
env["QUERY_STRING"] ||= ""
|
||||
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
||||
env["REQUEST_PATH"] ||= "/"
|
||||
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
|
||||
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
out.binmode if out.respond_to?(:binmode)
|
||||
out.sync = false if out.respond_to?(:sync=)
|
||||
|
||||
headers['Status'] = status.to_s
|
||||
|
||||
if headers.include?('Set-Cookie')
|
||||
headers['cookie'] = headers.delete('Set-Cookie').split("\n")
|
||||
end
|
||||
|
||||
out.write(cgi.header(headers))
|
||||
|
||||
body.each { |part|
|
||||
out.write part
|
||||
out.flush if out.respond_to?(:flush)
|
||||
}
|
||||
ensure
|
||||
body.close if body.respond_to?(:close)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequest #:nodoc:
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => nil,
|
||||
:prefix => "ruby_sess.",
|
||||
:session_path => "/",
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true,
|
||||
:session_http_only => true
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -22,11 +22,6 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
@@ -42,13 +37,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def run_prepare_callbacks
|
||||
if defined?(Rails) && Rails.logger
|
||||
logger = Rails.logger
|
||||
else
|
||||
logger = Logger.new($stderr)
|
||||
end
|
||||
|
||||
new(logger).send :run_callbacks, :prepare_dispatch
|
||||
new.send :run_callbacks, :prepare_dispatch
|
||||
end
|
||||
|
||||
def reload_application
|
||||
@@ -75,10 +64,8 @@ module ActionController
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
# DEPRECATE: Remove arguments, since they are only used by CGI
|
||||
def initialize(output = $stdout, request = nil, response = nil)
|
||||
@output = output
|
||||
build_middleware_stack if @@cache_classes
|
||||
def initialize
|
||||
build_middleware_stack
|
||||
end
|
||||
|
||||
def dispatch
|
||||
@@ -96,21 +83,11 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
CGIHandler.dispatch_cgi(self, cgi, @output)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if @@cache_classes
|
||||
@app.call(env)
|
||||
else
|
||||
Reloader.run do
|
||||
# When class reloading is turned on, we will want to rebuild the
|
||||
# middleware stack every time we process a request. If we don't
|
||||
# rebuild the middleware stack, then the stack may contain references
|
||||
# to old classes metal classes, which will b0rk class reloading.
|
||||
build_middleware_stack
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,37 +45,74 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def []=(k, v)
|
||||
k = k.to_s
|
||||
@flash[k] = v
|
||||
@flash.discard(k)
|
||||
v
|
||||
end
|
||||
|
||||
def [](k)
|
||||
@flash[k]
|
||||
@flash[k.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
class FlashHash < Hash
|
||||
def self.from_session_value(value)
|
||||
flash = case value
|
||||
when FlashHash # Rails 2.3
|
||||
value
|
||||
when Hash # Rails 4.0
|
||||
flashes = value['flashes'] || {}
|
||||
flashes.stringify_keys!
|
||||
discard = value['discard'] || []
|
||||
discard = discard.map do |item|
|
||||
item.kind_of?(Symbol) ? item.to_s : item
|
||||
end
|
||||
used = Hash[flashes.keys.map{|k| [k, discard.include?(k)] }]
|
||||
|
||||
new_from_values(flashes, used)
|
||||
else
|
||||
new
|
||||
end
|
||||
flash
|
||||
end
|
||||
|
||||
def initialize #:nodoc:
|
||||
super
|
||||
@used = {}
|
||||
end
|
||||
|
||||
def to_session_value
|
||||
return nil if empty?
|
||||
rails_3_discard_list = @used.map{|k,v| k if v}.compact
|
||||
{'discard' => rails_3_discard_list, 'flashes' => Hash[to_a]}
|
||||
end
|
||||
|
||||
def []=(k, v) #:nodoc:
|
||||
k = k.to_s
|
||||
keep(k)
|
||||
super
|
||||
super(k, v)
|
||||
end
|
||||
|
||||
def [](k)
|
||||
super(k.to_s)
|
||||
end
|
||||
|
||||
def delete(k)
|
||||
super(k.to_s)
|
||||
end
|
||||
|
||||
def update(h) #:nodoc:
|
||||
h.stringify_keys!
|
||||
h.keys.each { |k| keep(k) }
|
||||
super
|
||||
super(h)
|
||||
end
|
||||
|
||||
alias :merge! :update
|
||||
|
||||
def replace(h) #:nodoc:
|
||||
@used = {}
|
||||
super
|
||||
super(h.stringify_keys)
|
||||
end
|
||||
|
||||
# Sets a flash that will not be available to the next action, only to the current.
|
||||
@@ -126,8 +163,7 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def store(session, key = "flash")
|
||||
return if self.empty?
|
||||
session[key] = self
|
||||
session[key] = to_session_value
|
||||
end
|
||||
|
||||
private
|
||||
@@ -138,11 +174,20 @@ module ActionController #:nodoc:
|
||||
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
|
||||
def use(k=nil, v=true)
|
||||
unless k.nil?
|
||||
@used[k] = v
|
||||
@used[k.to_s] = v
|
||||
else
|
||||
keys.each{ |key| use(key, v) }
|
||||
end
|
||||
end
|
||||
|
||||
def self.new_from_values(flashes, used)
|
||||
new.tap do |flash_hash|
|
||||
flashes.each do |k, v|
|
||||
flash_hash[k] = v
|
||||
end
|
||||
flash_hash.instance_variable_set("@used", used)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods #:nodoc:
|
||||
@@ -168,11 +213,11 @@ module ActionController #:nodoc:
|
||||
if notice = response_status_and_flash.delete(:notice)
|
||||
flash[:notice] = notice
|
||||
end
|
||||
|
||||
|
||||
if other_flashes = response_status_and_flash.delete(:flash)
|
||||
flash.update(other_flashes)
|
||||
end
|
||||
|
||||
|
||||
redirect_to_without_flash(options, response_status_and_flash)
|
||||
end
|
||||
|
||||
@@ -181,19 +226,19 @@ module ActionController #:nodoc:
|
||||
# to put a new one.
|
||||
def flash #:doc:
|
||||
if !defined?(@_flash)
|
||||
@_flash = session["flash"] || FlashHash.new
|
||||
@_flash = Flash::FlashHash.from_session_value(session["flash"])
|
||||
@_flash.sweep
|
||||
end
|
||||
|
||||
@_flash
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Convenience accessor for flash[:alert]
|
||||
def alert
|
||||
flash[:alert]
|
||||
end
|
||||
|
||||
|
||||
# Convenience accessor for flash[:alert]=
|
||||
def alert=(message)
|
||||
flash[:alert] = message
|
||||
@@ -203,7 +248,7 @@ module ActionController #:nodoc:
|
||||
def notice
|
||||
flash[:notice]
|
||||
end
|
||||
|
||||
|
||||
# Convenience accessor for flash[:notice]=
|
||||
def notice=(message)
|
||||
flash[:notice] = message
|
||||
|
||||
@@ -69,6 +69,22 @@ module ActionController #:nodoc:
|
||||
# N/A | Carolina Railhaws Training Workshop
|
||||
#
|
||||
module ClassMethods
|
||||
# To avoid extending an instance of ActionView::Base with the master_helper_module
|
||||
# every single time we render a view, we're caching a class that has
|
||||
# master_helper_module already included that we can just instantiate.
|
||||
def master_helper_class
|
||||
return @master_helper_class if @master_helper_class
|
||||
|
||||
@master_helper_class = Class.new(ActionView::Base).tap do |klass|
|
||||
klass.send(:include, master_helper_module)
|
||||
end
|
||||
end
|
||||
|
||||
def master_helper_module=(mod)
|
||||
write_inheritable_attribute(:master_helper_module, mod)
|
||||
@master_helper_class = nil
|
||||
end
|
||||
|
||||
# Makes all the (instance) methods in the helper module available to templates rendered through this controller.
|
||||
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
|
||||
# available to the templates.
|
||||
@@ -182,8 +198,7 @@ module ActionController #:nodoc:
|
||||
# Provides a proxy to access helpers methods from outside the view.
|
||||
def helpers
|
||||
unless @helper_proxy
|
||||
@helper_proxy = ActionView::Base.new
|
||||
@helper_proxy.extend master_helper_module
|
||||
@helper_proxy = master_helper_class.new
|
||||
else
|
||||
@helper_proxy
|
||||
end
|
||||
|
||||
@@ -423,13 +423,13 @@ EOM
|
||||
|
||||
# Override Rack's GET method to support indifferent access
|
||||
def GET
|
||||
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
|
||||
@env["action_controller.request.query_parameters"] ||= deep_munge(normalize_parameters(super) || {})
|
||||
end
|
||||
alias_method :query_parameters, :GET
|
||||
|
||||
# Override Rack's POST method to support indifferent access
|
||||
def POST
|
||||
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
|
||||
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
|
||||
end
|
||||
alias_method :request_parameters, :POST
|
||||
|
||||
@@ -469,6 +469,22 @@ EOM
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
# Remove nils from the params hash
|
||||
def deep_munge(hash)
|
||||
hash.each do |k, v|
|
||||
case v
|
||||
when Array
|
||||
v.grep(Hash) { |x| deep_munge(x) }
|
||||
v.compact!
|
||||
hash[k] = nil if v.empty?
|
||||
when Hash
|
||||
deep_munge(v)
|
||||
end
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
# Convert nested Hashs to HashWithIndifferentAccess and replace
|
||||
# file upload hashs with UploadedFile objects
|
||||
def normalize_parameters(value)
|
||||
|
||||
@@ -192,14 +192,7 @@ module ActionController
|
||||
url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
|
||||
#
|
||||
end # end
|
||||
#Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
|
||||
def formatted_#{selector}(*args) # def formatted_users_url(*args)
|
||||
ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
|
||||
"formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
|
||||
"Please pass format to the standard " + # "Please pass format to the standard " +
|
||||
"#{selector} method instead.", caller) # "users_url method instead.", caller)
|
||||
#{selector}(*args) # users_url(*args)
|
||||
end # end
|
||||
|
||||
protected :#{selector} # protected :users_url
|
||||
end_eval
|
||||
helpers << selector
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'rack/utils'
|
||||
|
||||
module ActionController
|
||||
module Session
|
||||
class AbstractStore
|
||||
class AbstractStore
|
||||
ENV_SESSION_KEY = 'rack.session'.freeze
|
||||
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
||||
|
||||
@@ -55,17 +55,17 @@ module ActionController
|
||||
|
||||
def [](key)
|
||||
load_for_read!
|
||||
super
|
||||
fetch(key.to_s, super(key))
|
||||
end
|
||||
|
||||
def has_key?(key)
|
||||
load_for_read!
|
||||
super
|
||||
super(key.to_s) || super(key)
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
load_for_write!
|
||||
super
|
||||
super(key.to_s, value)
|
||||
end
|
||||
|
||||
def clear
|
||||
@@ -82,12 +82,19 @@ module ActionController
|
||||
|
||||
def update(hash)
|
||||
load_for_write!
|
||||
super
|
||||
super(hash.stringify_keys)
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
load_for_write!
|
||||
super
|
||||
if has_key? key
|
||||
value = self[key]
|
||||
super(key)
|
||||
super(key.to_s)
|
||||
value
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def data
|
||||
@@ -119,7 +126,7 @@ module ActionController
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
def load_for_read!
|
||||
load! if !loaded? && exists?
|
||||
end
|
||||
@@ -183,7 +190,7 @@ module ActionController
|
||||
request = ActionController::Request.new(env)
|
||||
|
||||
return response if (options[:secure] && !request.ssl?)
|
||||
|
||||
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
||||
|
||||
sid = options[:id] || generate_sid
|
||||
@@ -205,12 +212,12 @@ module ActionController
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
def prepare!(env)
|
||||
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
||||
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
||||
end
|
||||
|
||||
|
||||
def generate_sid
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
@@ -222,7 +229,7 @@ module ActionController
|
||||
[sid, session]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def extract_session_id(env)
|
||||
stale_session_check! do
|
||||
request = Rack::Request.new(env)
|
||||
@@ -235,7 +242,7 @@ module ActionController
|
||||
def current_session_id(env)
|
||||
env[ENV_SESSION_OPTIONS_KEY][:id]
|
||||
end
|
||||
|
||||
|
||||
def exists?(env)
|
||||
current_session_id(env).present?
|
||||
end
|
||||
@@ -247,11 +254,11 @@ module ActionController
|
||||
def set_session(env, sid, session_data)
|
||||
raise '#set_session needs to be implemented.'
|
||||
end
|
||||
|
||||
|
||||
def destroy(env)
|
||||
raise '#destroy needs to be implemented.'
|
||||
end
|
||||
|
||||
|
||||
module SessionUtils
|
||||
private
|
||||
def stale_session_check!
|
||||
|
||||
@@ -37,7 +37,7 @@ module ActionController
|
||||
# Note that changing digest or secret invalidates all existing sessions!
|
||||
class CookieStore
|
||||
include AbstractStore::SessionUtils
|
||||
|
||||
|
||||
# Cookies can typically store 4096 bytes.
|
||||
MAX = 4096
|
||||
SECRET_MIN_LENGTH = 30 # characters
|
||||
@@ -86,7 +86,8 @@ module ActionController
|
||||
@secret = options.delete(:secret).freeze
|
||||
|
||||
@digest = options.delete(:digest) || 'SHA1'
|
||||
@verifier = verifier_for(@secret, @digest)
|
||||
@serializer = options.delete(:serializer) || Marshal
|
||||
@verifier = verifier_for(@secret, @digest, @serializer)
|
||||
|
||||
@default_options = DEFAULT_OPTIONS.merge(options).freeze
|
||||
|
||||
@@ -95,14 +96,21 @@ module ActionController
|
||||
|
||||
def call(env)
|
||||
prepare!(env)
|
||||
|
||||
|
||||
status, headers, body = @app.call(env)
|
||||
|
||||
session_data = env[ENV_SESSION_KEY]
|
||||
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||
request = ActionController::Request.new(env)
|
||||
|
||||
|
||||
if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
|
||||
|
||||
# Backport standard Rack::Session::Cookie behavior
|
||||
# Skip writing session if env['rack.session.options'][:skip] is set
|
||||
if options[:skip]
|
||||
return [status, headers, body]
|
||||
end
|
||||
|
||||
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
||||
|
||||
persistent_session_id!(session_data)
|
||||
@@ -122,7 +130,7 @@ module ActionController
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
def prepare!(env)
|
||||
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
||||
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
|
||||
@@ -131,13 +139,13 @@ module ActionController
|
||||
def load_session(env)
|
||||
data = unpacked_cookie_data(env)
|
||||
data = persistent_session_id!(data)
|
||||
[data[:session_id], data]
|
||||
[data["session_id"] || data[:session_id], data]
|
||||
end
|
||||
|
||||
|
||||
def extract_session_id(env)
|
||||
if data = unpacked_cookie_data(env)
|
||||
persistent_session_id!(data) unless data.empty?
|
||||
data[:session_id]
|
||||
data["session_id"] || data[:session_id]
|
||||
else
|
||||
nil
|
||||
end
|
||||
@@ -207,9 +215,9 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
def verifier_for(secret, digest)
|
||||
def verifier_for(secret, digest, serializer)
|
||||
key = secret.respond_to?(:call) ? secret.call : secret
|
||||
ActiveSupport::MessageVerifier.new(key, digest: digest)
|
||||
ActiveSupport::MessageVerifier.new(key, digest: digest, serializer: serializer)
|
||||
end
|
||||
|
||||
def generate_sid
|
||||
@@ -225,12 +233,12 @@ module ActionController
|
||||
end
|
||||
|
||||
def inject_persistent_session_id(data)
|
||||
requires_session_id?(data) ? { :session_id => generate_sid } : {}
|
||||
requires_session_id?(data) ? { "session_id" => generate_sid } : {}
|
||||
end
|
||||
|
||||
def requires_session_id?(data)
|
||||
if data
|
||||
data.respond_to?(:key?) && !data.key?(:session_id)
|
||||
data.respond_to?(:key?) && !(data.key?("session_id") || data.key?(:session_id))
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
@@ -219,7 +219,7 @@ module ActionController #:nodoc:
|
||||
|
||||
# A shortcut to the flash. Returns an empty hash if no session flash exists.
|
||||
def flash
|
||||
session['flash'] || {}
|
||||
ActionController::Flash::FlashHash.from_session_value(session["flash"]) || {}
|
||||
end
|
||||
|
||||
# Do we have a flash?
|
||||
|
||||
@@ -175,13 +175,6 @@ module ActionView #:nodoc:
|
||||
delegate :logger, :to => 'ActionController::Base'
|
||||
end
|
||||
|
||||
@@debug_rjs = false
|
||||
##
|
||||
# :singleton-method:
|
||||
# Specify whether RJS responses should be wrapped in a try/catch block
|
||||
# that alert()s the caught exception (and then re-raises it).
|
||||
cattr_accessor :debug_rjs
|
||||
|
||||
# Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
|
||||
# Automatically reloading templates are not thread safe and should only be used in development mode.
|
||||
@@cache_template_loading = nil
|
||||
@@ -210,24 +203,10 @@ module ActionView #:nodoc:
|
||||
ActionView::PathSet.new(Array(value))
|
||||
end
|
||||
|
||||
attr_reader :helpers
|
||||
|
||||
class ProxyModule < Module
|
||||
def initialize(receiver)
|
||||
@receiver = receiver
|
||||
end
|
||||
|
||||
def include(*args)
|
||||
super(*args)
|
||||
@receiver.extend(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
|
||||
@assigns = assigns_for_first_render
|
||||
@assigns_added = nil
|
||||
@controller = controller
|
||||
@helpers = ProxyModule.new(self)
|
||||
self.view_paths = view_paths
|
||||
|
||||
@_first_render = nil
|
||||
@@ -270,8 +249,6 @@ module ActionView #:nodoc:
|
||||
elsif options[:text]
|
||||
options[:text]
|
||||
end
|
||||
when :update
|
||||
update_page(&block)
|
||||
else
|
||||
render_partial(:partial => options, :locals => local_assigns)
|
||||
end
|
||||
|
||||
@@ -14,12 +14,10 @@ module ActionView #:nodoc:
|
||||
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
|
||||
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
|
||||
autoload :NumberHelper, 'action_view/helpers/number_helper'
|
||||
autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
|
||||
autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper'
|
||||
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
|
||||
autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper'
|
||||
autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper'
|
||||
autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper'
|
||||
autoload :TagHelper, 'action_view/helpers/tag_helper'
|
||||
autoload :TextHelper, 'action_view/helpers/text_helper'
|
||||
autoload :TranslationHelper, 'action_view/helpers/translation_helper'
|
||||
@@ -47,12 +45,10 @@ module ActionView #:nodoc:
|
||||
include FormTagHelper
|
||||
include JavaScriptHelper
|
||||
include NumberHelper
|
||||
include PrototypeHelper
|
||||
include RawOutputHelper
|
||||
include RecordIdentificationHelper
|
||||
include RecordTagHelper
|
||||
include SanitizeHelper
|
||||
include ScriptaculousHelper
|
||||
include TagHelper
|
||||
include TextHelper
|
||||
include TranslationHelper
|
||||
|
||||
@@ -768,7 +768,11 @@ module ActionView
|
||||
options = options.stringify_keys
|
||||
tag_value = options.delete("value")
|
||||
name_and_id = options.dup
|
||||
name_and_id["id"] = name_and_id["for"]
|
||||
if name_and_id.has_key?("for")
|
||||
name_and_id["id"] = name_and_id["for"]
|
||||
else
|
||||
name_and_id.delete("id")
|
||||
end
|
||||
add_default_name_and_id_for_value(tag_value, name_and_id)
|
||||
options.delete("index")
|
||||
options["for"] ||= name_and_id["id"]
|
||||
@@ -928,15 +932,15 @@ module ActionView
|
||||
|
||||
def add_default_name_and_id(options)
|
||||
if options.has_key?("index")
|
||||
options["name"] ||= tag_name_with_index(options["index"])
|
||||
options["id"] ||= tag_id_with_index(options["index"])
|
||||
options["name"] = tag_name_with_index(options["index"]) unless options.has_key?("name")
|
||||
options["id"] = tag_id_with_index(options["index"]) unless options.has_key?("id")
|
||||
options.delete("index")
|
||||
elsif defined?(@auto_index)
|
||||
options["name"] ||= tag_name_with_index(@auto_index)
|
||||
options["id"] ||= tag_id_with_index(@auto_index)
|
||||
options["name"] = tag_name_with_index(@auto_index) unless options.has_key?("name")
|
||||
options["id"] = tag_id_with_index(@auto_index) unless options.has_key?("id")
|
||||
else
|
||||
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
|
||||
options["id"] ||= tag_id
|
||||
options["name"] = tag_name + (options.has_key?('multiple') ? '[]' : '') unless options.has_key?("name")
|
||||
options["id"] = tag_id unless options.has_key?("id")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'action_view/helpers/tag_helper'
|
||||
require 'action_view/helpers/prototype_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
@@ -39,95 +38,6 @@ module ActionView
|
||||
JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
|
||||
end
|
||||
|
||||
include PrototypeHelper
|
||||
|
||||
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
|
||||
# onclick handler and return false after the fact.
|
||||
#
|
||||
# The first argument +name+ is used as the link text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
#
|
||||
# Examples:
|
||||
# link_to_function "Greeting", "alert('Hello world!')"
|
||||
# Produces:
|
||||
# <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
|
||||
#
|
||||
# link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
|
||||
# Produces:
|
||||
# <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
|
||||
# <img src="/images/delete.png?" alt="Delete"/>
|
||||
# </a>
|
||||
#
|
||||
# link_to_function("Show me more", nil, :id => "more_link") do |page|
|
||||
# page[:details].visual_effect :toggle_blind
|
||||
# page[:more_link].replace_html "Show me less"
|
||||
# end
|
||||
# Produces:
|
||||
# <a href="#" id="more_link" onclick="try {
|
||||
# $("details").visualEffect("toggle_blind");
|
||||
# $("more_link").update("Show me less");
|
||||
# }
|
||||
# catch (e) {
|
||||
# alert('RJS error:\n\n' + e.toString());
|
||||
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
||||
# \n$(\"more_link\").update(\"Show me less\");');
|
||||
# throw e
|
||||
# };
|
||||
# return false;">Show me more</a>
|
||||
#
|
||||
def link_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
|
||||
href = html_options[:href] || '#'
|
||||
|
||||
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
|
||||
end
|
||||
|
||||
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# The first argument +name+ is used as the button's value or display text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
|
||||
# button_to_function "Details" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
# button_to_function "Details", :class => "details_button" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
def button_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!.symbolize_keys
|
||||
|
||||
function = block_given? ? update_page(&block) : args[0] || ''
|
||||
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
|
||||
|
||||
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
|
||||
end
|
||||
|
||||
JS_ESCAPE_MAP = {
|
||||
'\\' => '\\\\',
|
||||
'</' => '<\/',
|
||||
|
||||
@@ -73,6 +73,8 @@ module ActionView
|
||||
def number_to_currency(number, options = {})
|
||||
options.symbolize_keys!
|
||||
|
||||
options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(currency)
|
||||
@@ -85,11 +87,13 @@ module ActionView
|
||||
separator = '' if precision == 0
|
||||
|
||||
begin
|
||||
format.gsub(/%n/, number_with_precision(number,
|
||||
value = number_with_precision(number,
|
||||
:precision => precision,
|
||||
:delimiter => delimiter,
|
||||
:separator => separator)
|
||||
).gsub(/%u/, unit).html_safe
|
||||
value = ERB::Util.html_escape(value) if value
|
||||
unit = ERB::Util.html_escape(unit)
|
||||
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
|
||||
rescue
|
||||
number
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,226 +0,0 @@
|
||||
require 'action_view/helpers/javascript_helper'
|
||||
require 'active_support/json'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides a set of helpers for calling Scriptaculous JavaScript
|
||||
# functions, including those which create Ajax controls and visual effects.
|
||||
#
|
||||
# To be able to use these helpers, you must include the Prototype
|
||||
# JavaScript framework and the Scriptaculous JavaScript library in your
|
||||
# pages. See the documentation for ActionView::Helpers::JavaScriptHelper
|
||||
# for more information on including the necessary JavaScript.
|
||||
#
|
||||
# The Scriptaculous helpers' behavior can be tweaked with various options.
|
||||
# See the documentation at http://script.aculo.us for more information on
|
||||
# using these helpers in your application.
|
||||
module ScriptaculousHelper
|
||||
unless const_defined? :TOGGLE_EFFECTS
|
||||
TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
|
||||
end
|
||||
|
||||
# Returns a JavaScript snippet to be used on the Ajax callbacks for
|
||||
# starting visual effects.
|
||||
#
|
||||
# Example:
|
||||
# <%= link_to_remote "Reload", :update => "posts",
|
||||
# :url => { :action => "reload" },
|
||||
# :complete => visual_effect(:highlight, "posts", :duration => 0.5)
|
||||
#
|
||||
# If no +element_id+ is given, it assumes "element" which should be a local
|
||||
# variable in the generated JavaScript execution context. This can be
|
||||
# used for example with +drop_receiving_element+:
|
||||
#
|
||||
# <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
|
||||
#
|
||||
# This would fade the element that was dropped on the drop receiving
|
||||
# element.
|
||||
#
|
||||
# For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
|
||||
# <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
|
||||
# blinddown/blindup respectively.
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def visual_effect(name, element_id = false, js_options = {})
|
||||
element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
|
||||
|
||||
js_options[:queue] = if js_options[:queue].is_a?(Hash)
|
||||
'{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
|
||||
elsif js_options[:queue]
|
||||
"'#{js_options[:queue]}'"
|
||||
end if js_options[:queue]
|
||||
|
||||
[:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
|
||||
js_options[option] = "'#{js_options[option]}'" if js_options[option]
|
||||
end
|
||||
|
||||
if TOGGLE_EFFECTS.include? name.to_sym
|
||||
"Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
|
||||
else
|
||||
"new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
|
||||
end
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ sortable
|
||||
# by drag-and-drop and make an Ajax call whenever the sort order has
|
||||
# changed. By default, the action called gets the serialized sortable
|
||||
# element as parameters.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# <%= sortable_element("my_list", :url => { :action => "order" }) %>
|
||||
#
|
||||
# In the example, the action gets a "my_list" array parameter
|
||||
# containing the values of the ids of elements the sortable consists
|
||||
# of, in the current order.
|
||||
#
|
||||
# Important: For this to work, the sortable elements must have id
|
||||
# attributes in the form "string_identifier". For example, "item_1". Only
|
||||
# the identifier part of the id attribute will be serialized.
|
||||
#
|
||||
# Additional +options+ are:
|
||||
#
|
||||
# * <tt>:format</tt> - A regular expression to determine what to send as the
|
||||
# serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
|
||||
#
|
||||
# * <tt>:constraint</tt> - Whether to constrain the dragging to either
|
||||
# <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
|
||||
#
|
||||
# * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
|
||||
# or <tt>:vertical</tt> direction.
|
||||
#
|
||||
# * <tt>:tag</tt> - Which children of the container element to treat as
|
||||
# sortable (default is <tt>li</tt>).
|
||||
#
|
||||
# * <tt>:containment</tt> - Takes an element or array of elements to treat as
|
||||
# potential drop targets (defaults to the original target element).
|
||||
#
|
||||
# * <tt>:only</tt> - A CSS class name or array of class names used to filter
|
||||
# out child elements as candidates.
|
||||
#
|
||||
# * <tt>:scroll</tt> - Determines whether to scroll the list during drag
|
||||
# operations if the list runs past the visual border.
|
||||
#
|
||||
# * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
|
||||
# main sortable list. This means that you can create multi-layer lists,
|
||||
# and not only sort items at the same level, but drag and sort items
|
||||
# between levels.
|
||||
#
|
||||
# * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
|
||||
# when an accepted Draggable is hovered over it.
|
||||
#
|
||||
# * <tt>:handle</tt> - Sets whether the element should only be draggable by an
|
||||
# embedded handle. The value may be a string referencing a CSS class value
|
||||
# (as of script.aculo.us V1.5). The first child/grandchild/etc. element
|
||||
# found within the element that has this CSS class value will be used as
|
||||
# the handle.
|
||||
#
|
||||
# * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
|
||||
# the original in place until the clone is dropped (default is <tt>false</tt>).
|
||||
#
|
||||
# * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
|
||||
# a Droppable, that can receive a Draggable (as according to the containment
|
||||
# rules) as a child element when there are no more elements inside (default
|
||||
# is <tt>false</tt>).
|
||||
#
|
||||
# * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
|
||||
# dragging from one Sortable to another, the callback is called once on each
|
||||
# Sortable. Gets the affected element as its parameter.
|
||||
#
|
||||
# * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
|
||||
# changed in any way. When dragging from one Sortable to another, the callback
|
||||
# is called once on each Sortable. Gets the container as its parameter.
|
||||
#
|
||||
# See http://script.aculo.us for more documentation.
|
||||
def sortable_element(element_id, options = {})
|
||||
javascript_tag(sortable_element_js(element_id, options).chop!)
|
||||
end
|
||||
|
||||
def sortable_element_js(element_id, options = {}) #:nodoc:
|
||||
options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
|
||||
options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
|
||||
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
|
||||
|
||||
[:tag, :overlap, :constraint, :handle].each do |option|
|
||||
options[option] = "'#{options[option]}'" if options[option]
|
||||
end
|
||||
|
||||
options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
|
||||
options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
|
||||
|
||||
%(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ draggable.
|
||||
#
|
||||
# Example:
|
||||
# <%= draggable_element("my_image", :revert => true)
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def draggable_element(element_id, options = {})
|
||||
javascript_tag(draggable_element_js(element_id, options).chop!)
|
||||
end
|
||||
|
||||
def draggable_element_js(element_id, options = {}) #:nodoc:
|
||||
%(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ receive
|
||||
# dropped draggable elements (created by +draggable_element+).
|
||||
# and make an AJAX call. By default, the action called gets the DOM ID
|
||||
# of the element as parameter.
|
||||
#
|
||||
# Example:
|
||||
# <%= drop_receiving_element("my_cart", :url =>
|
||||
# { :controller => "cart", :action => "add" }) %>
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
#
|
||||
# Some of these +options+ include:
|
||||
# * <tt>:accept</tt> - Set this to a string or an array of strings describing the
|
||||
# allowable CSS classes that the +draggable_element+ must have in order
|
||||
# to be accepted by this +drop_receiving_element+.
|
||||
#
|
||||
# * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
|
||||
#
|
||||
# :confirm => "Are you sure you want to do this?"
|
||||
#
|
||||
# * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
|
||||
# this additional CSS class when an accepted +draggable_element+ is
|
||||
# hovered over it.
|
||||
#
|
||||
# * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
|
||||
# this element. Override this callback with a JavaScript expression to
|
||||
# change the default drop behaviour. Example:
|
||||
#
|
||||
# :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
|
||||
#
|
||||
# This callback gets three parameters: The Draggable element, the Droppable
|
||||
# element and the Event object. You can extract additional information about
|
||||
# the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
|
||||
#
|
||||
# * <tt>:with</tt> - A JavaScript expression specifying the parameters for
|
||||
# the XMLHttpRequest. Any expressions should return a valid URL query string.
|
||||
def drop_receiving_element(element_id, options = {})
|
||||
javascript_tag(drop_receiving_element_js(element_id, options).chop!)
|
||||
end
|
||||
|
||||
def drop_receiving_element_js(element_id, options = {}) #:nodoc:
|
||||
options[:with] ||= "'id=' + encodeURIComponent(element.id)"
|
||||
options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
|
||||
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
|
||||
|
||||
options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
|
||||
options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
|
||||
|
||||
# Confirmation happens during the onDrop callback, so it can be removed from the options
|
||||
options.delete(:confirm) if options[:confirm]
|
||||
|
||||
%(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -98,7 +98,7 @@ module ActionView #:nodoc:
|
||||
include Renderable
|
||||
|
||||
# Templates that are exempt from layouts
|
||||
@@exempt_from_layout = Set.new([/\.rjs$/])
|
||||
@@exempt_from_layout = Set.new
|
||||
|
||||
# Don't render layouts for templates with the given extensions.
|
||||
def self.exempt_from_layout(*extensions)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
module ActionView #:nodoc:
|
||||
module TemplateHandlers #:nodoc:
|
||||
autoload :ERB, 'action_view/template_handlers/erb'
|
||||
autoload :RJS, 'action_view/template_handlers/rjs'
|
||||
autoload :Builder, 'action_view/template_handlers/builder'
|
||||
|
||||
def self.extended(base)
|
||||
base.register_default_template_handler :erb, TemplateHandlers::ERB
|
||||
base.register_template_handler :rjs, TemplateHandlers::RJS
|
||||
base.register_template_handler :builder, TemplateHandlers::Builder
|
||||
|
||||
# TODO: Depreciate old template extensions
|
||||
|
||||
@@ -17,7 +17,7 @@ module ActionView
|
||||
src << "@output_buffer.safe_append='"
|
||||
src << "\n" * @newline_pending if @newline_pending > 0
|
||||
src << escape_text(text)
|
||||
src << "';"
|
||||
src << "'.freeze;"
|
||||
|
||||
@newline_pending = 0
|
||||
end
|
||||
@@ -63,7 +63,7 @@ module ActionView
|
||||
|
||||
def flush_newline_if_pending(src)
|
||||
if @newline_pending > 0
|
||||
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
|
||||
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
||||
@newline_pending = 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
module ActionView
|
||||
module TemplateHandlers
|
||||
class RJS < TemplateHandler
|
||||
include Compilable
|
||||
|
||||
def compile(template)
|
||||
"@template_format = :html;" +
|
||||
"controller.response.content_type ||= Mime::JS;" +
|
||||
"update_page do |page|;#{template.source}\nend"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -116,8 +116,7 @@ module ActionView
|
||||
end
|
||||
|
||||
def _view
|
||||
view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
|
||||
view.helpers.include master_helper_module
|
||||
view = self.class.master_helper_class.new(ActionController::Base.view_paths, _assigns, @controller)
|
||||
view.output_buffer = self.output_buffer
|
||||
view
|
||||
end
|
||||
|
||||
@@ -47,13 +47,6 @@ class AssertSelectTest < ActionController::TestCase
|
||||
@content = nil
|
||||
end
|
||||
|
||||
def rjs()
|
||||
render :update do |page|
|
||||
@update.call page
|
||||
end
|
||||
@update = nil
|
||||
end
|
||||
|
||||
def xml()
|
||||
render :text=>@content, :layout=>false, :content_type=>Mime::XML
|
||||
@content = nil
|
||||
@@ -220,43 +213,6 @@ class AssertSelectTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
# With single result.
|
||||
def test_assert_select_from_rjs_with_single_result
|
||||
render_rjs do |page|
|
||||
page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
|
||||
end
|
||||
assert_select "div" do |elements|
|
||||
assert elements.size == 2
|
||||
assert_select "#1"
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_select "div#?", /\d+/ do |elements|
|
||||
assert_select "#1"
|
||||
assert_select "#2"
|
||||
end
|
||||
end
|
||||
|
||||
# With multiple results.
|
||||
def test_assert_select_from_rjs_with_multiple_results
|
||||
render_rjs do |page|
|
||||
page.replace_html "test", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
end
|
||||
assert_select "div" do |elements|
|
||||
assert elements.size == 2
|
||||
assert_select "#1"
|
||||
assert_select "#2"
|
||||
end
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_positioned_insert_should_fail_when_mixing_arguments
|
||||
render_rjs do |page|
|
||||
page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
|
||||
page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
|
||||
end
|
||||
assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"}
|
||||
end
|
||||
|
||||
def test_elect_with_xml_namespace_attributes
|
||||
render_html %Q{<link xlink:href="http://nowhere.com"></link>}
|
||||
assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" }
|
||||
@@ -290,365 +246,6 @@ class AssertSelectTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
# With one result.
|
||||
def test_css_select_from_rjs_with_single_result
|
||||
render_rjs do |page|
|
||||
page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
|
||||
end
|
||||
assert_equal 2, css_select("div").size
|
||||
assert_equal 1, css_select("#1").size
|
||||
assert_equal 1, css_select("#2").size
|
||||
end
|
||||
|
||||
# With multiple results.
|
||||
def test_css_select_from_rjs_with_multiple_results
|
||||
render_rjs do |page|
|
||||
page.replace_html "test", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
end
|
||||
|
||||
assert_equal 2, css_select("div").size
|
||||
assert_equal 1, css_select("#1").size
|
||||
assert_equal 1, css_select("#2").size
|
||||
end
|
||||
|
||||
#
|
||||
# Test assert_select_rjs.
|
||||
#
|
||||
|
||||
# Test that we can pick up all statements in the result.
|
||||
def test_assert_select_rjs_picks_up_all_statements
|
||||
render_rjs do |page|
|
||||
page.replace "test", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
|
||||
end
|
||||
|
||||
found = false
|
||||
assert_select_rjs do
|
||||
assert_select "#1"
|
||||
assert_select "#2"
|
||||
assert_select "#3"
|
||||
found = true
|
||||
end
|
||||
assert found
|
||||
end
|
||||
|
||||
# Test that we fail if there is nothing to pick.
|
||||
def test_assert_select_rjs_fails_if_nothing_to_pick
|
||||
render_rjs { }
|
||||
assert_raise(Assertion) { assert_select_rjs }
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_with_unicode
|
||||
# Test that non-ascii characters (which are converted into \uXXXX in RJS) are decoded correctly.
|
||||
render_rjs do |page|
|
||||
page.replace "test", "<div id=\"1\">\343\203\201\343\202\261\343\203\203\343\203\210</div>"
|
||||
end
|
||||
assert_select_rjs do
|
||||
str = "#1"
|
||||
assert_select str, :text => "\343\203\201\343\202\261\343\203\203\343\203\210"
|
||||
assert_select str, "\343\203\201\343\202\261\343\203\203\343\203\210"
|
||||
if str.respond_to?(:force_encoding)
|
||||
str.force_encoding(Encoding::UTF_8)
|
||||
assert_select str, /\343\203\201..\343\203\210/u
|
||||
assert_raise(Assertion) { assert_select str, /\343\203\201.\343\203\210/u }
|
||||
else
|
||||
assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U')
|
||||
assert_raise(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_with_id
|
||||
# Test that we can pick up all statements in the result.
|
||||
render_rjs do |page|
|
||||
page.replace "test1", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
|
||||
end
|
||||
assert_select_rjs "test1" do
|
||||
assert_select "div", 1
|
||||
assert_select "#1"
|
||||
end
|
||||
assert_select_rjs "test2" do
|
||||
assert_select "div", 1
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_select_rjs "test3" do
|
||||
assert_select "div", 1
|
||||
assert_select "#3"
|
||||
end
|
||||
assert_raise(Assertion) { assert_select_rjs "test4" }
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_replace
|
||||
render_rjs do |page|
|
||||
page.replace "test1", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
|
||||
end
|
||||
# Replace.
|
||||
assert_select_rjs :replace do
|
||||
assert_select "div", 1
|
||||
assert_select "#1"
|
||||
end
|
||||
assert_select_rjs :replace, "test1" do
|
||||
assert_select "div", 1
|
||||
assert_select "#1"
|
||||
end
|
||||
assert_raise(Assertion) { assert_select_rjs :replace, "test2" }
|
||||
# Replace HTML.
|
||||
assert_select_rjs :replace_html do
|
||||
assert_select "div", 1
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_select_rjs :replace_html, "test2" do
|
||||
assert_select "div", 1
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" }
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_chained_replace
|
||||
render_rjs do |page|
|
||||
page['test1'].replace "<div id=\"1\">foo</div>"
|
||||
page['test2'].replace_html "<div id=\"2\">foo</div>"
|
||||
page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
|
||||
end
|
||||
# Replace.
|
||||
assert_select_rjs :chained_replace do
|
||||
assert_select "div", 1
|
||||
assert_select "#1"
|
||||
end
|
||||
assert_select_rjs :chained_replace, "test1" do
|
||||
assert_select "div", 1
|
||||
assert_select "#1"
|
||||
end
|
||||
assert_raise(Assertion) { assert_select_rjs :chained_replace, "test2" }
|
||||
# Replace HTML.
|
||||
assert_select_rjs :chained_replace_html do
|
||||
assert_select "div", 1
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_select_rjs :chained_replace_html, "test2" do
|
||||
assert_select "div", 1
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" }
|
||||
end
|
||||
|
||||
# Simple remove
|
||||
def test_assert_select_rjs_for_remove
|
||||
render_rjs do |page|
|
||||
page.remove "test1"
|
||||
end
|
||||
|
||||
assert_select_rjs :remove, "test1"
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_remove_offers_useful_error_when_assertion_fails
|
||||
render_rjs do |page|
|
||||
page.remove "test_with_typo"
|
||||
end
|
||||
|
||||
assert_select_rjs :remove, "test1"
|
||||
|
||||
rescue Assertion
|
||||
assert_equal "No RJS statement that removes 'test1' was rendered.", $!.message
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_remove_ignores_block
|
||||
render_rjs do |page|
|
||||
page.remove "test1"
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_select_rjs :remove, "test1" do
|
||||
assert_select "p"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Simple show
|
||||
def test_assert_select_rjs_for_show
|
||||
render_rjs do |page|
|
||||
page.show "test1"
|
||||
end
|
||||
|
||||
assert_select_rjs :show, "test1"
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_show_offers_useful_error_when_assertion_fails
|
||||
render_rjs do |page|
|
||||
page.show "test_with_typo"
|
||||
end
|
||||
|
||||
assert_select_rjs :show, "test1"
|
||||
|
||||
rescue Assertion
|
||||
assert_equal "No RJS statement that shows 'test1' was rendered.", $!.message
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_show_ignores_block
|
||||
render_rjs do |page|
|
||||
page.show "test1"
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_select_rjs :show, "test1" do
|
||||
assert_select "p"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Simple hide
|
||||
def test_assert_select_rjs_for_hide
|
||||
render_rjs do |page|
|
||||
page.hide "test1"
|
||||
end
|
||||
|
||||
assert_select_rjs :hide, "test1"
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_hide_offers_useful_error_when_assertion_fails
|
||||
render_rjs do |page|
|
||||
page.hide "test_with_typo"
|
||||
end
|
||||
|
||||
assert_select_rjs :hide, "test1"
|
||||
|
||||
rescue Assertion
|
||||
assert_equal "No RJS statement that hides 'test1' was rendered.", $!.message
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_hide_ignores_block
|
||||
render_rjs do |page|
|
||||
page.hide "test1"
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_select_rjs :hide, "test1" do
|
||||
assert_select "p"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Simple toggle
|
||||
def test_assert_select_rjs_for_toggle
|
||||
render_rjs do |page|
|
||||
page.toggle "test1"
|
||||
end
|
||||
|
||||
assert_select_rjs :toggle, "test1"
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_toggle_offers_useful_error_when_assertion_fails
|
||||
render_rjs do |page|
|
||||
page.toggle "test_with_typo"
|
||||
end
|
||||
|
||||
assert_select_rjs :toggle, "test1"
|
||||
|
||||
rescue Assertion
|
||||
assert_equal "No RJS statement that toggles 'test1' was rendered.", $!.message
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_for_toggle_ignores_block
|
||||
render_rjs do |page|
|
||||
page.toggle "test1"
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_select_rjs :toggle, "test1" do
|
||||
assert_select "p"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Non-positioned insert.
|
||||
def test_assert_select_rjs_for_nonpositioned_insert
|
||||
render_rjs do |page|
|
||||
page.replace "test1", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
|
||||
end
|
||||
assert_select_rjs :insert_html do
|
||||
assert_select "div", 1
|
||||
assert_select "#3"
|
||||
end
|
||||
assert_select_rjs :insert_html, "test3" do
|
||||
assert_select "div", 1
|
||||
assert_select "#3"
|
||||
end
|
||||
assert_raise(Assertion) { assert_select_rjs :insert_html, "test1" }
|
||||
end
|
||||
|
||||
# Positioned insert.
|
||||
def test_assert_select_rjs_for_positioned_insert
|
||||
render_rjs do |page|
|
||||
page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
|
||||
page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
|
||||
page.insert_html :before, "test3", "<div id=\"3\">foo</div>"
|
||||
page.insert_html :after, "test4", "<div id=\"4\">foo</div>"
|
||||
end
|
||||
assert_select_rjs :insert, :top do
|
||||
assert_select "div", 1
|
||||
assert_select "#1"
|
||||
end
|
||||
assert_select_rjs :insert, :bottom do
|
||||
assert_select "div", 1
|
||||
assert_select "#2"
|
||||
end
|
||||
assert_select_rjs :insert, :before do
|
||||
assert_select "div", 1
|
||||
assert_select "#3"
|
||||
end
|
||||
assert_select_rjs :insert, :after do
|
||||
assert_select "div", 1
|
||||
assert_select "#4"
|
||||
end
|
||||
assert_select_rjs :insert_html do
|
||||
assert_select "div", 4
|
||||
end
|
||||
end
|
||||
|
||||
def test_assert_select_rjs_raise_errors
|
||||
assert_raise(ArgumentError) { assert_select_rjs(:destroy) }
|
||||
assert_raise(ArgumentError) { assert_select_rjs(:insert, :left) }
|
||||
end
|
||||
|
||||
# Simple selection from a single result.
|
||||
def test_nested_assert_select_rjs_with_single_result
|
||||
render_rjs do |page|
|
||||
page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
|
||||
end
|
||||
|
||||
assert_select_rjs "test" do |elements|
|
||||
assert_equal 2, elements.size
|
||||
assert_select "#1"
|
||||
assert_select "#2"
|
||||
end
|
||||
end
|
||||
|
||||
# Deal with two results.
|
||||
def test_nested_assert_select_rjs_with_two_results
|
||||
render_rjs do |page|
|
||||
page.replace_html "test", "<div id=\"1\">foo</div>"
|
||||
page.replace_html "test2", "<div id=\"2\">foo</div>"
|
||||
end
|
||||
|
||||
assert_select_rjs "test" do |elements|
|
||||
assert_equal 1, elements.size
|
||||
assert_select "#1"
|
||||
end
|
||||
|
||||
assert_select_rjs "test2" do |elements|
|
||||
assert_equal 1, elements.size
|
||||
assert_select "#2"
|
||||
end
|
||||
end
|
||||
|
||||
def test_feed_item_encoded
|
||||
render_xml <<-EOF
|
||||
<rss version="2.0">
|
||||
@@ -725,11 +322,6 @@ EOF
|
||||
get :html
|
||||
end
|
||||
|
||||
def render_rjs(&block)
|
||||
@controller.response_with &block
|
||||
get :rjs
|
||||
end
|
||||
|
||||
def render_xml(xml)
|
||||
@controller.response_with = xml
|
||||
get :xml
|
||||
|
||||
@@ -622,6 +622,19 @@ class FragmentCachingTest < ActionController::TestCase
|
||||
assert_equal 'generated till now -> fragment content', buffer
|
||||
end
|
||||
|
||||
def test_fragment_for_bytesize
|
||||
buffer = "\xC4\x8D"
|
||||
buffer.force_encoding('ASCII-8BIT')
|
||||
|
||||
@controller.fragment_for(buffer, 'bytesize') do
|
||||
buffer.force_encoding('UTF-8')
|
||||
buffer << "abc"
|
||||
end
|
||||
|
||||
assert_equal Encoding::UTF_8, buffer.encoding
|
||||
assert_equal "abc", @store.read('views/bytesize')
|
||||
end
|
||||
|
||||
def test_html_safety
|
||||
assert_nil @store.read('views/name')
|
||||
content = 'value'.html_safe
|
||||
@@ -703,13 +716,6 @@ CACHED
|
||||
assert_match "Some cached content", @store.read('views/test.host/functional_caching/inline_fragment_cached')
|
||||
end
|
||||
|
||||
def test_fragment_caching_in_rjs_partials
|
||||
xhr :get, :js_fragment_cached_with_partial
|
||||
assert_response :success
|
||||
assert_match /Fragment caching in a partial/, @response.body
|
||||
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
|
||||
end
|
||||
|
||||
def test_html_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "html"
|
||||
assert_response :success
|
||||
@@ -729,15 +735,4 @@ CACHED
|
||||
|
||||
assert_equal " <p>Builder</p>\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
|
||||
def test_js_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "js"
|
||||
assert_response :success
|
||||
expected_body = %(title = "Hey";\n$("element_1").visualEffect("highlight");\n) +
|
||||
%($("element_2").visualEffect("highlight");\nfooter = "Bye";)
|
||||
assert_equal expected_body, @response.body
|
||||
|
||||
assert_equal ['$("element_1").visualEffect("highlight");', '$("element_2").visualEffect("highlight");'],
|
||||
@store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,9 +30,6 @@ class ContentTypeController < ActionController::Base
|
||||
def render_default_for_rxml
|
||||
end
|
||||
|
||||
def render_default_for_rjs
|
||||
end
|
||||
|
||||
def render_change_for_rxml
|
||||
response.content_type = Mime::HTML
|
||||
render :action => "render_default_for_rxml"
|
||||
@@ -123,12 +120,6 @@ class ContentTypeTest < ActionController::TestCase
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
|
||||
def test_default_for_rjs
|
||||
xhr :post, :render_default_for_rjs
|
||||
assert_equal Mime::JS, @response.content_type
|
||||
assert_equal "utf-8", @response.charset
|
||||
end
|
||||
|
||||
def test_change_for_rxml
|
||||
get :render_change_for_rxml
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
|
||||
@@ -45,7 +45,7 @@ class DispatcherTest < Test::Unit::TestCase
|
||||
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
|
||||
dispatcher = create_dispatcher(false)
|
||||
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||
dispatcher.expects(:build_middleware_stack).twice
|
||||
dispatcher.expects(:build_middleware_stack).never
|
||||
dispatcher.call(nil)
|
||||
Reloader.default_lock.unlock
|
||||
dispatcher.call(nil)
|
||||
|
||||
@@ -255,11 +255,6 @@ class MimeControllerTest < ActionController::TestCase
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal 'Hello world!', @response.body
|
||||
|
||||
@request.accept = "text/javascript"
|
||||
get :using_defaults
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
assert_equal '$("body").visualEffect("highlight");', @response.body
|
||||
|
||||
@request.accept = "application/xml"
|
||||
get :using_defaults
|
||||
assert_equal "application/xml", @response.content_type
|
||||
@@ -272,11 +267,6 @@ class MimeControllerTest < ActionController::TestCase
|
||||
assert_equal "text/html", @response.content_type
|
||||
assert_equal 'Hello world!', @response.body
|
||||
|
||||
@request.accept = "text/javascript"
|
||||
get :using_defaults_with_type_list
|
||||
assert_equal "text/javascript", @response.content_type
|
||||
assert_equal '$("body").visualEffect("highlight");', @response.body
|
||||
|
||||
@request.accept = "application/xml"
|
||||
get :using_defaults_with_type_list
|
||||
assert_equal "application/xml", @response.content_type
|
||||
@@ -372,26 +362,12 @@ class MimeControllerTest < ActionController::TestCase
|
||||
assert_equal 'Whatever you ask for, I got it', @response.body
|
||||
end
|
||||
|
||||
def test_rjs_type_skips_layout
|
||||
@request.accept = "text/javascript"
|
||||
get :all_types_with_layout
|
||||
assert_equal 'RJS for all_types_with_layout', @response.body
|
||||
end
|
||||
|
||||
def test_html_type_with_layout
|
||||
@request.accept = "text/html"
|
||||
get :all_types_with_layout
|
||||
assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
|
||||
end
|
||||
|
||||
def test_xhr
|
||||
xhr :get, :js_or_html
|
||||
assert_equal 'JS', @response.body
|
||||
|
||||
xhr :get, :using_defaults
|
||||
assert_equal '$("body").visualEffect("highlight");', @response.body
|
||||
end
|
||||
|
||||
def test_custom_constant
|
||||
get :custom_constant_handling, :format => "mobile"
|
||||
assert_equal "text/x-mobile", @response.content_type
|
||||
|
||||
@@ -5,7 +5,6 @@ class BaseRackTest < ActiveSupport::TestCase
|
||||
@env = {
|
||||
"HTTP_MAX_FORWARDS" => "10",
|
||||
"SERVER_NAME" => "glu.ttono.us",
|
||||
"FCGI_ROLE" => "RESPONDER",
|
||||
"AUTH_TYPE" => "Basic",
|
||||
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
|
||||
"HTTP_ACCEPT_CHARSET" => "UTF-8",
|
||||
|
||||
@@ -221,12 +221,6 @@ class TestController < ActionController::Base
|
||||
render :text => "hello world", :status => 404
|
||||
end
|
||||
|
||||
def render_custom_code_rjs
|
||||
render :update, :status => 404 do |page|
|
||||
page.replace :foo, :partial => 'partial'
|
||||
end
|
||||
end
|
||||
|
||||
def render_text_with_nil
|
||||
render :text => nil
|
||||
end
|
||||
@@ -448,30 +442,6 @@ class TestController < ActionController::Base
|
||||
render :template => "test/hello_world_from_rxml.builder"
|
||||
end
|
||||
|
||||
module RenderTestHelper
|
||||
def rjs_helper_method_from_module
|
||||
page.visual_effect :highlight
|
||||
end
|
||||
end
|
||||
|
||||
helper RenderTestHelper
|
||||
helper do
|
||||
def rjs_helper_method(value)
|
||||
page.visual_effect :highlight, value
|
||||
end
|
||||
end
|
||||
|
||||
def enum_rjs_test
|
||||
render :update do |page|
|
||||
page.select('.product').each do |value|
|
||||
page.rjs_helper_method_from_module
|
||||
page.rjs_helper_method(value)
|
||||
page.sortable(value, :url => { :action => "order" })
|
||||
page.draggable(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete_with_js
|
||||
@project_id = 4
|
||||
end
|
||||
@@ -486,28 +456,6 @@ class TestController < ActionController::Base
|
||||
render :action => 'delete_with_js'
|
||||
end
|
||||
|
||||
def update_page
|
||||
render :update do |page|
|
||||
page.replace_html 'balance', '$37,000,000.00'
|
||||
page.visual_effect :highlight, 'balance'
|
||||
end
|
||||
end
|
||||
|
||||
def update_page_with_instance_variables
|
||||
@money = '$37,000,000.00'
|
||||
@div_id = 'balance'
|
||||
render :update do |page|
|
||||
page.replace_html @div_id, @money
|
||||
page.visual_effect :highlight, @div_id
|
||||
end
|
||||
end
|
||||
|
||||
def update_page_with_view_method
|
||||
render :update do |page|
|
||||
page.replace_html 'person', pluralize(2, 'person')
|
||||
end
|
||||
end
|
||||
|
||||
def action_talk_to_layout
|
||||
# Action template sets variable that's picked up by layout
|
||||
end
|
||||
@@ -585,35 +533,10 @@ class TestController < ActionController::Base
|
||||
render :partial => 'partial.html.erb'
|
||||
end
|
||||
|
||||
def partial_as_rjs
|
||||
render :update do |page|
|
||||
page.replace :foo, :partial => 'partial'
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_partial_as_rjs
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace :foo, :partial => 'partial'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def partial
|
||||
render :partial => 'partial'
|
||||
end
|
||||
|
||||
def render_alternate_default
|
||||
# For this test, the method "default_render" is overridden:
|
||||
@alternate_default_render = lambda do
|
||||
render :update do |page|
|
||||
page.replace :foo, :partial => 'partial'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def partial_only_with_layout
|
||||
render :partial => "partial_only", :layout => true
|
||||
end
|
||||
@@ -944,12 +867,6 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal 'hello world', @response.body
|
||||
end
|
||||
|
||||
def test_render_custom_code_rjs
|
||||
get :render_custom_code_rjs
|
||||
assert_response 404
|
||||
assert_equal %(Element.replace("foo", "partial html");), @response.body
|
||||
end
|
||||
|
||||
def test_render_text_with_nil
|
||||
get :render_text_with_nil
|
||||
assert_response 200
|
||||
@@ -1023,20 +940,6 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal "<test>\n <hello/>\n</test>\n", @response.body
|
||||
end
|
||||
|
||||
def test_enum_rjs_test
|
||||
ActiveSupport::SecureRandom.stubs(:base64).returns("asdf")
|
||||
get :enum_rjs_test
|
||||
body = %{
|
||||
$$(".product").each(function(value, index) {
|
||||
new Effect.Highlight(element,{});
|
||||
new Effect.Highlight(value,{});
|
||||
Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value) + '&authenticity_token=' + encodeURIComponent('asdf')})}});
|
||||
new Draggable(value, {});
|
||||
});
|
||||
}.gsub(/^ /, '').strip
|
||||
assert_equal body, @response.body
|
||||
end
|
||||
|
||||
def test_layout_rendering
|
||||
get :layout_test
|
||||
assert_equal "<html>Hello world!</html>", @response.body
|
||||
@@ -1083,24 +986,6 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal "Goodbye, Local David", @response.body
|
||||
end
|
||||
|
||||
def test_render_in_an_rjs_template_should_pick_html_templates_when_available
|
||||
[:js, "js"].each do |format|
|
||||
assert_nothing_raised do
|
||||
get :render_implicit_html_template, :format => format
|
||||
assert_equal %(document.write("Hello world\\n");), @response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_explicitly_rendering_an_html_template_with_implicit_html_template_renders_should_be_possible_from_an_rjs_template
|
||||
[:js, "js"].each do |format|
|
||||
assert_nothing_raised do
|
||||
get :render_explicit_html_template, :format => format
|
||||
assert_equal %(document.write("Hello world\\n");), @response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_implicitly_render_html_template_from_xhr_request
|
||||
xhr :get, :render_implicit_html_template_from_xhr_request
|
||||
assert_equal "XHR!\nHello HTML!", @response.body
|
||||
@@ -1142,26 +1027,6 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal "application/atomsvc+xml", @response.content_type
|
||||
end
|
||||
|
||||
def test_render_with_default_from_accept_header
|
||||
xhr :get, :greeting
|
||||
assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
|
||||
end
|
||||
|
||||
def test_render_rjs_with_default
|
||||
get :delete_with_js
|
||||
assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
|
||||
end
|
||||
|
||||
def test_render_rjs_template_explicitly
|
||||
get :render_js_with_explicit_template
|
||||
assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
|
||||
end
|
||||
|
||||
def test_rendering_rjs_action_explicitly
|
||||
get :render_js_with_explicit_action_template
|
||||
assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
|
||||
end
|
||||
|
||||
def test_layout_test_with_different_layout
|
||||
get :layout_test_with_different_layout
|
||||
assert_equal "<html>Hello world!</html>", @response.body
|
||||
@@ -1267,28 +1132,6 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal "The secret is area51\n", @response.body
|
||||
end
|
||||
|
||||
def test_update_page
|
||||
get :update_page
|
||||
assert_template nil
|
||||
assert_equal 'text/javascript; charset=utf-8', @response.headers['Content-Type']
|
||||
assert_equal 2, @response.body.split($/).length
|
||||
end
|
||||
|
||||
def test_update_page_with_instance_variables
|
||||
get :update_page_with_instance_variables
|
||||
assert_template nil
|
||||
assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"]
|
||||
assert_match /balance/, @response.body
|
||||
assert_match /\$37/, @response.body
|
||||
end
|
||||
|
||||
def test_update_page_with_view_method
|
||||
get :update_page_with_view_method
|
||||
assert_template nil
|
||||
assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"]
|
||||
assert_match /2 people/, @response.body
|
||||
end
|
||||
|
||||
def test_yield_content_for
|
||||
assert_not_deprecated { get :yield_content_for }
|
||||
assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
|
||||
@@ -1414,26 +1257,11 @@ class RenderTest < ActionController::TestCase
|
||||
assert_equal 'partial html', @response.body
|
||||
end
|
||||
|
||||
def test_should_render_html_formatted_partial_with_rjs
|
||||
xhr :get, :partial_as_rjs
|
||||
assert_equal %(Element.replace("foo", "partial html");), @response.body
|
||||
end
|
||||
|
||||
def test_should_render_html_formatted_partial_with_rjs_and_js_format
|
||||
xhr :get, :respond_to_partial_as_rjs
|
||||
assert_equal %(Element.replace("foo", "partial html");), @response.body
|
||||
end
|
||||
|
||||
def test_should_render_js_partial
|
||||
xhr :get, :partial, :format => 'js'
|
||||
assert_equal 'partial js', @response.body
|
||||
end
|
||||
|
||||
def test_should_render_with_alternate_default_render
|
||||
xhr :get, :render_alternate_default
|
||||
assert_equal %(Element.replace("foo", "partial html");), @response.body
|
||||
end
|
||||
|
||||
def test_partial_only_with_layout
|
||||
get :partial_only_with_layout
|
||||
assert_equal "<html>only partial</html>", @response.body
|
||||
|
||||
@@ -357,24 +357,6 @@ class UrlWriterTests < ActionController::TestCase
|
||||
ActionController::Routing::Routes.load!
|
||||
end
|
||||
|
||||
def test_formatted_url_methods_are_deprecated
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.resources :posts
|
||||
end
|
||||
# We need to create a new class in order to install the new named route.
|
||||
kls = Class.new { include ActionController::UrlWriter }
|
||||
controller = kls.new
|
||||
params = {:id => 1, :format => :xml}
|
||||
assert_deprecated do
|
||||
assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
|
||||
end
|
||||
assert_deprecated do
|
||||
assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
|
||||
end
|
||||
ensure
|
||||
ActionController::Routing::Routes.load!
|
||||
end
|
||||
|
||||
def test_multiple_includes_maintain_distinct_options
|
||||
first_class = Class.new { include ActionController::UrlWriter }
|
||||
second_class = Class.new { include ActionController::UrlWriter }
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
page.alert 'hello world!'
|
||||
@@ -1,6 +0,0 @@
|
||||
page.assign 'title', 'Hey'
|
||||
cache do
|
||||
page['element_1'].visual_effect :highlight
|
||||
page['element_2'].visual_effect :highlight
|
||||
end
|
||||
page.assign 'footer', 'Bye'
|
||||
@@ -1 +0,0 @@
|
||||
page.replace_html 'notices', :partial => 'partial'
|
||||
@@ -1 +0,0 @@
|
||||
page << "RJS for all_types_with_layout"
|
||||
@@ -1 +0,0 @@
|
||||
page[:body].visual_effect :highlight
|
||||
@@ -1 +0,0 @@
|
||||
page[:body].visual_effect :highlight
|
||||
@@ -1,2 +0,0 @@
|
||||
page.remove 'person'
|
||||
page.visual_effect :highlight, "project-#{@project_id}"
|
||||
@@ -1,6 +0,0 @@
|
||||
page.select('.product').each do |value|
|
||||
page.visual_effect :highlight
|
||||
page.visual_effect :highlight, value
|
||||
page.sortable(value, :url => { :action => "order" })
|
||||
page.draggable(value)
|
||||
end
|
||||
@@ -1 +0,0 @@
|
||||
page[:body].visual_effect :highlight
|
||||
@@ -1 +0,0 @@
|
||||
page.call "document.write", render(:partial => "one.html.erb")
|
||||
@@ -1 +0,0 @@
|
||||
page.call "document.write", render(:partial => "one")
|
||||
@@ -175,6 +175,10 @@ class FormHelperTest < ActionView::TestCase
|
||||
I18n.locale = old_locale
|
||||
end
|
||||
|
||||
def test_label_with_for_attribute_as_nil
|
||||
assert_dom_equal('<label>Title</label>', label(:post, :title, nil, :for => nil))
|
||||
end
|
||||
|
||||
def test_label_with_for_attribute_as_symbol
|
||||
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
|
||||
end
|
||||
@@ -274,6 +278,11 @@ class FormHelperTest < ActionView::TestCase
|
||||
hidden_field("post", "title", :value => "Something Else")
|
||||
end
|
||||
|
||||
def test_text_field_with_id_as_nil
|
||||
assert_dom_equal '<input name="post[title]" type="hidden" value="Hello World" />',
|
||||
hidden_field("post", "title", :id => nil)
|
||||
end
|
||||
|
||||
def test_check_box
|
||||
assert_dom_equal(
|
||||
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
|
||||
@@ -1244,28 +1253,6 @@ class FormHelperTest < ActionView::TestCase
|
||||
|
||||
end
|
||||
|
||||
# Perhaps this test should be moved to prototype helper tests.
|
||||
def test_remote_form_for_with_labelled_builder
|
||||
failed_pre_200
|
||||
|
||||
self.extend ActionView::Helpers::PrototypeHelper
|
||||
|
||||
remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
|
||||
concat f.text_field(:title)
|
||||
concat f.text_area(:body)
|
||||
concat f.check_box(:secret)
|
||||
end
|
||||
|
||||
expected =
|
||||
%(<form action="http://www.example.com" onsubmit="new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" method="post">) +
|
||||
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
|
||||
"<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
|
||||
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" +
|
||||
"</form>"
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_fields_for_with_labelled_builder
|
||||
fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
|
||||
concat f.text_field(:title)
|
||||
@@ -1412,17 +1399,6 @@ class FormHelperTest < ActionView::TestCase
|
||||
assert_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_remote_form_for_with_html_options_adds_options_to_form_tag
|
||||
failed_pre_200
|
||||
|
||||
self.extend ActionView::Helpers::PrototypeHelper
|
||||
|
||||
remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
|
||||
expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\" onsubmit=\"new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\"></form>"
|
||||
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
protected
|
||||
def comments_path(post)
|
||||
"/posts/#{post.id}/comments"
|
||||
|
||||
@@ -16,74 +16,6 @@ class JavaScriptHelperTest < ActionView::TestCase
|
||||
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
|
||||
end
|
||||
|
||||
def test_link_to_function
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
|
||||
link_to_function("Greeting", "alert('Hello world!')")
|
||||
end
|
||||
|
||||
def test_link_to_function_with_existing_onclick
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>),
|
||||
link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
|
||||
end
|
||||
|
||||
def test_link_to_function_with_rjs_block
|
||||
html = link_to_function( "Greet me!" ) do |page|
|
||||
page.replace_html 'header', "<h1>Greetings</h1>"
|
||||
end
|
||||
assert_dom_equal %(<a href="#" onclick="Element.update("header", "\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E");; return false;">Greet me!</a>), html
|
||||
end
|
||||
|
||||
def test_link_to_function_with_rjs_block_and_options
|
||||
html = link_to_function( "Greet me!", :class => "updater" ) do |page|
|
||||
page.replace_html 'header', "<h1>Greetings</h1>"
|
||||
end
|
||||
assert_dom_equal %(<a href="#" class="updater" onclick="Element.update("header", "\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E");; return false;">Greet me!</a>), html
|
||||
end
|
||||
|
||||
def test_link_to_function_with_href
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
|
||||
link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
|
||||
end
|
||||
|
||||
def test_button_to_function
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
|
||||
button_to_function("Greeting", "alert('Hello world!')")
|
||||
end
|
||||
|
||||
def test_button_to_function_with_rjs_block
|
||||
html = button_to_function( "Greet me!" ) do |page|
|
||||
page.replace_html 'header', "<h1>Greetings</h1>"
|
||||
end
|
||||
assert_dom_equal %(<input type="button" onclick="Element.update("header", "\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E");;" value="Greet me!" />), html
|
||||
end
|
||||
|
||||
def test_button_to_function_with_rjs_block_and_options
|
||||
html = button_to_function( "Greet me!", :class => "greeter" ) do |page|
|
||||
page.replace_html 'header', "<h1>Greetings</h1>"
|
||||
end
|
||||
assert_dom_equal %(<input type="button" class="greeter" onclick="Element.update("header", "\\u003Ch1\\u003EGreetings\\u003C\/h1\\u003E");;" value="Greet me!" />), html
|
||||
end
|
||||
|
||||
def test_button_to_function_with_onclick
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal "<input onclick=\"alert('Goodbye World :('); alert('Hello world!');\" type=\"button\" value=\"Greeting\" />",
|
||||
button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
|
||||
end
|
||||
|
||||
def test_button_to_function_without_function
|
||||
assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />",
|
||||
button_to_function("Greeting")
|
||||
end
|
||||
|
||||
def test_javascript_tag
|
||||
self.output_buffer = 'foo'
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@ require 'abstract_unit'
|
||||
class NumberHelperTest < ActionView::TestCase
|
||||
tests ActionView::Helpers::NumberHelper
|
||||
|
||||
def test_number_helpers_escape_delimiter_and_separator
|
||||
assert_equal "$1<script></script>01", number_to_currency(1.01, :separator => "<script></script>")
|
||||
assert_equal "$1<script></script>000.00", number_to_currency(1000, :delimiter => "<script></script>")
|
||||
assert_equal "<script>1,000.00$</script>", number_to_currency(1000, :format => "<script>%n%u</script>")
|
||||
end
|
||||
|
||||
def test_number_to_phone
|
||||
assert_equal("555-1234", number_to_phone(5551234))
|
||||
assert_equal("800-555-1212", number_to_phone(8005551212))
|
||||
@@ -24,9 +30,10 @@ class NumberHelperTest < ActionView::TestCase
|
||||
assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
|
||||
assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0}))
|
||||
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
|
||||
assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""}))
|
||||
assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => raw("£"), :separator => ",", :delimiter => ""}))
|
||||
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""}))
|
||||
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
|
||||
assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"}))
|
||||
assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => raw("Kč"), :format => "%n %u"}))
|
||||
#assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation
|
||||
assert_equal("$x", number_to_currency("x"))
|
||||
assert_nil number_to_currency(nil)
|
||||
|
||||
@@ -1,666 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
Bunny = Struct.new(:Bunny, :id)
|
||||
|
||||
class Author
|
||||
attr_reader :id
|
||||
def save; @id = 1 end
|
||||
def new_record?; @id.nil? end
|
||||
def name
|
||||
@id.nil? ? 'new author' : "author ##{@id}"
|
||||
end
|
||||
end
|
||||
|
||||
class Article
|
||||
attr_reader :id
|
||||
attr_reader :author_id
|
||||
def save; @id = 1; @author_id = 1 end
|
||||
def new_record?; @id.nil? end
|
||||
def name
|
||||
@id.nil? ? 'new article' : "article ##{@id}"
|
||||
end
|
||||
end
|
||||
|
||||
class Author::Nested < Author; end
|
||||
|
||||
|
||||
class PrototypeHelperBaseTest < ActionView::TestCase
|
||||
attr_accessor :template_format, :output_buffer
|
||||
|
||||
def setup
|
||||
@template = self
|
||||
@controller = Class.new do
|
||||
def url_for(options)
|
||||
if options.is_a?(String)
|
||||
options
|
||||
else
|
||||
url = "http://www.example.com/"
|
||||
url << options[:action].to_s if options and options[:action]
|
||||
url << "?a=#{options[:a]}" if options && options[:a]
|
||||
url << "&b=#{options[:b]}" if options && options[:a] && options[:b]
|
||||
url
|
||||
end
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
protected
|
||||
def request_forgery_protection_token
|
||||
nil
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
false
|
||||
end
|
||||
|
||||
def create_generator
|
||||
block = Proc.new { |*args| yield *args if block_given? }
|
||||
JavaScriptGenerator.new self, &block
|
||||
end
|
||||
end
|
||||
|
||||
class PrototypeHelperTest < PrototypeHelperBaseTest
|
||||
def setup
|
||||
@record = @author = Author.new
|
||||
@article = Article.new
|
||||
super
|
||||
end
|
||||
|
||||
def test_link_to_remote
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }}, { :class => "fine" })
|
||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", :complete => "alert(request.responseText)", :url => { :action => "whatnot" })
|
||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", :success => "alert(request.responseText)", :url => { :action => "whatnot" })
|
||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot" })
|
||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
|
||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:false, evalScripts:true}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :type => :synchronous)
|
||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, insertion:'bottom'}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :position => :bottom)
|
||||
end
|
||||
|
||||
def test_link_to_remote_html_options
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>),
|
||||
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } })
|
||||
end
|
||||
|
||||
def test_link_to_remote_url_quote_escaping
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<a href="#" onclick="new Ajax.Request('http://www.example.com/whatnot\\\'s', {asynchronous:true, evalScripts:true}); return false;">Remote</a>),
|
||||
link_to_remote("Remote", { :url => { :action => "whatnot's" } })
|
||||
end
|
||||
|
||||
def test_button_to_remote
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<input class=\"fine\" type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true});\" />),
|
||||
button_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
|
||||
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}});\" />),
|
||||
button_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" })
|
||||
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}});\" />),
|
||||
button_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" })
|
||||
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}});\" />),
|
||||
button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" })
|
||||
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}});\" />),
|
||||
button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
|
||||
end
|
||||
|
||||
def test_periodically_call_remote
|
||||
assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
|
||||
periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
|
||||
end
|
||||
|
||||
def test_periodically_call_remote_with_frequency
|
||||
assert_dom_equal(
|
||||
"<script type=\"text/javascript\">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true})}, 2)\n//]]>\n</script>",
|
||||
periodically_call_remote(:frequency => 2)
|
||||
)
|
||||
end
|
||||
|
||||
def test_form_remote_tag
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast })
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
||||
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast })
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
||||
form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast })
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
||||
form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast })
|
||||
end
|
||||
|
||||
def test_form_remote_tag_with_method
|
||||
failed_pre_200
|
||||
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\"><div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :html => { :method => :put })
|
||||
end
|
||||
|
||||
def test_form_remote_tag_with_block_in_erb
|
||||
failed_pre_200
|
||||
|
||||
__in_erb_template = ''
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) { concat "Hello world!" }
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">Hello world!</form>), output_buffer
|
||||
end
|
||||
|
||||
def test_remote_form_for_with_record_identification_with_new_record
|
||||
failed_pre_200
|
||||
|
||||
remote_form_for(@record, {:html => { :id => 'create-author' }}) {}
|
||||
|
||||
expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' id='create-author' method='post'></form>)
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_remote_form_for_with_record_identification_without_html_options
|
||||
failed_pre_200
|
||||
|
||||
remote_form_for(@record) {}
|
||||
|
||||
expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' method='post' id='new_author'></form>)
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_remote_form_for_with_record_identification_with_existing_record
|
||||
failed_pre_200
|
||||
|
||||
@record.save
|
||||
remote_form_for(@record) {}
|
||||
|
||||
expected = %(<form action='#{author_path(@record)}' id='edit_author_1' method='post' onsubmit="new Ajax.Request('#{author_path(@record)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_author'><div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div></form>)
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_remote_form_for_with_new_object_in_list
|
||||
failed_pre_200
|
||||
|
||||
remote_form_for([@author, @article]) {}
|
||||
|
||||
expected = %(<form action='#{author_articles_path(@author)}' onsubmit="new Ajax.Request('#{author_articles_path(@author)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_article' method='post' id='new_article'></form>)
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_remote_form_for_with_existing_object_in_list
|
||||
failed_pre_200
|
||||
|
||||
@author.save
|
||||
@article.save
|
||||
remote_form_for([@author, @article]) {}
|
||||
|
||||
expected = %(<form action='#{author_article_path(@author, @article)}' id='edit_article_1' method='post' onsubmit="new Ajax.Request('#{author_article_path(@author, @article)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_article'><div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div></form>)
|
||||
assert_dom_equal expected, output_buffer
|
||||
end
|
||||
|
||||
def test_on_callbacks
|
||||
failed_pre_200
|
||||
|
||||
callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
|
||||
callbacks.each do |callback|
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
|
||||
end
|
||||
|
||||
#HTTP status codes 200 up to 599 have callbacks
|
||||
#these should work
|
||||
100.upto(599) do |callback|
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
||||
end
|
||||
|
||||
#test 200 and 404
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
|
||||
|
||||
#these shouldn't
|
||||
1.upto(99) do |callback|
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
||||
end
|
||||
600.upto(999) do |callback|
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
||||
end
|
||||
|
||||
#test ultimate combo
|
||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">),
|
||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
|
||||
|
||||
end
|
||||
|
||||
def test_submit_to_remote
|
||||
failed_pre_200
|
||||
assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});\" type=\"button\" value=\"1000000\" />),
|
||||
submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
|
||||
end
|
||||
|
||||
def test_observe_field
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
|
||||
observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
|
||||
end
|
||||
|
||||
def test_observe_field_using_with_option
|
||||
expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(value)})})\n//]]>\n</script>)
|
||||
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id')
|
||||
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "'id=' + encodeURIComponent(value)")
|
||||
end
|
||||
|
||||
def test_observe_field_using_json_in_with_option
|
||||
expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:{'id':value}})})\n//]]>\n</script>)
|
||||
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}")
|
||||
end
|
||||
|
||||
def test_observe_field_using_function_for_callback
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {alert('Element changed')})\n//]]>\n</script>),
|
||||
observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')")
|
||||
end
|
||||
|
||||
def test_observe_form
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
|
||||
observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
|
||||
end
|
||||
|
||||
def test_observe_form_using_function_for_callback
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>),
|
||||
observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
|
||||
end
|
||||
|
||||
def test_observe_field_without_frequency
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.EventObserver('glass', function(element, value) {new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
|
||||
observe_field("glass")
|
||||
end
|
||||
|
||||
def test_update_page
|
||||
old_output_buffer = output_buffer
|
||||
|
||||
block = Proc.new { |page| page.replace_html('foo', 'bar') }
|
||||
assert_equal create_generator(&block).to_s, update_page(&block)
|
||||
|
||||
assert_equal old_output_buffer, output_buffer
|
||||
end
|
||||
|
||||
def test_update_page_tag
|
||||
block = Proc.new { |page| page.replace_html('foo', 'bar') }
|
||||
assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
|
||||
end
|
||||
|
||||
def test_update_page_tag_with_html_options
|
||||
block = Proc.new { |page| page.replace_html('foo', 'bar') }
|
||||
assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block)
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def author_path(record)
|
||||
"/authors/#{record.id}"
|
||||
end
|
||||
|
||||
def authors_path
|
||||
"/authors"
|
||||
end
|
||||
|
||||
def author_articles_path(author)
|
||||
"/authors/#{author.id}/articles"
|
||||
end
|
||||
|
||||
def author_article_path(author, article)
|
||||
"/authors/#{author.id}/articles/#{article.id}"
|
||||
end
|
||||
end
|
||||
|
||||
class JavaScriptGeneratorTest < PrototypeHelperBaseTest
|
||||
def setup
|
||||
super
|
||||
@generator = create_generator
|
||||
end
|
||||
|
||||
def test_insert_html_with_string
|
||||
assert_equal 'Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });',
|
||||
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
|
||||
assert_equal 'Element.insert("element", { bottom: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
|
||||
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
|
||||
assert_equal 'Element.insert("element", { before: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
|
||||
@generator.insert_html(:before, 'element', '<p>This is a test</p>')
|
||||
assert_equal 'Element.insert("element", { after: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
|
||||
@generator.insert_html(:after, 'element', '<p>This is a test</p>')
|
||||
end
|
||||
|
||||
def test_replace_html_with_string
|
||||
assert_equal 'Element.update("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
|
||||
@generator.replace_html('element', '<p>This is a test</p>')
|
||||
end
|
||||
|
||||
def test_replace_element_with_string
|
||||
assert_equal 'Element.replace("element", "\\u003Cdiv id=\"element\"\\u003E\\u003Cp\\u003EThis is a test\\u003C/p\\u003E\\u003C/div\\u003E");',
|
||||
@generator.replace('element', '<div id="element"><p>This is a test</p></div>')
|
||||
end
|
||||
|
||||
def test_remove
|
||||
assert_equal 'Element.remove("foo");',
|
||||
@generator.remove('foo')
|
||||
assert_equal '["foo","bar","baz"].each(Element.remove);',
|
||||
@generator.remove('foo', 'bar', 'baz')
|
||||
end
|
||||
|
||||
def test_show
|
||||
assert_equal 'Element.show("foo");',
|
||||
@generator.show('foo')
|
||||
assert_equal '["foo","bar","baz"].each(Element.show);',
|
||||
@generator.show('foo', 'bar', 'baz')
|
||||
end
|
||||
|
||||
def test_hide
|
||||
assert_equal 'Element.hide("foo");',
|
||||
@generator.hide('foo')
|
||||
assert_equal '["foo","bar","baz"].each(Element.hide);',
|
||||
@generator.hide('foo', 'bar', 'baz')
|
||||
end
|
||||
|
||||
def test_toggle
|
||||
assert_equal 'Element.toggle("foo");',
|
||||
@generator.toggle('foo')
|
||||
assert_equal '["foo","bar","baz"].each(Element.toggle);',
|
||||
@generator.toggle('foo', 'bar', 'baz')
|
||||
end
|
||||
|
||||
def test_alert
|
||||
assert_equal 'alert("hello");', @generator.alert('hello')
|
||||
end
|
||||
|
||||
def test_redirect_to
|
||||
assert_equal 'window.location.href = "http://www.example.com/welcome";',
|
||||
@generator.redirect_to(:action => 'welcome')
|
||||
assert_equal 'window.location.href = "http://www.example.com/welcome?a=b&c=d";',
|
||||
@generator.redirect_to("http://www.example.com/welcome?a=b&c=d")
|
||||
end
|
||||
|
||||
def test_reload
|
||||
assert_equal 'window.location.reload();',
|
||||
@generator.reload
|
||||
end
|
||||
|
||||
def test_delay
|
||||
@generator.delay(20) do
|
||||
@generator.hide('foo')
|
||||
end
|
||||
|
||||
assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
|
||||
end
|
||||
|
||||
def test_to_s
|
||||
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
|
||||
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
|
||||
@generator.remove('foo', 'bar')
|
||||
@generator.replace_html('baz', '<p>This is a test</p>')
|
||||
|
||||
assert_equal <<-EOS.chomp, @generator.to_s
|
||||
Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
|
||||
Element.insert("element", { bottom: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
|
||||
["foo","bar"].each(Element.remove);
|
||||
Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_element_access
|
||||
assert_equal %($("hello");), @generator['hello']
|
||||
end
|
||||
|
||||
def test_element_access_on_records
|
||||
assert_equal %($("bunny_5");), @generator[Bunny.new(:id => 5)]
|
||||
assert_equal %($("new_bunny");), @generator[Bunny.new]
|
||||
end
|
||||
|
||||
def test_element_proxy_one_deep
|
||||
@generator['hello'].hide
|
||||
assert_equal %($("hello").hide();), @generator.to_s
|
||||
end
|
||||
|
||||
def test_element_proxy_variable_access
|
||||
@generator['hello']['style']
|
||||
assert_equal %($("hello").style;), @generator.to_s
|
||||
end
|
||||
|
||||
def test_element_proxy_variable_access_with_assignment
|
||||
@generator['hello']['style']['color'] = 'red'
|
||||
assert_equal %($("hello").style.color = "red";), @generator.to_s
|
||||
end
|
||||
|
||||
def test_element_proxy_assignment
|
||||
@generator['hello'].width = 400
|
||||
assert_equal %($("hello").width = 400;), @generator.to_s
|
||||
end
|
||||
|
||||
def test_element_proxy_two_deep
|
||||
@generator['hello'].hide("first").clean_whitespace
|
||||
assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
|
||||
end
|
||||
|
||||
def test_select_access
|
||||
assert_equal %($$("div.hello");), @generator.select('div.hello')
|
||||
end
|
||||
|
||||
def test_select_proxy_one_deep
|
||||
@generator.select('p.welcome b').first.hide
|
||||
assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
|
||||
end
|
||||
|
||||
def test_visual_effect
|
||||
assert_equal %(new Effect.Puff("blah",{});),
|
||||
@generator.visual_effect(:puff,'blah')
|
||||
end
|
||||
|
||||
def test_visual_effect_toggle
|
||||
assert_equal %(Effect.toggle("blah",'appear',{});),
|
||||
@generator.visual_effect(:toggle_appear,'blah')
|
||||
end
|
||||
|
||||
def test_sortable
|
||||
assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
|
||||
@generator.sortable('blah', :url => { :action => "order" })
|
||||
assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
|
||||
@generator.sortable('blah', :url => { :action => "order" }, :type => :synchronous)
|
||||
end
|
||||
|
||||
def test_draggable
|
||||
assert_equal %(new Draggable("blah", {});),
|
||||
@generator.draggable('blah')
|
||||
end
|
||||
|
||||
def test_drop_receiving
|
||||
assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
|
||||
@generator.drop_receiving('blah', :url => { :action => "order" })
|
||||
assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
|
||||
@generator.drop_receiving('blah', :url => { :action => "order" }, :type => :synchronous)
|
||||
end
|
||||
|
||||
def test_collection_first_and_last
|
||||
@generator.select('p.welcome b').first.hide()
|
||||
@generator.select('p.welcome b').last.show()
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
$$("p.welcome b").first().hide();
|
||||
$$("p.welcome b").last().show();
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_each
|
||||
@generator.select('p.welcome b').each do |value|
|
||||
value.remove_class_name 'selected'
|
||||
end
|
||||
@generator.select('p.welcome b').each do |value, index|
|
||||
@generator.visual_effect :highlight, value
|
||||
end
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
$$("p.welcome b").each(function(value, index) {
|
||||
value.removeClassName("selected");
|
||||
});
|
||||
$$("p.welcome b").each(function(value, index) {
|
||||
new Effect.Highlight(value,{});
|
||||
});
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_on_collect
|
||||
@generator.select('p').collect('a') { |para| para.show }
|
||||
@generator.select('p').collect { |para| para.hide }
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = $$("p").collect(function(value, index) {
|
||||
return value.show();
|
||||
});
|
||||
$$("p").collect(function(value, index) {
|
||||
return value.hide();
|
||||
});
|
||||
EOS
|
||||
@generator = create_generator
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_grep
|
||||
@generator.select('p').grep 'a', /^a/ do |value|
|
||||
@generator << '(value.className == "welcome")'
|
||||
end
|
||||
@generator.select('p').grep 'b', /b$/ do |value, index|
|
||||
@generator.call 'alert', value
|
||||
@generator << '(value.className == "welcome")'
|
||||
end
|
||||
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = $$("p").grep(/^a/, function(value, index) {
|
||||
return (value.className == "welcome");
|
||||
});
|
||||
var b = $$("p").grep(/b$/, function(value, index) {
|
||||
alert(value);
|
||||
return (value.className == "welcome");
|
||||
});
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_inject
|
||||
@generator.select('p').inject 'a', [] do |memo, value|
|
||||
@generator << '(value.className == "welcome")'
|
||||
end
|
||||
@generator.select('p').inject 'b', nil do |memo, value, index|
|
||||
@generator.call 'alert', memo
|
||||
@generator << '(value.className == "welcome")'
|
||||
end
|
||||
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = $$("p").inject([], function(memo, value, index) {
|
||||
return (value.className == "welcome");
|
||||
});
|
||||
var b = $$("p").inject(null, function(memo, value, index) {
|
||||
alert(memo);
|
||||
return (value.className == "welcome");
|
||||
});
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_pluck
|
||||
@generator.select('p').pluck('a', 'className')
|
||||
assert_equal %(var a = $$("p").pluck("className");), @generator.to_s
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_zip
|
||||
ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
|
||||
ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |array|
|
||||
@generator.call 'array.reverse'
|
||||
end
|
||||
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = [1, 2, 3].zip([4,5,6], [7,8,9]);
|
||||
var b = [1, 2, 3].zip([4,5,6], [7,8,9], function(array) {
|
||||
return array.reverse();
|
||||
});
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_find_all
|
||||
@generator.select('p').find_all 'a' do |value, index|
|
||||
@generator << '(value.className == "welcome")'
|
||||
end
|
||||
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = $$("p").findAll(function(value, index) {
|
||||
return (value.className == "welcome");
|
||||
});
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_in_groups_of
|
||||
@generator.select('p').in_groups_of('a', 3)
|
||||
@generator.select('p').in_groups_of('a', 3, 'x')
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = $$("p").inGroupsOf(3);
|
||||
var a = $$("p").inGroupsOf(3, "x");
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_collection_proxy_with_each_slice
|
||||
@generator.select('p').each_slice('a', 3)
|
||||
@generator.select('p').each_slice('a', 3) do |group, index|
|
||||
group.reverse
|
||||
end
|
||||
|
||||
assert_equal <<-EOS.strip, @generator.to_s
|
||||
var a = $$("p").eachSlice(3);
|
||||
var a = $$("p").eachSlice(3, function(value, index) {
|
||||
return value.reverse();
|
||||
});
|
||||
EOS
|
||||
end
|
||||
|
||||
def test_debug_rjs
|
||||
ActionView::Base.debug_rjs = true
|
||||
@generator['welcome'].replace_html 'Welcome'
|
||||
assert_equal "try {\n$(\"welcome\").update(\"Welcome\");\n} catch (e) { alert('RJS error:\\n\\n' + e.toString()); alert('$(\\\"welcome\\\").update(\\\"Welcome\\\");'); throw e }", @generator.to_s
|
||||
ensure
|
||||
ActionView::Base.debug_rjs = false
|
||||
end
|
||||
|
||||
def test_literal
|
||||
literal = @generator.literal("function() {}")
|
||||
assert_equal "function() {}", ActiveSupport::JSON.encode(literal)
|
||||
assert_equal "", @generator.to_s
|
||||
end
|
||||
|
||||
def test_class_proxy
|
||||
@generator.form.focus('my_field')
|
||||
assert_equal "Form.focus(\"my_field\");", @generator.to_s
|
||||
end
|
||||
|
||||
def test_call_with_block
|
||||
@generator.call(:before)
|
||||
@generator.call(:my_method) do |p|
|
||||
p[:one].show
|
||||
p[:two].hide
|
||||
end
|
||||
@generator.call(:in_between)
|
||||
@generator.call(:my_method_with_arguments, true, "hello") do |p|
|
||||
p[:three].visual_effect(:highlight)
|
||||
end
|
||||
assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s
|
||||
end
|
||||
|
||||
def test_class_proxy_call_with_block
|
||||
@generator.my_object.my_method do |p|
|
||||
p[:one].show
|
||||
p[:two].hide
|
||||
end
|
||||
assert_equal "MyObject.myMethod(function() { $(\"one\").show();\n$(\"two\").hide(); });", @generator.to_s
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,8 +9,8 @@ module RenderTestCases
|
||||
|
||||
# Reload and register danish language for testing
|
||||
I18n.reload!
|
||||
I18n.backend.store_translations 'da', {}
|
||||
I18n.backend.store_translations 'pt-BR', {}
|
||||
I18n.backend.store_translations 'da', 'da' => {}
|
||||
I18n.backend.store_translations 'pt-BR', 'pt-BR' => {}
|
||||
|
||||
# Ensure original are still the same since we are reindexing view paths
|
||||
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
|
||||
@@ -89,12 +89,6 @@ module RenderTestCases
|
||||
assert_equal "test/template.erb", @view.render(:file => "test/template.erb")
|
||||
end
|
||||
|
||||
def test_render_update
|
||||
# TODO: You should not have to stub out template because template is self!
|
||||
@view.instance_variable_set(:@template, @view)
|
||||
assert_equal 'alert("Hello, World!");', @view.render(:update) { |page| page.alert('Hello, World!') }
|
||||
end
|
||||
|
||||
def test_render_partial_from_default
|
||||
assert_equal "only partial", @view.render("test/partial_only")
|
||||
end
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class ScriptaculousHelperTest < ActionView::TestCase
|
||||
tests ActionView::Helpers::ScriptaculousHelper
|
||||
|
||||
def setup
|
||||
@controller = Class.new do
|
||||
def url_for(options)
|
||||
url = "http://www.example.com/"
|
||||
url << options[:action].to_s if options and options[:action]
|
||||
url
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
def test_effect
|
||||
assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, "posts")
|
||||
assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect("highlight", :posts)
|
||||
assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, :posts)
|
||||
assert_equal "new Effect.Fade(\"fademe\",{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
|
||||
assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
|
||||
assert_equal "new Effect.DropOut(\"dropme\",{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
|
||||
assert_equal "new Effect.Highlight(\"status\",{endcolor:'#EEEEEE'});", visual_effect(:highlight, 'status', :endcolor => '#EEEEEE')
|
||||
assert_equal "new Effect.Highlight(\"status\",{restorecolor:'#500000', startcolor:'#FEFEFE'});", visual_effect(:highlight, 'status', :restorecolor => '#500000', :startcolor => '#FEFEFE')
|
||||
|
||||
# chop the queue params into a comma separated list
|
||||
beginning, ending = 'new Effect.DropOut("dropme",{queue:{', '}});'
|
||||
ve = [
|
||||
visual_effect(:drop_out, 'dropme', :queue => {:position => "end", :scope => "test", :limit => 2}),
|
||||
visual_effect(:drop_out, 'dropme', :queue => {:scope => :list, :limit => 2}),
|
||||
visual_effect(:drop_out, 'dropme', :queue => {:position => :end, :scope => :test, :limit => 2})
|
||||
].collect { |v| v[beginning.length..-ending.length-1].split(',') }
|
||||
|
||||
assert ve[0].include?("limit:2")
|
||||
assert ve[0].include?("scope:'test'")
|
||||
assert ve[0].include?("position:'end'")
|
||||
|
||||
assert ve[1].include?("limit:2")
|
||||
assert ve[1].include?("scope:'list'")
|
||||
|
||||
assert ve[2].include?("limit:2")
|
||||
assert ve[2].include?("scope:'test'")
|
||||
assert ve[2].include?("position:'end'")
|
||||
end
|
||||
|
||||
def test_toggle_effects
|
||||
assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect(:toggle_appear, "posts")
|
||||
assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect(:toggle_slide, "posts")
|
||||
assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect(:toggle_blind, "posts")
|
||||
assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect("toggle_appear", "posts")
|
||||
assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect("toggle_slide", "posts")
|
||||
assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect("toggle_blind", "posts")
|
||||
end
|
||||
|
||||
|
||||
def test_sortable_element
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
|
||||
sortable_element("mylist", :url => { :action => "order" })
|
||||
assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}, tag:'div'})\n//]]>\n</script>),
|
||||
sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
|
||||
assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>|,
|
||||
sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
|
||||
sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
|
||||
end
|
||||
|
||||
def test_draggable_element
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {})\n//]]>\n</script>),
|
||||
draggable_element("product_13")
|
||||
assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {revert:true})\n//]]>\n</script>),
|
||||
draggable_element("product_13", :revert => true)
|
||||
end
|
||||
|
||||
def test_drop_receiving_element
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
||||
drop_receiving_element("droptarget1")
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
||||
drop_receiving_element("droptarget1", :accept => 'products')
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
||||
drop_receiving_element("droptarget1", :accept => 'products', :update => 'infobox')
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
||||
drop_receiving_element("droptarget1", :accept => ['tshirts','mugs'], :update => 'infobox')
|
||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add("droptarget1", {hoverclass:'dropready', onDrop:function(element){if (confirm('Are you sure?')) { new Ajax.Request('http://www.example.com/update_drop', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)}); }}})\n//]]>\n</script>),
|
||||
drop_receiving_element('droptarget1', :hoverclass=>'dropready', :url=>{:action=>'update_drop'}, :confirm => 'Are you sure?')
|
||||
|
||||
end
|
||||
def protect_against_forgery?
|
||||
false
|
||||
end
|
||||
end
|
||||
@@ -64,7 +64,6 @@ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase op
|
||||
end
|
||||
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
|
||||
t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
|
||||
t.verbose = true
|
||||
}
|
||||
|
||||
namespace adapter do
|
||||
|
||||
19
activerecord/activerecord.gemspec
Normal file
19
activerecord/activerecord.gemspec
Normal file
@@ -0,0 +1,19 @@
|
||||
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).chomp
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'activerecord'
|
||||
s.version = version
|
||||
s.summary = 'Implements the ActiveRecord pattern for ORM.'
|
||||
s.description = '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.author = 'David Heinemeier Hansson'
|
||||
s.email = 'david@loudthinking.com'
|
||||
s.homepage = 'http://www.rubyonrails.org'
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.files = ['README']
|
||||
s.rdoc_options = ['--main', 'README']
|
||||
s.extra_rdoc_files = ['README']
|
||||
|
||||
s.add_dependency 'activesupport', "= #{version}"
|
||||
end
|
||||
@@ -47,14 +47,29 @@ module ActiveRecord
|
||||
# instantiation of the actual post records.
|
||||
class AssociationProxy #:nodoc:
|
||||
alias_method :proxy_respond_to?, :respond_to?
|
||||
alias_method :proxy_extend, :extend
|
||||
delegate :to_param, :to => :proxy_target
|
||||
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id)$|^__|^respond_to_missing|proxy_/ }
|
||||
|
||||
def self.new(owner, reflection)
|
||||
klass =
|
||||
reflection.cached_extend_class ||=
|
||||
if reflection.options[:extend]
|
||||
const_name = "AR_CACHED_EXTEND_CLASS_#{reflection.name}_#{reflection.options[:extend].join("_").gsub("::","_")}"
|
||||
reflection.active_record.const_set(const_name, Class.new(self) do
|
||||
include *reflection.options[:extend]
|
||||
end)
|
||||
else
|
||||
self
|
||||
end
|
||||
|
||||
proxy = klass.allocate
|
||||
proxy.send(:initialize, owner, reflection)
|
||||
proxy
|
||||
end
|
||||
|
||||
def initialize(owner, reflection)
|
||||
@owner, @reflection = owner, reflection
|
||||
reflection.check_validity!
|
||||
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
||||
reset
|
||||
end
|
||||
|
||||
|
||||
@@ -2920,15 +2920,23 @@ module ActiveRecord #:nodoc:
|
||||
attributes.each do |k, v|
|
||||
if k.to_s.include?("(")
|
||||
multiparameter_attributes << [ k, v ]
|
||||
elsif RUBY_VERSION == "1.9.3"
|
||||
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
|
||||
else
|
||||
(respond_to?(:"#{k}=", true) && !method(:"#{k}=").private?) ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
|
||||
_assign_attribute(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
assign_multiparameter_attributes(multiparameter_attributes) unless multiparameter_attributes.empty?
|
||||
end
|
||||
|
||||
def _assign_attribute(k, v)
|
||||
public_send("#{k}=", v)
|
||||
rescue NoMethodError => e
|
||||
if respond_to?("#{k}=")
|
||||
raise e
|
||||
else
|
||||
raise UnknownAttributeError, "unknown attribute: #{k}"
|
||||
end
|
||||
end
|
||||
|
||||
def create_or_update
|
||||
raise ReadOnlyRecord if readonly?
|
||||
|
||||
@@ -261,7 +261,7 @@ module ActiveRecord
|
||||
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
|
||||
end
|
||||
|
||||
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
|
||||
calculated_data.inject({}) do |all, row|
|
||||
key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
|
||||
key = key.first if key.size == 1
|
||||
key = key_records[key] if associated
|
||||
|
||||
@@ -195,7 +195,9 @@ module ActiveRecord
|
||||
def log_info(sql, name, ms)
|
||||
if @logger && @logger.debug?
|
||||
name = '%s (%.1fms)' % [name || 'SQL', ms]
|
||||
sql.force_encoding 'binary' if sql.respond_to?(:force_encoding)
|
||||
if sql.respond_to?(:force_encoding)
|
||||
sql = sql.dup.force_encoding 'binary'
|
||||
end
|
||||
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
|
||||
end
|
||||
end
|
||||
@@ -212,13 +214,7 @@ module ActiveRecord
|
||||
log_info(sql, name, 0)
|
||||
nil
|
||||
end
|
||||
rescue SystemExit, SignalException, NoMemoryError => e
|
||||
# Don't re-wrap these exceptions. They are probably not being caused by invalid
|
||||
# sql, but rather some external stimulus beyond the responsibilty of this code.
|
||||
# Additionaly, wrapping these exceptions with StatementInvalid would lead to
|
||||
# meaningful loss of data, such as losing SystemExit#status.
|
||||
raise e
|
||||
rescue Exception => e
|
||||
rescue => e
|
||||
# Log message and raise exception.
|
||||
# Set last_verification to 0, so that connection gets verified
|
||||
# upon reentering the request loop
|
||||
|
||||
@@ -77,6 +77,7 @@ module ActiveRecord
|
||||
# those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
||||
class MacroReflection
|
||||
attr_reader :active_record
|
||||
attr_accessor :cached_extend_class
|
||||
|
||||
def initialize(macro, name, options, active_record)
|
||||
@macro, @name, @options, @active_record = macro, name, options, active_record
|
||||
|
||||
@@ -295,7 +295,7 @@ module ActiveRecord
|
||||
|
||||
# Removes all errors that have been added.
|
||||
def clear
|
||||
@errors = ActiveSupport::OrderedHash.new
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
|
||||
|
||||
@@ -552,7 +552,7 @@ module NestedAttributesOnACollectionAssociationTests
|
||||
end
|
||||
|
||||
def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models
|
||||
attributes = ActiveSupport::OrderedHash.new
|
||||
attributes = {}
|
||||
attributes['123726353'] = { :name => 'Grace OMalley' }
|
||||
attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353
|
||||
@pirate.send(association_setter, attributes)
|
||||
@@ -562,7 +562,6 @@ module NestedAttributesOnACollectionAssociationTests
|
||||
|
||||
def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
|
||||
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
|
||||
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, ActiveSupport::OrderedHash.new) }
|
||||
|
||||
assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do
|
||||
@pirate.send(association_setter, "foo")
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
*2.3.11 (February 9, 2011)*
|
||||
*2.3.10 (October 15, 2010)*
|
||||
*2.3.9 (September 4, 2010)*
|
||||
*2.3.8 (May 24, 2010)*
|
||||
*2.3.7 (May 24, 2010)*
|
||||
|
||||
* Version bump.
|
||||
|
||||
|
||||
*2.3.6 (May 23, 2010)*
|
||||
|
||||
* No changes, just a version bump.
|
||||
|
||||
|
||||
*2.3.5 (November 25, 2009)*
|
||||
|
||||
* Minor Bug Fixes and deprecation warnings
|
||||
|
||||
* More flexible content type handling when parsing responses.
|
||||
|
||||
Ensures that ARes will handle responses like test/xml, or content types
|
||||
with charsets included.
|
||||
|
||||
*2.3.4 (September 4, 2009)*
|
||||
|
||||
* 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.3 (July 12, 2009)*
|
||||
|
||||
* No changes, just a version bump.
|
||||
|
||||
|
||||
*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]
|
||||
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2006-2010 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.
|
||||
@@ -1,165 +0,0 @@
|
||||
= Active Resource
|
||||
|
||||
Active Resource (ARes) connects business objects and Representational State Transfer (REST)
|
||||
web services. It implements object-relational mapping for REST webservices 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.
|
||||
|
||||
=== Configuration and Usage
|
||||
|
||||
Putting ActiveResource to use is very similar to ActiveRecord. 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 ActiveRecord invokes
|
||||
lifecycle 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 ActionController 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
|
||||
|
||||
GET Http requests 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.find(: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
|
||||
|
||||
|
||||
You can find more usage information in the ActiveResource::Base documentation.
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rdoc/task'
|
||||
require 'rake/packagetask'
|
||||
require 'rubygems/package_task'
|
||||
|
||||
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/**/*", "[A-Z]*", "Rakefile"
|
||||
].exclude(/\bCVS\b|~$/)
|
||||
|
||||
desc "Default Task"
|
||||
task :default => [ :test ]
|
||||
|
||||
# Run the unit tests
|
||||
|
||||
Rake::TestTask.new { |t|
|
||||
activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib"
|
||||
t.libs << activesupport_path if File.directory?(activesupport_path)
|
||||
t.libs << "test"
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
t.warning = true
|
||||
}
|
||||
|
||||
|
||||
# Generate the RDoc documentation
|
||||
|
||||
RDoc::Task.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.options << '--charset' << 'utf-8'
|
||||
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
|
||||
rdoc.rdoc_files.include('README', 'CHANGELOG')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('lib/activeresource.rb')
|
||||
}
|
||||
|
||||
|
||||
# Create compressed packages
|
||||
|
||||
dist_dirs = [ "lib", "test", "examples", "dev-utils" ]
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.name = PKG_NAME
|
||||
s.version = PKG_VERSION
|
||||
s.summary = "Think Active Record for web resources."
|
||||
s.description = %q{Wraps web resources in model classes that can be manipulated through XML over REST.}
|
||||
|
||||
s.files = [ "Rakefile", "README", "CHANGELOG" ]
|
||||
dist_dirs.each do |dir|
|
||||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
|
||||
|
||||
s.require_path = 'lib'
|
||||
|
||||
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 = "activeresource"
|
||||
end
|
||||
|
||||
Gem::PackageTask.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_resource/**/*.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
|
||||
require 'rake/contrib/sshpublisher'
|
||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
require 'rake/contrib/sshpublisher'
|
||||
Rake::SshDirPublisher.new("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
|
||||
@@ -1,44 +0,0 @@
|
||||
#--
|
||||
# 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.
|
||||
#++
|
||||
|
||||
begin
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
if File.directory?(activesupport_path)
|
||||
$:.unshift activesupport_path
|
||||
require 'active_support'
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_resource/formats'
|
||||
require 'active_resource/base'
|
||||
require 'active_resource/validations'
|
||||
require 'active_resource/custom_methods'
|
||||
|
||||
module ActiveResource
|
||||
Base.class_eval do
|
||||
include Validations
|
||||
include CustomMethods
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,283 +0,0 @@
|
||||
require 'net/https'
|
||||
require 'date'
|
||||
require 'time'
|
||||
require 'uri'
|
||||
require 'benchmark'
|
||||
|
||||
module ActiveResource
|
||||
class ConnectionError < StandardError # :nodoc:
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, message = nil)
|
||||
@response = response
|
||||
@message = message
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Failed with #{response.code} #{response.message if response.respond_to?(: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
|
||||
|
||||
# 4xx Client Error
|
||||
class ClientError < ConnectionError; end # :nodoc:
|
||||
|
||||
# 400 Bad Request
|
||||
class BadRequest < ClientError; end # :nodoc
|
||||
|
||||
# 401 Unauthorized
|
||||
class UnauthorizedAccess < ClientError; end # :nodoc
|
||||
|
||||
# 403 Forbidden
|
||||
class ForbiddenAccess < ClientError; end # :nodoc
|
||||
|
||||
# 404 Not Found
|
||||
class ResourceNotFound < ClientError; end # :nodoc:
|
||||
|
||||
# 409 Conflict
|
||||
class ResourceConflict < ClientError; end # :nodoc:
|
||||
|
||||
# 410 Gone
|
||||
class ResourceGone < ClientError; end # :nodoc:
|
||||
|
||||
# 5xx Server Error
|
||||
class ServerError < ConnectionError; end # :nodoc:
|
||||
|
||||
# 405 Method Not Allowed
|
||||
class MethodNotAllowed < ClientError # :nodoc:
|
||||
def allowed_methods
|
||||
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
||||
end
|
||||
end
|
||||
|
||||
# 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',
|
||||
:delete => 'Accept',
|
||||
:head => 'Accept'
|
||||
}
|
||||
|
||||
attr_reader :site, :user, :password, :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[:xml])
|
||||
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::DEFAULT_PARSER.unescape(@site.user) if @site.user
|
||||
@password = URI::DEFAULT_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
|
||||
|
||||
# Set the user for remote service.
|
||||
def user=(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
# Set password for remote service.
|
||||
def password=(password)
|
||||
@password = password
|
||||
end
|
||||
|
||||
# Set 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
|
||||
|
||||
# Execute a GET request.
|
||||
# Used to get (find) resources.
|
||||
def get(path, headers = {})
|
||||
format.decode(request(:get, path, build_request_headers(headers, :get)).body)
|
||||
end
|
||||
|
||||
# Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
|
||||
# Used to delete resources.
|
||||
def delete(path, headers = {})
|
||||
request(:delete, path, build_request_headers(headers, :delete))
|
||||
end
|
||||
|
||||
# Execute a PUT request (see HTTP protocol documentation if unfamiliar).
|
||||
# Used to update resources.
|
||||
def put(path, body = '', headers = {})
|
||||
request(:put, path, body.to_s, build_request_headers(headers, :put))
|
||||
end
|
||||
|
||||
# Execute a POST request.
|
||||
# Used to create new resources.
|
||||
def post(path, body = '', headers = {})
|
||||
request(:post, path, body.to_s, build_request_headers(headers, :post))
|
||||
end
|
||||
|
||||
# Execute 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 = {})
|
||||
request(:head, path, build_request_headers(headers, :head))
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Makes request to remote service.
|
||||
def request(method, path, *arguments)
|
||||
logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
|
||||
result = nil
|
||||
ms = Benchmark.ms { result = http.send(method, path, *arguments) }
|
||||
logger.info "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
|
||||
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 remote service.
|
||||
def handle_response(response)
|
||||
case response.code.to_i
|
||||
when 301,302
|
||||
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
|
||||
# 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)
|
||||
http = apply_ssl_options(http)
|
||||
|
||||
# Net::HTTP timeouts default to 60 seconds.
|
||||
if @timeout
|
||||
http.open_timeout = @timeout
|
||||
http.read_timeout = @timeout
|
||||
end
|
||||
|
||||
http
|
||||
end
|
||||
|
||||
def apply_ssl_options(http)
|
||||
return http unless @site.is_a?(URI::HTTPS)
|
||||
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
return http unless defined?(@ssl_options)
|
||||
|
||||
http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
|
||||
http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
|
||||
|
||||
http.cert = @ssl_options[:cert] if @ssl_options[:cert]
|
||||
http.key = @ssl_options[:key] if @ssl_options[:key]
|
||||
|
||||
http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
|
||||
http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
|
||||
|
||||
http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
|
||||
http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
|
||||
http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
|
||||
|
||||
http
|
||||
end
|
||||
|
||||
def default_header
|
||||
@default_header ||= {}
|
||||
end
|
||||
|
||||
# Builds headers for request to remote service.
|
||||
def build_request_headers(headers, http_method=nil)
|
||||
authorization_header.update(default_header).update(http_format_header(http_method)).update(headers)
|
||||
end
|
||||
|
||||
# Sets authorization header
|
||||
def authorization_header
|
||||
(@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
|
||||
end
|
||||
|
||||
def http_format_header(http_method)
|
||||
{HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
|
||||
end
|
||||
|
||||
def logger #:nodoc:
|
||||
Base.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,120 +0,0 @@
|
||||
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.xml # PeopleController.register
|
||||
# PUT /people/1/promote.xml # PeopleController.promote with :id => 1
|
||||
# DELETE /people/1/deactivate.xml # PeopleController.deactivate with :id => 1
|
||||
# GET /people/active.xml # PeopleController.active
|
||||
#
|
||||
# Using this module, Active Resource can use these custom REST methods just like the
|
||||
# standard methods.
|
||||
#
|
||||
# class Person < ActiveResource::Base
|
||||
# self.site = "http://37s.sunrise.i:3000"
|
||||
# end
|
||||
#
|
||||
# Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.xml
|
||||
# # => { :id => 1, :name => 'Ryan' }
|
||||
#
|
||||
# Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml
|
||||
# Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml
|
||||
#
|
||||
# Person.get(:active) # GET /people/active.xml
|
||||
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
||||
#
|
||||
module CustomMethods
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ActiveResource::CustomMethods::ClassMethods
|
||||
include ActiveResource::CustomMethods::InstanceMethods
|
||||
|
||||
class << self
|
||||
alias :orig_delete :delete
|
||||
|
||||
# Invokes a GET to a given custom REST method. For example:
|
||||
#
|
||||
# Person.get(:active) # GET /people/active.xml
|
||||
# # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
|
||||
#
|
||||
# Person.get(:active, :awesome => true) # GET /people/active.xml?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 = {})
|
||||
connection.get(custom_method_collection_url(custom_method_name, options), headers)
|
||||
end
|
||||
|
||||
def post(custom_method_name, options = {}, body = '')
|
||||
connection.post(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
|
||||
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
|
||||
|
||||
module InstanceMethods
|
||||
def get(method_name, options = {})
|
||||
connection.get(custom_method_element_url(method_name, options), self.class.headers)
|
||||
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 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
|
||||
end
|
||||
@@ -1,66 +0,0 @@
|
||||
module ActiveResource
|
||||
class ConnectionError < StandardError # :nodoc:
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, message = nil)
|
||||
@response = response
|
||||
@message = message
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Failed with #{response.code} #{response.message if response.respond_to?(: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
|
||||
|
||||
# 4xx Client Error
|
||||
class ClientError < ConnectionError; end # :nodoc:
|
||||
|
||||
# 400 Bad Request
|
||||
class BadRequest < ClientError; end # :nodoc
|
||||
|
||||
# 401 Unauthorized
|
||||
class UnauthorizedAccess < ClientError; end # :nodoc
|
||||
|
||||
# 403 Forbidden
|
||||
class ForbiddenAccess < ClientError; end # :nodoc
|
||||
|
||||
# 404 Not Found
|
||||
class ResourceNotFound < ClientError; end # :nodoc:
|
||||
|
||||
# 409 Conflict
|
||||
class ResourceConflict < ClientError; end # :nodoc:
|
||||
|
||||
# 410 Gone
|
||||
class ResourceGone < ClientError; end # :nodoc:
|
||||
|
||||
# 5xx Server Error
|
||||
class ServerError < ConnectionError; end # :nodoc:
|
||||
|
||||
# 405 Method Not Allowed
|
||||
class MethodNotAllowed < ClientError # :nodoc:
|
||||
def allowed_methods
|
||||
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
module ActiveResource
|
||||
module Formats
|
||||
# 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(mime_type_reference.to_s.camelize + "Format")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_resource/formats/xml_format'
|
||||
require 'active_resource/formats/json_format'
|
||||
@@ -1,23 +0,0 @@
|
||||
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)
|
||||
ActiveSupport::JSON.decode(json)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,34 +0,0 @@
|
||||
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)
|
||||
from_xml_data(Hash.from_xml(xml))
|
||||
end
|
||||
|
||||
private
|
||||
# Manipulate from_xml Hash, because xml_simple is not exactly what we
|
||||
# want for Active Resource.
|
||||
def from_xml_data(data)
|
||||
if data.is_a?(Hash) && data.keys.size == 1
|
||||
data.values.first
|
||||
else
|
||||
data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,302 +0,0 @@
|
||||
require 'active_resource/connection'
|
||||
|
||||
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+, +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/xml" }</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 XML.
|
||||
# * <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 = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
|
||||
# ActiveResource::HttpMock.respond_to do |mock|
|
||||
# mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
|
||||
# mock.get "/people/1.xml", {}, @matz
|
||||
# mock.put "/people/1.xml", {}, nil, 204
|
||||
# mock.delete "/people/1.xml", {}, 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
|
||||
|
||||
for method in [ :post, :put, :get, :delete, :head ]
|
||||
# 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 = {})
|
||||
@responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
|
||||
end
|
||||
EOE
|
||||
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 = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
|
||||
# ActiveResource::HttpMock.respond_to do |mock|
|
||||
# mock.get "/people/1.xml", {}, @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.xml")
|
||||
#
|
||||
# # 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 responses.assoc(request).
|
||||
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 = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
|
||||
# ActiveResource::HttpMock.respond_to do |mock|
|
||||
# mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
|
||||
# mock.get "/people/1.xml", {}, @matz
|
||||
# mock.put "/people/1.xml", {}, nil, 204
|
||||
# mock.delete "/people/1.xml", {}, 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 = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
|
||||
#
|
||||
# create_matz = ActiveResource::Request.new(:post, '/people.xml', @matz, {})
|
||||
# created_response = ActiveResource::Response.new("", 201, {"Location" => "/people/1.xml"})
|
||||
# get_matz = ActiveResource::Request.new(:get, '/people/1.xml', 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 behaviour, pass in +false+ as the last argument to +respond_to+
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# ActiveResource::HttpMock.respond_to do |mock|
|
||||
# mock.send(:get, "/people/1", {}, "XML1")
|
||||
# end
|
||||
# ActiveResource::HttpMock.responses.length #=> 1
|
||||
#
|
||||
# ActiveResource::HttpMock.respond_to(false) do |mock|
|
||||
# mock.send(:get, "/people/2", {}, "XML2")
|
||||
# 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", {}, "XML1")
|
||||
# end
|
||||
# ActiveResource::HttpMock.responses.length #=> 1
|
||||
#
|
||||
# get_matz = ActiveResource::Request.new(:get, '/people/1.xml', nil)
|
||||
# ok_response = ActiveResource::Response.new("", 200, {})
|
||||
#
|
||||
# pairs = {get_matz => ok_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
|
||||
responses.concat pairs.to_a
|
||||
if block_given?
|
||||
yield Responder.new(responses)
|
||||
else
|
||||
Responder.new(responses)
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes all logged requests and responses.
|
||||
def reset!
|
||||
requests.clear
|
||||
responses.clear
|
||||
end
|
||||
end
|
||||
|
||||
# body? methods
|
||||
{ true => %w(post 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
|
||||
|
||||
if @body.nil?
|
||||
self['Content-Length'] = "0"
|
||||
else
|
||||
self['Content-Length'] = body.size.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def success?
|
||||
(200..299).include?(code)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
headers[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
headers[key] = value
|
||||
end
|
||||
|
||||
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
|
||||
@@ -1,290 +0,0 @@
|
||||
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
|
||||
include Enumerable
|
||||
attr_reader :errors
|
||||
|
||||
delegate :empty?, :to => :errors
|
||||
|
||||
def initialize(base) # :nodoc:
|
||||
@base, @errors = base, {}
|
||||
end
|
||||
|
||||
# Add an error to the base Active Resource object rather than an attribute.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_folder = Folder.find(1)
|
||||
# my_folder.errors.add_to_base("You can't edit an existing folder")
|
||||
# my_folder.errors.on_base
|
||||
# # => "You can't edit an existing folder"
|
||||
#
|
||||
# my_folder.errors.add_to_base("This folder has been tagged as frozen")
|
||||
# my_folder.valid?
|
||||
# # => false
|
||||
# my_folder.errors.on_base
|
||||
# # => ["You can't edit an existing folder", "This folder has been tagged as frozen"]
|
||||
#
|
||||
def add_to_base(msg)
|
||||
add(:base, msg)
|
||||
end
|
||||
|
||||
# Adds an error to an Active Resource object's attribute (named for the +attribute+ parameter)
|
||||
# with the error message in +msg+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_resource = Node.find(1)
|
||||
# my_resource.errors.add('name', 'can not be "base"') if my_resource.name == 'base'
|
||||
# my_resource.errors.on('name')
|
||||
# # => 'can not be "base"!'
|
||||
#
|
||||
# my_resource.errors.add('desc', 'can not be blank') if my_resource.desc == ''
|
||||
# my_resource.valid?
|
||||
# # => false
|
||||
# my_resource.errors.on('desc')
|
||||
# # => 'can not be blank!'
|
||||
#
|
||||
def add(attribute, msg)
|
||||
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
||||
@errors[attribute.to_s] << msg
|
||||
end
|
||||
|
||||
# Returns true if the specified +attribute+ has errors associated with it.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_resource = Disk.find(1)
|
||||
# my_resource.errors.add('location', 'must be Main') unless my_resource.location == 'Main'
|
||||
# my_resource.errors.on('location')
|
||||
# # => 'must be Main!'
|
||||
#
|
||||
# my_resource.errors.invalid?('location')
|
||||
# # => true
|
||||
# my_resource.errors.invalid?('name')
|
||||
# # => false
|
||||
def invalid?(attribute)
|
||||
!@errors[attribute.to_s].nil?
|
||||
end
|
||||
|
||||
# A method to return the errors associated with +attribute+, which returns nil, if no errors are
|
||||
# associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+,
|
||||
# or an array of error messages if more than one error is associated with the specified +attribute+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
# my_person.errors.on('login')
|
||||
# # => nil
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.on('login')
|
||||
# # => 'can not be empty'
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be longer than 10 characters') if my_person.login.length > 10
|
||||
# my_person.errors.on('login')
|
||||
# # => ['can not be empty', 'can not be longer than 10 characters']
|
||||
def on(attribute)
|
||||
errors = @errors[attribute.to_s]
|
||||
return nil if errors.nil?
|
||||
errors.size == 1 ? errors.first : errors
|
||||
end
|
||||
|
||||
alias :[] :on
|
||||
|
||||
# A method to return errors assigned to +base+ object through add_to_base, which returns nil, if no errors are
|
||||
# associated with the specified +attribute+, the error message if one error is associated with the specified +attribute+,
|
||||
# or an array of error messages if more than one error is associated with the specified +attribute+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_account = Account.find(1)
|
||||
# my_account.errors.on_base
|
||||
# # => nil
|
||||
#
|
||||
# my_account.errors.add_to_base("This account is frozen")
|
||||
# my_account.errors.on_base
|
||||
# # => "This account is frozen"
|
||||
#
|
||||
# my_account.errors.add_to_base("This account has been closed")
|
||||
# my_account.errors.on_base
|
||||
# # => ["This account is frozen", "This account has been closed"]
|
||||
#
|
||||
def on_base
|
||||
on(:base)
|
||||
end
|
||||
|
||||
# Yields each attribute and associated message per error added.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# messages = ''
|
||||
# my_person.errors.each {|attr, msg| messages += attr.humanize + " " + msg + "<br />"}
|
||||
# messages
|
||||
# # => "Login can not be empty<br />Password can not be empty<br />"
|
||||
#
|
||||
def each
|
||||
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
||||
end
|
||||
|
||||
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
||||
# through iteration as "First name can't be empty".
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# messages = ''
|
||||
# my_person.errors.each_full {|msg| messages += msg + "<br/>"}
|
||||
# messages
|
||||
# # => "Login can not be empty<br />Password can not be empty<br />"
|
||||
#
|
||||
def each_full
|
||||
full_messages.each { |msg| yield msg }
|
||||
end
|
||||
|
||||
# Returns all the full error messages in an array.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# messages = ''
|
||||
# my_person.errors.full_messages.each {|msg| messages += msg + "<br/>"}
|
||||
# messages
|
||||
# # => "Login can not be empty<br />Password can not be empty<br />"
|
||||
#
|
||||
def full_messages
|
||||
full_messages = []
|
||||
|
||||
@errors.each_key do |attr|
|
||||
@errors[attr].each do |msg|
|
||||
next if msg.nil?
|
||||
|
||||
if attr == "base"
|
||||
full_messages << msg
|
||||
else
|
||||
full_messages << [attr.humanize, msg].join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
full_messages
|
||||
end
|
||||
|
||||
def clear
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
||||
# with this as well.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_person = Person.new(params[:person])
|
||||
# my_person.errors.size
|
||||
# # => 0
|
||||
#
|
||||
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
|
||||
# my_person.errors.add('password', 'can not be empty') if my_person.password == ''
|
||||
# my_person.error.size
|
||||
# # => 2
|
||||
#
|
||||
def size
|
||||
@errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
|
||||
end
|
||||
|
||||
alias_method :count, :size
|
||||
alias_method :length, :size
|
||||
|
||||
# Grabs errors from an array of messages (like ActiveRecord::Validations)
|
||||
def from_array(messages)
|
||||
clear
|
||||
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(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
|
||||
|
||||
add_to_base message if attr_message.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Grabs errors from the json response.
|
||||
def from_json(json)
|
||||
array = ActiveSupport::JSON.decode(json)['errors'] rescue []
|
||||
from_array array
|
||||
end
|
||||
|
||||
# Grabs errors from the XML response.
|
||||
def from_xml(xml)
|
||||
array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
|
||||
from_array array
|
||||
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 ActiveRecord::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.on(:last_name) # => "can't be empty"
|
||||
# person.last_name = "Halpert"
|
||||
# person.save # => true (and person is now saved to the remote service)
|
||||
#
|
||||
module Validations
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
alias_method_chain :save, :validation
|
||||
end
|
||||
end
|
||||
|
||||
# Validate a resource and save (POST) it to the remote web service.
|
||||
def save_with_validation
|
||||
save_without_validation
|
||||
true
|
||||
rescue ResourceInvalid => error
|
||||
case self.class.format
|
||||
when ActiveResource::Formats[:xml]
|
||||
errors.from_xml(error.response.body)
|
||||
when ActiveResource::Formats[:json]
|
||||
errors.from_json(error.response.body)
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Checks for errors on an object (i.e., is resource.errors empty?).
|
||||
#
|
||||
# ==== 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?
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
# Returns the Errors object that holds all information about attribute error messages.
|
||||
def errors
|
||||
@errors ||= Errors.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
module ActiveResource
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 14
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
require 'active_resource'
|
||||
ActiveSupport::Deprecation.warn 'require "activeresource" is deprecated and will be removed in Rails 3. Use require "active_resource" instead.'
|
||||
@@ -1,21 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'active_support/test_case'
|
||||
|
||||
$:.unshift File.expand_path('../../lib', __FILE__)
|
||||
$:.unshift File.expand_path('../../../activesupport/lib', __FILE__)
|
||||
require 'active_resource'
|
||||
require 'active_resource/http_mock'
|
||||
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../test"
|
||||
require 'setter_trap'
|
||||
|
||||
ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log")
|
||||
|
||||
def uses_gem(gem_name, test_name, version = '> 0')
|
||||
gem gem_name.to_s, version
|
||||
require gem_name.to_s
|
||||
yield
|
||||
rescue LoadError
|
||||
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
|
||||
end
|
||||
@@ -1,122 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class AuthorizationTest < Test::Unit::TestCase
|
||||
Response = Struct.new(:code)
|
||||
|
||||
def setup
|
||||
@conn = ActiveResource::Connection.new('http://localhost')
|
||||
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
||||
@david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
|
||||
@authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost")
|
||||
@authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
|
||||
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/2.xml", @authorization_request_header, @david
|
||||
mock.put "/people/2.xml", @authorization_request_header, nil, 204
|
||||
mock.delete "/people/2.xml", @authorization_request_header, nil, 200
|
||||
mock.post "/people/2/addresses.xml", @authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5'
|
||||
end
|
||||
end
|
||||
|
||||
def test_authorization_header
|
||||
authorization_header = @authenticated_conn.__send__(:authorization_header)
|
||||
assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["david", "test123"], ActiveSupport::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)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["david"], ActiveSupport::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)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["", "test123"], ActiveSupport::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)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["my@email.com", "123"], ActiveSupport::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)
|
||||
assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["david", "test123"], ActiveSupport::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)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["david"], ActiveSupport::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)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
|
||||
end
|
||||
|
||||
def test_get
|
||||
david = @authenticated_conn.get("/people/2.xml")
|
||||
assert_equal "David", david["name"]
|
||||
end
|
||||
|
||||
def test_post
|
||||
response = @authenticated_conn.post("/people/2/addresses.xml")
|
||||
assert_equal "/people/1/addresses/5", response["Location"]
|
||||
end
|
||||
|
||||
def test_put
|
||||
response = @authenticated_conn.put("/people/2.xml")
|
||||
assert_equal 204, response.code
|
||||
end
|
||||
|
||||
def test_delete
|
||||
response = @authenticated_conn.delete("/people/2.xml")
|
||||
assert_equal 200, response.code
|
||||
end
|
||||
|
||||
def test_raises_invalid_request_on_unauthorized_requests
|
||||
assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") }
|
||||
assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") }
|
||||
assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") }
|
||||
assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") }
|
||||
end
|
||||
|
||||
protected
|
||||
def assert_response_raises(klass, code)
|
||||
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
|
||||
@conn.__send__(:handle_response, Response.new(code))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,100 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require 'fixtures/person'
|
||||
require 'fixtures/street_address'
|
||||
|
||||
class CustomMethodsTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
||||
@matz_deep = { :id => 1, :name => 'Matz', :other => 'other' }.to_xml(:root => 'person')
|
||||
@matz_array = [{ :id => 1, :name => 'Matz' }].to_xml(:root => 'people')
|
||||
@ryan = { :name => 'Ryan' }.to_xml(:root => 'person')
|
||||
@addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
|
||||
@addy_deep = { :id => 1, :street => '12345 Street', :zip => "27519" }.to_xml(:root => 'address')
|
||||
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/1.xml", {}, @matz
|
||||
mock.get "/people/1/shallow.xml", {}, @matz
|
||||
mock.get "/people/1/deep.xml", {}, @matz_deep
|
||||
mock.get "/people/retrieve.xml?name=Matz", {}, @matz_array
|
||||
mock.get "/people/managers.xml", {}, @matz_array
|
||||
mock.post "/people/hire.xml?name=Matz", {}, nil, 201
|
||||
mock.put "/people/1/promote.xml?position=Manager", {}, nil, 204
|
||||
mock.put "/people/promote.xml?name=Matz", {}, nil, 204, {}
|
||||
mock.put "/people/sort.xml?by=name", {}, nil, 204
|
||||
mock.delete "/people/deactivate.xml?name=Matz", {}, nil, 200
|
||||
mock.delete "/people/1/deactivate.xml", {}, nil, 200
|
||||
mock.post "/people/new/register.xml", {}, @ryan, 201, 'Location' => '/people/5.xml'
|
||||
mock.post "/people/1/register.xml", {}, @matz, 201
|
||||
mock.get "/people/1/addresses/1.xml", {}, @addy
|
||||
mock.get "/people/1/addresses/1/deep.xml", {}, @addy_deep
|
||||
mock.put "/people/1/addresses/1/normalize_phone.xml?locale=US", {}, nil, 204
|
||||
mock.put "/people/1/addresses/sort.xml?by=name", {}, nil, 204
|
||||
mock.post "/people/1/addresses/new/link.xml", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml'
|
||||
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.xml'}), ryan.post(:register)
|
||||
expected_request = ActiveResource::Request.new(:post, '/people/new/register.xml', @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({ :street => '12345 Street' }.to_xml(:root => 'address'),
|
||||
201, {'Location' => '/people/1/addresses/2.xml'}),
|
||||
addy.post(:link)
|
||||
|
||||
matz = Person.new(:id => 1, :name => 'Matz')
|
||||
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
|
||||
@@ -1,52 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require "fixtures/person"
|
||||
require "fixtures/street_address"
|
||||
|
||||
class BaseEqualityTest < Test::Unit::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
|
||||
@@ -1,161 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require "fixtures/person"
|
||||
require "fixtures/street_address"
|
||||
|
||||
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 < Test::Unit::TestCase
|
||||
def setup
|
||||
@matz = { :id => 1, :name => 'Matz' }
|
||||
|
||||
@first_address = { :id => 1, :street => '12345 Street' }
|
||||
@addresses = [@first_address, { :id => 2, :street => '67890 Street' }]
|
||||
@addresses_from_xml = { :street_addresses => @addresses }
|
||||
@addresses_from_xml_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 ],
|
||||
:places => [ "Columbia City", "Unknown" ]}}}
|
||||
|
||||
@person = Person.new
|
||||
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_load_one_with_existing_resource
|
||||
address = @person.load(:street_address => @first_address).street_address
|
||||
assert_kind_of StreetAddress, address
|
||||
assert_equal @first_address.stringify_keys, address.attributes
|
||||
end
|
||||
|
||||
def test_load_one_with_unknown_resource
|
||||
address = silence_warnings { @person.load(:address => @first_address).address }
|
||||
assert_kind_of Person::Address, address
|
||||
assert_equal @first_address.stringify_keys, address.attributes
|
||||
end
|
||||
|
||||
def test_load_collection_with_existing_resource
|
||||
addresses = @person.load(@addresses_from_xml).street_addresses
|
||||
assert_kind_of Array, addresses
|
||||
addresses.each { |address| assert_kind_of StreetAddress, address }
|
||||
assert_equal @addresses.map(&: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(&:stringify_keys), addresses.map(&:attributes)
|
||||
end
|
||||
|
||||
def test_load_collection_with_single_existing_resource
|
||||
addresses = @person.load(@addresses_from_xml_single).street_addresses
|
||||
assert_kind_of Array, addresses
|
||||
addresses.each { |address| assert_kind_of StreetAddress, address }
|
||||
assert_equal [ @first_address ].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 ].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
|
||||
end
|
||||
|
||||
def test_nested_collections_within_the_same_namespace
|
||||
n = Highrise::Note.new(:comments => [{ :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
|
||||
@@ -1,98 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require "fixtures/person"
|
||||
|
||||
class BaseErrorsTest < Test::Unit::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","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
|
||||
end
|
||||
@person = Person.new(:name => '', :age => '')
|
||||
assert_equal @person.save, false
|
||||
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_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_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","Name 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
|
||||
|
||||
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
@@ -1,238 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class ConnectionTest < Test::Unit::TestCase
|
||||
ResponseCodeStub = Struct.new(:code)
|
||||
|
||||
def setup
|
||||
@conn = ActiveResource::Connection.new('http://localhost')
|
||||
@matz = { :id => 1, :name => 'Matz' }
|
||||
@david = { :id => 2, :name => 'David' }
|
||||
@people = [ @matz, @david ].to_xml(:root => 'people')
|
||||
@people_single = [ @matz ].to_xml(:root => 'people-single-elements')
|
||||
@people_empty = [ ].to_xml(:root => 'people-empty-elements')
|
||||
@matz = @matz.to_xml(:root => 'person')
|
||||
@david = @david.to_xml(:root => 'person')
|
||||
@header = {'key' => 'value'}.freeze
|
||||
|
||||
@default_request_headers = { 'Content-Type' => 'application/xml' }
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/2.xml", @header, @david
|
||||
mock.get "/people.xml", {}, @people
|
||||
mock.get "/people_single_elements.xml", {}, @people_single
|
||||
mock.get "/people_empty_elements.xml", {}, @people_empty
|
||||
mock.get "/people/1.xml", {}, @matz
|
||||
mock.put "/people/1.xml", {}, nil, 204
|
||||
mock.put "/people/2.xml", {}, @header, 204
|
||||
mock.delete "/people/1.xml", {}, nil, 200
|
||||
mock.delete "/people/2.xml", @header, nil, 200
|
||||
mock.post "/people.xml", {}, nil, 201, 'Location' => '/people/5.xml'
|
||||
mock.post "/members.xml", {}, @header, 201, 'Location' => '/people/6.xml'
|
||||
mock.head "/people/1.xml", {}, 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
|
||||
|
||||
# 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 requst (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 missing 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 with 405 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 = @conn.get("/people/1.xml")
|
||||
assert_equal "Matz", matz["name"]
|
||||
end
|
||||
|
||||
def test_head
|
||||
response = @conn.head("/people/1.xml")
|
||||
assert response.body.blank?
|
||||
assert_equal 200, response.code
|
||||
end
|
||||
|
||||
def test_get_with_header
|
||||
david = @conn.get("/people/2.xml", @header)
|
||||
assert_equal "David", david["name"]
|
||||
end
|
||||
|
||||
def test_get_collection
|
||||
people = @conn.get("/people.xml")
|
||||
assert_equal "Matz", people[0]["name"]
|
||||
assert_equal "David", people[1]["name"]
|
||||
end
|
||||
|
||||
def test_get_collection_single
|
||||
people = @conn.get("/people_single_elements.xml")
|
||||
assert_equal "Matz", people[0]["name"]
|
||||
end
|
||||
|
||||
def test_get_collection_empty
|
||||
people = @conn.get("/people_empty_elements.xml")
|
||||
assert_equal [], people
|
||||
end
|
||||
|
||||
def test_post
|
||||
response = @conn.post("/people.xml")
|
||||
assert_equal "/people/5.xml", response["Location"]
|
||||
end
|
||||
|
||||
def test_post_with_header
|
||||
response = @conn.post("/members.xml", @header)
|
||||
assert_equal "/people/6.xml", response["Location"]
|
||||
end
|
||||
|
||||
def test_put
|
||||
response = @conn.put("/people/1.xml")
|
||||
assert_equal 204, response.code
|
||||
end
|
||||
|
||||
def test_put_with_header
|
||||
response = @conn.put("/people/2.xml", @header)
|
||||
assert_equal 204, response.code
|
||||
end
|
||||
|
||||
def test_delete
|
||||
response = @conn.delete("/people/1.xml")
|
||||
assert_equal 200, response.code
|
||||
end
|
||||
|
||||
def test_delete_with_header
|
||||
response = @conn.delete("/people/2.xml", @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.xml') }
|
||||
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.timeout = 10 # prevent warning about uninitialized.
|
||||
@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.xml') }
|
||||
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 handle_response(response)
|
||||
@conn.__send__(:handle_response, response)
|
||||
end
|
||||
end
|
||||
14
activeresource/test/fixtures/beast.rb
vendored
14
activeresource/test/fixtures/beast.rb
vendored
@@ -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
|
||||
3
activeresource/test/fixtures/customer.rb
vendored
3
activeresource/test/fixtures/customer.rb
vendored
@@ -1,3 +0,0 @@
|
||||
class Customer < ActiveResource::Base
|
||||
self.site = "http://37s.sunrise.i:3000"
|
||||
end
|
||||
3
activeresource/test/fixtures/person.rb
vendored
3
activeresource/test/fixtures/person.rb
vendored
@@ -1,3 +0,0 @@
|
||||
class Person < ActiveResource::Base
|
||||
self.site = "http://37s.sunrise.i:3000"
|
||||
end
|
||||
4
activeresource/test/fixtures/proxy.rb
vendored
4
activeresource/test/fixtures/proxy.rb
vendored
@@ -1,4 +0,0 @@
|
||||
class ProxyResource < ActiveResource::Base
|
||||
self.site = "http://localhost"
|
||||
self.proxy = "http://user:password@proxy.local:3000"
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
class StreetAddress < ActiveResource::Base
|
||||
self.site = "http://37s.sunrise.i:3000/people/:person_id/"
|
||||
self.element_name = 'address'
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user