/text/src/test/resources/examples/ruby/rails.in.rb

https://github.com/rimolive/core · Ruby · 74259 lines · 54247 code · 8777 blank · 11235 comment · 3148 complexity · cc3b4273ae9f93be89092edb70074a1b MD5 · raw file

  1. require 'rbconfig'
  2. require 'find'
  3. require 'ftools'
  4. include Config
  5. # this was adapted from rdoc's install.rb by way of Log4r
  6. $sitedir = CONFIG["sitelibdir"]
  7. unless $sitedir
  8. version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  9. $libdir = File.join(CONFIG["libdir"], "ruby", version)
  10. $sitedir = $:.find {|x| x =~ /site_ruby/ }
  11. if !$sitedir
  12. $sitedir = File.join($libdir, "site_ruby")
  13. elsif $sitedir !~ Regexp.quote(version)
  14. $sitedir = File.join($sitedir, version)
  15. end
  16. end
  17. # the acual gruntwork
  18. Dir.chdir("lib")
  19. Find.find("action_mailer", "action_mailer.rb") { |f|
  20. if f[-3..-1] == ".rb"
  21. File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  22. else
  23. File::makedirs(File.join($sitedir, *f.split(/\//)))
  24. end
  25. }
  26. module ActionMailer
  27. module AdvAttrAccessor #:nodoc:
  28. def self.append_features(base)
  29. super
  30. base.extend(ClassMethods)
  31. end
  32. module ClassMethods #:nodoc:
  33. def adv_attr_accessor(*names)
  34. names.each do |name|
  35. ivar = "@#{name}"
  36. define_method("#{name}=") do |value|
  37. instance_variable_set(ivar, value)
  38. end
  39. define_method(name) do |*parameters|
  40. raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
  41. if parameters.empty?
  42. if instance_variables.include?(ivar)
  43. instance_variable_get(ivar)
  44. end
  45. else
  46. instance_variable_set(ivar, parameters.first)
  47. end
  48. end
  49. end
  50. end
  51. end
  52. end
  53. end
  54. require 'action_mailer/adv_attr_accessor'
  55. require 'action_mailer/part'
  56. require 'action_mailer/part_container'
  57. require 'action_mailer/utils'
  58. require 'tmail/net'
  59. module ActionMailer #:nodoc:
  60. # ActionMailer allows you to send email from your application using a mailer model and views.
  61. #
  62. # = Mailer Models
  63. # To use ActionMailer, you need to create a mailer model.
  64. #
  65. # $ script/generate mailer Notifier
  66. #
  67. # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then
  68. # used to set variables to be used in the mail template, to change options on the mail, or
  69. # to add attachments.
  70. #
  71. # Examples:
  72. #
  73. # class Notifier < ActionMailer::Base
  74. # def signup_notification(recipient)
  75. # recipients recipient.email_address_with_name
  76. # from "system@example.com"
  77. # subject "New account information"
  78. # body "account" => recipient
  79. # end
  80. # end
  81. #
  82. # Mailer methods have the following configuration methods available.
  83. #
  84. # * <tt>recipients</tt> - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the <tt>To:</tt> header.
  85. # * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header.
  86. # * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header.
  87. # * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header.
  88. # * <tt>bcc</tt> - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc</tt> header.
  89. # * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header wil be set by the delivery agent.
  90. # * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
  91. # * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
  92. #
  93. # The <tt>body</tt> method has special behavior. It takes a hash which generates an instance variable
  94. # named after each key in the hash containing the value that that key points to.
  95. #
  96. # So, for example, <tt>body "account" => recipient</tt> would result
  97. # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the
  98. # view.
  99. #
  100. # = Mailer Views
  101. # Like ActionController, each mailer class has a corresponding view directory
  102. # in which each method of the class looks for a template with its name.
  103. # To define a template to be used with a mailing, create an <tt>.rhtml</tt> file with the same name as the method
  104. # in your mailer model. For example, in the mailer defined above, the template at
  105. # <tt>app/views/notifier/signup_notification.rhtml</tt> would be used to generate the email.
  106. #
  107. # Variables defined in the model are accessible as instance variables in the view.
  108. #
  109. # Emails by default are sent in plain text, so a sample view for our model example might look like this:
  110. #
  111. # Hi <%= @account.name %>,
  112. # Thanks for joining our service! Please check back often.
  113. #
  114. # = Sending Mail
  115. # Once a mailer action and template are defined, you can deliver your message or create it and save it
  116. # for delivery later:
  117. #
  118. # Notifier.deliver_signup_notification(david) # sends the email
  119. # mail = Notifier.create_signup_notification(david) # => a tmail object
  120. # Notifier.deliver(mail)
  121. #
  122. # You never instantiate your mailer class. Rather, your delivery instance
  123. # methods are automatically wrapped in class methods that start with the word
  124. # <tt>deliver_</tt> followed by the name of the mailer method that you would
  125. # like to deliver. The <tt>signup_notification</tt> method defined above is
  126. # delivered by invoking <tt>Notifier.deliver_signup_notification</tt>.
  127. #
  128. # = HTML Email
  129. # To send mail as HTML, make sure your view (the <tt>.rhtml</tt> file) generates HTML and
  130. # set the content type to html.
  131. #
  132. # class MyMailer < ActionMailer::Base
  133. # def signup_notification(recipient)
  134. # recipients recipient.email_address_with_name
  135. # subject "New account information"
  136. # body "account" => recipient
  137. # from "system@example.com"
  138. # content_type "text/html" # Here's where the magic happens
  139. # end
  140. # end
  141. #
  142. # = Multipart Email
  143. # You can explicitly specify multipart messages:
  144. #
  145. # class ApplicationMailer < ActionMailer::Base
  146. # def signup_notification(recipient)
  147. # recipients recipient.email_address_with_name
  148. # subject "New account information"
  149. # from "system@example.com"
  150. #
  151. # part :content_type => "text/html",
  152. # :body => render_message("signup-as-html", :account => recipient)
  153. #
  154. # part "text/plain" do |p|
  155. # p.body = render_message("signup-as-plain", :account => recipient)
  156. # p.transfer_encoding = "base64"
  157. # end
  158. # end
  159. # end
  160. #
  161. # Multipart messages can also be used implicitly because ActionMailer will automatically
  162. # detect and use multipart templates, where each template is named after the name of the action, followed
  163. # by the content type. Each such detected template will be added as separate part to the message.
  164. #
  165. # For example, if the following templates existed:
  166. # * signup_notification.text.plain.rhtml
  167. # * signup_notification.text.html.rhtml
  168. # * signup_notification.text.xml.rxml
  169. # * signup_notification.text.x-yaml.rhtml
  170. #
  171. # Each would be rendered and added as a separate part to the message,
  172. # with the corresponding content type. The same body hash is passed to
  173. # each template.
  174. #
  175. # = Attachments
  176. # Attachments can be added by using the +attachment+ method.
  177. #
  178. # Example:
  179. #
  180. # class ApplicationMailer < ActionMailer::Base
  181. # # attachments
  182. # def signup_notification(recipient)
  183. # recipients recipient.email_address_with_name
  184. # subject "New account information"
  185. # from "system@example.com"
  186. #
  187. # attachment :content_type => "image/jpeg",
  188. # :body => File.read("an-image.jpg")
  189. #
  190. # attachment "application/pdf" do |a|
  191. # a.body = generate_your_pdf_here()
  192. # end
  193. # end
  194. # end
  195. #
  196. # = Configuration options
  197. #
  198. # These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
  199. #
  200. # * <tt>template_root</tt> - template root determines the base from which template references will be made.
  201. #
  202. # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
  203. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
  204. #
  205. # * <tt>server_settings</tt> - Allows detailed configuration of the server:
  206. # * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
  207. # * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
  208. # * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
  209. # * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
  210. # * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
  211. # * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
  212. # This is a symbol and one of :plain, :login, :cram_md5
  213. #
  214. # * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
  215. #
  216. # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
  217. # Sendmail is assumed to be present at "/usr/sbin/sendmail".
  218. #
  219. # * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
  220. # but this can be turned off to help functional testing.
  221. #
  222. # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
  223. # for unit and functional testing.
  224. #
  225. # * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
  226. # pick a different charset from inside a method with <tt>@charset</tt>.
  227. # * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
  228. # can also pick a different content type from inside a method with <tt>@content_type</tt>.
  229. # * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to nil. You
  230. # can also pick a different value from inside a method with <tt>@mime_version</tt>. When multipart messages are in
  231. # use, <tt>@mime_version</tt> will be set to "1.0" if it is not set inside a method.
  232. # * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
  233. # which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
  234. # ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client
  235. # and appear last in the mime encoded message. You can also pick a different order from inside a method with
  236. # <tt>@implicit_parts_order</tt>.
  237. class Base
  238. include AdvAttrAccessor, PartContainer
  239. # Action Mailer subclasses should be reloaded by the dispatcher in Rails
  240. # when Dependencies.mechanism = :load.
  241. include Reloadable::Subclasses
  242. private_class_method :new #:nodoc:
  243. class_inheritable_accessor :template_root
  244. cattr_accessor :logger
  245. @@server_settings = {
  246. :address => "localhost",
  247. :port => 25,
  248. :domain => 'localhost.localdomain',
  249. :user_name => nil,
  250. :password => nil,
  251. :authentication => nil
  252. }
  253. cattr_accessor :server_settings
  254. @@raise_delivery_errors = true
  255. cattr_accessor :raise_delivery_errors
  256. @@delivery_method = :smtp
  257. cattr_accessor :delivery_method
  258. @@perform_deliveries = true
  259. cattr_accessor :perform_deliveries
  260. @@deliveries = []
  261. cattr_accessor :deliveries
  262. @@default_charset = "utf-8"
  263. cattr_accessor :default_charset
  264. @@default_content_type = "text/plain"
  265. cattr_accessor :default_content_type
  266. @@default_mime_version = nil
  267. cattr_accessor :default_mime_version
  268. @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
  269. cattr_accessor :default_implicit_parts_order
  270. # Specify the BCC addresses for the message
  271. adv_attr_accessor :bcc
  272. # Define the body of the message. This is either a Hash (in which case it
  273. # specifies the variables to pass to the template when it is rendered),
  274. # or a string, in which case it specifies the actual text of the message.
  275. adv_attr_accessor :body
  276. # Specify the CC addresses for the message.
  277. adv_attr_accessor :cc
  278. # Specify the charset to use for the message. This defaults to the
  279. # +default_charset+ specified for ActionMailer::Base.
  280. adv_attr_accessor :charset
  281. # Specify the content type for the message. This defaults to <tt>text/plain</tt>
  282. # in most cases, but can be automatically set in some situations.
  283. adv_attr_accessor :content_type
  284. # Specify the from address for the message.
  285. adv_attr_accessor :from
  286. # Specify additional headers to be added to the message.
  287. adv_attr_accessor :headers
  288. # Specify the order in which parts should be sorted, based on content-type.
  289. # This defaults to the value for the +default_implicit_parts_order+.
  290. adv_attr_accessor :implicit_parts_order
  291. # Override the mailer name, which defaults to an inflected version of the
  292. # mailer's class name. If you want to use a template in a non-standard
  293. # location, you can use this to specify that location.
  294. adv_attr_accessor :mailer_name
  295. # Defaults to "1.0", but may be explicitly given if needed.
  296. adv_attr_accessor :mime_version
  297. # The recipient addresses for the message, either as a string (for a single
  298. # address) or an array (for multiple addresses).
  299. adv_attr_accessor :recipients
  300. # The date on which the message was sent. If not set (the default), the
  301. # header will be set by the delivery agent.
  302. adv_attr_accessor :sent_on
  303. # Specify the subject of the message.
  304. adv_attr_accessor :subject
  305. # Specify the template name to use for current message. This is the "base"
  306. # template name, without the extension or directory, and may be used to
  307. # have multiple mailer methods share the same template.
  308. adv_attr_accessor :template
  309. # The mail object instance referenced by this mailer.
  310. attr_reader :mail
  311. class << self
  312. def method_missing(method_symbol, *parameters)#:nodoc:
  313. case method_symbol.id2name
  314. when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
  315. when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
  316. when "new" then nil
  317. else super
  318. end
  319. end
  320. # Receives a raw email, parses it into an email object, decodes it,
  321. # instantiates a new mailer, and passes the email object to the mailer
  322. # object's #receive method. If you want your mailer to be able to
  323. # process incoming messages, you'll need to implement a #receive
  324. # method that accepts the email object as a parameter:
  325. #
  326. # class MyMailer < ActionMailer::Base
  327. # def receive(mail)
  328. # ...
  329. # end
  330. # end
  331. def receive(raw_email)
  332. logger.info "Received mail:\n #{raw_email}" unless logger.nil?
  333. mail = TMail::Mail.parse(raw_email)
  334. mail.base64_decode
  335. new.receive(mail)
  336. end
  337. # Deliver the given mail object directly. This can be used to deliver
  338. # a preconstructed mail object, like:
  339. #
  340. # email = MyMailer.create_some_mail(parameters)
  341. # email.set_some_obscure_header "frobnicate"
  342. # MyMailer.deliver(email)
  343. def deliver(mail)
  344. new.deliver!(mail)
  345. end
  346. end
  347. # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
  348. # will be initialized according to the named method. If not, the mailer will
  349. # remain uninitialized (useful when you only need to invoke the "receive"
  350. # method, for instance).
  351. def initialize(method_name=nil, *parameters) #:nodoc:
  352. create!(method_name, *parameters) if method_name
  353. end
  354. # Initialize the mailer via the given +method_name+. The body will be
  355. # rendered and a new TMail::Mail object created.
  356. def create!(method_name, *parameters) #:nodoc:
  357. initialize_defaults(method_name)
  358. send(method_name, *parameters)
  359. # If an explicit, textual body has not been set, we check assumptions.
  360. unless String === @body
  361. # First, we look to see if there are any likely templates that match,
  362. # which include the content-type in their file name (i.e.,
  363. # "the_template_file.text.html.rhtml", etc.). Only do this if parts
  364. # have not already been specified manually.
  365. if @parts.empty?
  366. templates = Dir.glob("#{template_path}/#{@template}.*")
  367. templates.each do |path|
  368. # TODO: don't hardcode rhtml|rxml
  369. basename = File.basename(path)
  370. next unless md = /^([^\.]+)\.([^\.]+\.[^\+]+)\.(rhtml|rxml)$/.match(basename)
  371. template_name = basename
  372. content_type = md.captures[1].gsub('.', '/')
  373. @parts << Part.new(:content_type => content_type,
  374. :disposition => "inline", :charset => charset,
  375. :body => render_message(template_name, @body))
  376. end
  377. unless @parts.empty?
  378. @content_type = "multipart/alternative"
  379. @parts = sort_parts(@parts, @implicit_parts_order)
  380. end
  381. end
  382. # Then, if there were such templates, we check to see if we ought to
  383. # also render a "normal" template (without the content type). If a
  384. # normal template exists (or if there were no implicit parts) we render
  385. # it.
  386. template_exists = @parts.empty?
  387. template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
  388. @body = render_message(@template, @body) if template_exists
  389. # Finally, if there are other message parts and a textual body exists,
  390. # we shift it onto the front of the parts and set the body to nil (so
  391. # that create_mail doesn't try to render it in addition to the parts).
  392. if !@parts.empty? && String === @body
  393. @parts.unshift Part.new(:charset => charset, :body => @body)
  394. @body = nil
  395. end
  396. end
  397. # If this is a multipart e-mail add the mime_version if it is not
  398. # already set.
  399. @mime_version ||= "1.0" if !@parts.empty?
  400. # build the mail object itself
  401. @mail = create_mail
  402. end
  403. # Delivers a TMail::Mail object. By default, it delivers the cached mail
  404. # object (from the #create! method). If no cached mail object exists, and
  405. # no alternate has been given as the parameter, this will fail.
  406. def deliver!(mail = @mail)
  407. raise "no mail object available for delivery!" unless mail
  408. logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
  409. begin
  410. send("perform_delivery_#{delivery_method}", mail) if perform_deliveries
  411. rescue Object => e
  412. raise e if raise_delivery_errors
  413. end
  414. return mail
  415. end
  416. private
  417. # Set up the default values for the various instance variables of this
  418. # mailer. Subclasses may override this method to provide different
  419. # defaults.
  420. def initialize_defaults(method_name)
  421. @charset ||= @@default_charset.dup
  422. @content_type ||= @@default_content_type.dup
  423. @implicit_parts_order ||= @@default_implicit_parts_order.dup
  424. @template ||= method_name
  425. @mailer_name ||= Inflector.underscore(self.class.name)
  426. @parts ||= []
  427. @headers ||= {}
  428. @body ||= {}
  429. @mime_version = @@default_mime_version.dup if @@default_mime_version
  430. end
  431. def render_message(method_name, body)
  432. render :file => method_name, :body => body
  433. end
  434. def render(opts)
  435. body = opts.delete(:body)
  436. initialize_template_class(body).render(opts)
  437. end
  438. def template_path
  439. "#{template_root}/#{mailer_name}"
  440. end
  441. def initialize_template_class(assigns)
  442. ActionView::Base.new(template_path, assigns, self)
  443. end
  444. def sort_parts(parts, order = [])
  445. order = order.collect { |s| s.downcase }
  446. parts = parts.sort do |a, b|
  447. a_ct = a.content_type.downcase
  448. b_ct = b.content_type.downcase
  449. a_in = order.include? a_ct
  450. b_in = order.include? b_ct
  451. s = case
  452. when a_in && b_in
  453. order.index(a_ct) <=> order.index(b_ct)
  454. when a_in
  455. -1
  456. when b_in
  457. 1
  458. else
  459. a_ct <=> b_ct
  460. end
  461. # reverse the ordering because parts that come last are displayed
  462. # first in mail clients
  463. (s * -1)
  464. end
  465. parts
  466. end
  467. def create_mail
  468. m = TMail::Mail.new
  469. m.subject, = quote_any_if_necessary(charset, subject)
  470. m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
  471. m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
  472. m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
  473. m.mime_version = mime_version unless mime_version.nil?
  474. m.date = sent_on.to_time rescue sent_on if sent_on
  475. headers.each { |k, v| m[k] = v }
  476. real_content_type, ctype_attrs = parse_content_type
  477. if @parts.empty?
  478. m.set_content_type(real_content_type, nil, ctype_attrs)
  479. m.body = Utils.normalize_new_lines(body)
  480. else
  481. if String === body
  482. part = TMail::Mail.new
  483. part.body = Utils.normalize_new_lines(body)
  484. part.set_content_type(real_content_type, nil, ctype_attrs)
  485. part.set_content_disposition "inline"
  486. m.parts << part
  487. end
  488. @parts.each do |p|
  489. part = (TMail::Mail === p ? p : p.to_mail(self))
  490. m.parts << part
  491. end
  492. if real_content_type =~ /multipart/
  493. ctype_attrs.delete "charset"
  494. m.set_content_type(real_content_type, nil, ctype_attrs)
  495. end
  496. end
  497. @mail = m
  498. end
  499. def perform_delivery_smtp(mail)
  500. destinations = mail.destinations
  501. mail.ready_to_send
  502. Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
  503. server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
  504. smtp.sendmail(mail.encoded, mail.from, destinations)
  505. end
  506. end
  507. def perform_delivery_sendmail(mail)
  508. IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
  509. sm.print(mail.encoded.gsub(/\r/, ''))
  510. sm.flush
  511. end
  512. end
  513. def perform_delivery_test(mail)
  514. deliveries << mail
  515. end
  516. end
  517. end
  518. module ActionMailer
  519. module Helpers #:nodoc:
  520. def self.append_features(base) #:nodoc:
  521. super
  522. # Initialize the base module to aggregate its helpers.
  523. base.class_inheritable_accessor :master_helper_module
  524. base.master_helper_module = Module.new
  525. # Extend base with class methods to declare helpers.
  526. base.extend(ClassMethods)
  527. base.class_eval do
  528. # Wrap inherited to create a new master helper module for subclasses.
  529. class << self
  530. alias_method :inherited_without_helper, :inherited
  531. alias_method :inherited, :inherited_with_helper
  532. end
  533. # Wrap initialize_template_class to extend new template class
  534. # instances with the master helper module.
  535. alias_method :initialize_template_class_without_helper, :initialize_template_class
  536. alias_method :initialize_template_class, :initialize_template_class_with_helper
  537. end
  538. end
  539. module ClassMethods
  540. # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
  541. # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
  542. # available to the templates.
  543. def add_template_helper(helper_module) #:nodoc:
  544. master_helper_module.module_eval "include #{helper_module}"
  545. end
  546. # Declare a helper:
  547. # helper :foo
  548. # requires 'foo_helper' and includes FooHelper in the template class.
  549. # helper FooHelper
  550. # includes FooHelper in the template class.
  551. # helper { def foo() "#{bar} is the very best" end }
  552. # evaluates the block in the template class, adding method #foo.
  553. # helper(:three, BlindHelper) { def mice() 'mice' end }
  554. # does all three.
  555. def helper(*args, &block)
  556. args.flatten.each do |arg|
  557. case arg
  558. when Module
  559. add_template_helper(arg)
  560. when String, Symbol
  561. file_name = arg.to_s.underscore + '_helper'
  562. class_name = file_name.camelize
  563. begin
  564. require_dependency(file_name)
  565. rescue LoadError => load_error
  566. requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
  567. msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
  568. raise LoadError.new(msg).copy_blame!(load_error)
  569. end
  570. add_template_helper(class_name.constantize)
  571. else
  572. raise ArgumentError, 'helper expects String, Symbol, or Module argument'
  573. end
  574. end
  575. # Evaluate block in template class if given.
  576. master_helper_module.module_eval(&block) if block_given?
  577. end
  578. # Declare a controller method as a helper. For example,
  579. # helper_method :link_to
  580. # def link_to(name, options) ... end
  581. # makes the link_to controller method available in the view.
  582. def helper_method(*methods)
  583. methods.flatten.each do |method|
  584. master_helper_module.module_eval <<-end_eval
  585. def #{method}(*args, &block)
  586. controller.send(%(#{method}), *args, &block)
  587. end
  588. end_eval
  589. end
  590. end
  591. # Declare a controller attribute as a helper. For example,
  592. # helper_attr :name
  593. # attr_accessor :name
  594. # makes the name and name= controller methods available in the view.
  595. # The is a convenience wrapper for helper_method.
  596. def helper_attr(*attrs)
  597. attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
  598. end
  599. private
  600. def inherited_with_helper(child)
  601. inherited_without_helper(child)
  602. begin
  603. child.master_helper_module = Module.new
  604. child.master_helper_module.send :include, master_helper_module
  605. child.helper child.name.underscore
  606. rescue MissingSourceFile => e
  607. raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
  608. end
  609. end
  610. end
  611. private
  612. # Extend the template class instance with our controller's helper module.
  613. def initialize_template_class_with_helper(assigns)
  614. returning(template = initialize_template_class_without_helper(assigns)) do
  615. template.extend self.class.master_helper_module
  616. end
  617. end
  618. end
  619. endrequire 'text/format'
  620. module MailHelper
  621. # Uses Text::Format to take the text and format it, indented two spaces for
  622. # each line, and wrapped at 72 columns.
  623. def block_format(text)
  624. formatted = text.split(/\n\r\n/).collect { |paragraph|
  625. Text::Format.new(
  626. :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
  627. ).format
  628. }.join("\n")
  629. # Make list points stand on their own line
  630. formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
  631. formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
  632. formatted
  633. end
  634. end
  635. require 'action_mailer/adv_attr_accessor'
  636. require 'action_mailer/part_container'
  637. require 'action_mailer/utils'
  638. module ActionMailer
  639. # Represents a subpart of an email message. It shares many similar
  640. # attributes of ActionMailer::Base. Although you can create parts manually
  641. # and add them to the #parts list of the mailer, it is easier
  642. # to use the helper methods in ActionMailer::PartContainer.
  643. class Part
  644. include ActionMailer::AdvAttrAccessor
  645. include ActionMailer::PartContainer
  646. # Represents the body of the part, as a string. This should not be a
  647. # Hash (like ActionMailer::Base), but if you want a template to be rendered
  648. # into the body of a subpart you can do it with the mailer's #render method
  649. # and assign the result here.
  650. adv_attr_accessor :body
  651. # Specify the charset for this subpart. By default, it will be the charset
  652. # of the containing part or mailer.
  653. adv_attr_accessor :charset
  654. # The content disposition of this part, typically either "inline" or
  655. # "attachment".
  656. adv_attr_accessor :content_disposition
  657. # The content type of the part.
  658. adv_attr_accessor :content_type
  659. # The filename to use for this subpart (usually for attachments).
  660. adv_attr_accessor :filename
  661. # Accessor for specifying additional headers to include with this part.
  662. adv_attr_accessor :headers
  663. # The transfer encoding to use for this subpart, like "base64" or
  664. # "quoted-printable".
  665. adv_attr_accessor :transfer_encoding
  666. # Create a new part from the given +params+ hash. The valid params keys
  667. # correspond to the accessors.
  668. def initialize(params)
  669. @content_type = params[:content_type]
  670. @content_disposition = params[:disposition] || "inline"
  671. @charset = params[:charset]
  672. @body = params[:body]
  673. @filename = params[:filename]
  674. @transfer_encoding = params[:transfer_encoding] || "quoted-printable"
  675. @headers = params[:headers] || {}
  676. @parts = []
  677. end
  678. # Convert the part to a mail object which can be included in the parts
  679. # list of another mail object.
  680. def to_mail(defaults)
  681. part = TMail::Mail.new
  682. real_content_type, ctype_attrs = parse_content_type(defaults)
  683. if @parts.empty?
  684. part.content_transfer_encoding = transfer_encoding || "quoted-printable"
  685. case (transfer_encoding || "").downcase
  686. when "base64" then
  687. part.body = TMail::Base64.folding_encode(body)
  688. when "quoted-printable"
  689. part.body = [Utils.normalize_new_lines(body)].pack("M*")
  690. else
  691. part.body = body
  692. end
  693. # Always set the content_type after setting the body and or parts!
  694. # Also don't set filename and name when there is none (like in
  695. # non-attachment parts)
  696. if content_disposition == "attachment"
  697. ctype_attrs.delete "charset"
  698. part.set_content_type(real_content_type, nil,
  699. squish("name" => filename).merge(ctype_attrs))
  700. part.set_content_disposition(content_disposition,
  701. squish("filename" => filename).merge(ctype_attrs))
  702. else
  703. part.set_content_type(real_content_type, nil, ctype_attrs)
  704. part.set_content_disposition(content_disposition)
  705. end
  706. else
  707. if String === body
  708. part = TMail::Mail.new
  709. part.body = body
  710. part.set_content_type(real_content_type, nil, ctype_attrs)
  711. part.set_content_disposition "inline"
  712. m.parts << part
  713. end
  714. @parts.each do |p|
  715. prt = (TMail::Mail === p ? p : p.to_mail(defaults))
  716. part.parts << prt
  717. end
  718. part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/
  719. end
  720. headers.each { |k,v| part[k] = v }
  721. part
  722. end
  723. private
  724. def squish(values={})
  725. values.delete_if { |k,v| v.nil? }
  726. end
  727. end
  728. end
  729. module ActionMailer
  730. # Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
  731. # in common. Using these helpers you can easily add subparts or attachments
  732. # to your message:
  733. #
  734. # def my_mail_message(...)
  735. # ...
  736. # part "text/plain" do |p|
  737. # p.body "hello, world"
  738. # p.transfer_encoding "base64"
  739. # end
  740. #
  741. # attachment "image/jpg" do |a|
  742. # a.body = File.read("hello.jpg")
  743. # a.filename = "hello.jpg"
  744. # end
  745. # end
  746. module PartContainer
  747. # The list of subparts of this container
  748. attr_reader :parts
  749. # Add a part to a multipart message, with the given content-type. The
  750. # part itself is yielded to the block so that other properties (charset,
  751. # body, headers, etc.) can be set on it.
  752. def part(params)
  753. params = {:content_type => params} if String === params
  754. part = Part.new(params)
  755. yield part if block_given?
  756. @parts << part
  757. end
  758. # Add an attachment to a multipart message. This is simply a part with the
  759. # content-disposition set to "attachment".
  760. def attachment(params, &block)
  761. params = { :content_type => params } if String === params
  762. params = { :disposition => "attachment",
  763. :transfer_encoding => "base64" }.merge(params)
  764. part(params, &block)
  765. end
  766. private
  767. def parse_content_type(defaults=nil)
  768. return [defaults && defaults.content_type, {}] if content_type.blank?
  769. ctype, *attrs = content_type.split(/;\s*/)
  770. attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
  771. [ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
  772. end
  773. end
  774. end
  775. module ActionMailer
  776. module Quoting #:nodoc:
  777. # Convert the given text into quoted printable format, with an instruction
  778. # that the text be eventually interpreted in the given charset.
  779. def quoted_printable(text, charset)
  780. text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
  781. gsub( / /, "_" )
  782. "=?#{charset}?Q?#{text}?="
  783. end
  784. # Convert the given character to quoted printable format, taking into
  785. # account multi-byte characters (if executing with $KCODE="u", for instance)
  786. def quoted_printable_encode(character)
  787. result = ""
  788. character.each_byte { |b| result << "=%02x" % b }
  789. result
  790. end
  791. # A quick-and-dirty regexp for determining whether a string contains any
  792. # characters that need escaping.
  793. if !defined?(CHARS_NEEDING_QUOTING)
  794. CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
  795. end
  796. # Quote the given text if it contains any "illegal" characters
  797. def quote_if_necessary(text, charset)
  798. (text =~ CHARS_NEEDING_QUOTING) ?
  799. quoted_printable(text, charset) :
  800. text
  801. end
  802. # Quote any of the given strings if they contain any "illegal" characters
  803. def quote_any_if_necessary(charset, *args)
  804. args.map { |v| quote_if_necessary(v, charset) }
  805. end
  806. # Quote the given address if it needs to be. The address may be a
  807. # regular email address, or it can be a phrase followed by an address in
  808. # brackets. The phrase is the only part that will be quoted, and only if
  809. # it needs to be. This allows extended characters to be used in the
  810. # "to", "from", "cc", and "bcc" headers.
  811. def quote_address_if_necessary(address, charset)
  812. if Array === address
  813. address.map { |a| quote_address_if_necessary(a, charset) }
  814. elsif address =~ /^(\S.*)\s+(<.*>)$/
  815. address = $2
  816. phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
  817. "\"#{phrase}\" #{address}"
  818. else
  819. address
  820. end
  821. end
  822. # Quote any of the given addresses, if they need to be.
  823. def quote_any_address_if_necessary(charset, *args)
  824. args.map { |v| quote_address_if_necessary(v, charset) }
  825. end
  826. end
  827. end
  828. module ActionMailer
  829. module Utils #:nodoc:
  830. def normalize_new_lines(text)
  831. text.to_s.gsub(/\r\n?/, "\n")
  832. end
  833. module_function :normalize_new_lines
  834. end
  835. end
  836. #--
  837. # Text::Format for Ruby
  838. # Version 0.63
  839. #
  840. # Copyright (c) 2002 - 2003 Austin Ziegler
  841. #
  842. # $Id: format.rb,v 1.1.1.1 2004/10/14 11:59:57 webster132 Exp $
  843. #
  844. # ==========================================================================
  845. # Revision History ::
  846. # YYYY.MM.DD Change ID Developer
  847. # Description
  848. # --------------------------------------------------------------------------
  849. # 2002.10.18 Austin Ziegler
  850. # Fixed a minor problem with tabs not being counted. Changed
  851. # abbreviations from Hash to Array to better suit Ruby's
  852. # capabilities. Fixed problems with the way that Array arguments
  853. # are handled in calls to the major object types, excepting in
  854. # Text::Format#expand and Text::Format#unexpand (these will
  855. # probably need to be fixed).
  856. # 2002.10.30 Austin Ziegler
  857. # Fixed the ordering of the <=> for binary tests. Fixed
  858. # Text::Format#expand and Text::Format#unexpand to handle array
  859. # arguments better.
  860. # 2003.01.24 Austin Ziegler
  861. # Fixed a problem with Text::Format::RIGHT_FILL handling where a
  862. # single word is larger than #columns. Removed Comparable
  863. # capabilities (<=> doesn't make sense; == does). Added Symbol
  864. # equivalents for the Hash initialization. Hash initialization has
  865. # been modified so that values are set as follows (Symbols are
  866. # highest priority; strings are middle; defaults are lowest):
  867. # @columns = arg[:columns] || arg['columns'] || @columns
  868. # Added #hard_margins, #split_rules, #hyphenator, and #split_words.
  869. # 2003.02.07 Austin Ziegler
  870. # Fixed the installer for proper case-sensitive handling.
  871. # 2003.03.28 Austin Ziegler
  872. # Added the ability for a hyphenator to receive the formatter
  873. # object. Fixed a bug for strings matching /\A\s*\Z/ failing
  874. # entirely. Fixed a test case failing under 1.6.8.
  875. # 2003.04.04 Austin Ziegler
  876. # Handle the case of hyphenators returning nil for first/rest.
  877. # 2003.09.17 Austin Ziegler
  878. # Fixed a problem where #paragraphs(" ") was raising
  879. # NoMethodError.
  880. #
  881. # ==========================================================================
  882. #++
  883. module Text #:nodoc:
  884. # Text::Format for Ruby is copyright 2002 - 2005 by Austin Ziegler. It
  885. # is available under Ruby's licence, the Perl Artistic licence, or the
  886. # GNU GPL version 2 (or at your option, any later version). As a
  887. # special exception, for use with official Rails (provided by the
  888. # rubyonrails.org development team) and any project created with
  889. # official Rails, the following alternative MIT-style licence may be
  890. # used:
  891. #
  892. # == Text::Format Licence for Rails and Rails Applications
  893. # Permission is hereby granted, free of charge, to any person
  894. # obtaining a copy of this software and associated documentation files
  895. # (the "Software"), to deal in the Software without restriction,
  896. # including without limitation the rights to use, copy, modify, merge,
  897. # publish, distribute, sublicense, and/or sell copies of the Software,
  898. # and to permit persons to whom the Software is furnished to do so,
  899. # subject to the following conditions:
  900. #
  901. # * The names of its contributors may not be used to endorse or
  902. # promote products derived from this software without specific prior
  903. # written permission.
  904. #
  905. # The above copyright notice and this permission notice shall be
  906. # included in all copies or substantial portions of the Software.
  907. #
  908. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  909. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  910. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  911. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  912. # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  913. # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  914. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  915. # SOFTWARE.
  916. class Format
  917. VERSION = '0.63'
  918. # Local abbreviations. More can be added with Text::Format.abbreviations
  919. ABBREV = [ 'Mr', 'Mrs', 'Ms', 'Jr', 'Sr' ]
  920. # Formatting values
  921. LEFT_ALIGN = 0
  922. RIGHT_ALIGN = 1
  923. RIGHT_FILL = 2
  924. JUSTIFY = 3
  925. # Word split modes (only applies when #hard_margins is true).
  926. SPLIT_FIXED = 1
  927. SPLIT_CONTINUATION = 2
  928. SPLIT_HYPHENATION = 4
  929. SPLIT_CONTINUATION_FIXED = SPLIT_CONTINUATION | SPLIT_FIXED
  930. SPLIT_HYPHENATION_FIXED = SPLIT_HYPHENATION | SPLIT_FIXED
  931. SPLIT_HYPHENATION_CONTINUATION = SPLIT_HYPHENATION | SPLIT_CONTINUATION
  932. SPLIT_ALL = SPLIT_HYPHENATION | SPLIT_CONTINUATION | SPLIT_FIXED
  933. # Words forcibly split by Text::Format will be stored as split words.
  934. # This class represents a word forcibly split.
  935. class SplitWord
  936. # The word that was split.
  937. attr_reader :word
  938. # The first part of the word that was split.
  939. attr_reader :first
  940. # The remainder of the word that was split.
  941. attr_reader :rest
  942. def initialize(word, first, rest) #:nodoc:
  943. @word = word
  944. @first = first
  945. @rest = rest
  946. end
  947. end
  948. private
  949. LEQ_RE = /[.?!]['"]?$/
  950. def brk_re(i) #:nodoc:
  951. %r/((?:\S+\s+){#{i}})(.+)/
  952. end
  953. def posint(p) #:nodoc:
  954. p.to_i.abs
  955. end
  956. public
  957. # Compares two Text::Format objects. All settings of the objects are
  958. # compared *except* #hyphenator. Generated results (e.g., #split_words)
  959. # are not compared, either.
  960. def ==(o)
  961. (@text == o.text) &&
  962. (@columns == o.columns) &&
  963. (@left_margin == o.left_margin) &&
  964. (@right_margin == o.right_margin) &&
  965. (@hard_margins == o.hard_margins) &&
  966. (@split_rules == o.split_rules) &&
  967. (@first_indent == o.first_indent) &&
  968. (@body_indent == o.body_indent) &&
  969. (@tag_text == o.tag_text) &&
  970. (@tabstop == o.tabstop) &&
  971. (@format_style == o.format_style) &&
  972. (@extra_space == o.extra_space) &&
  973. (@tag_paragraph == o.tag_paragraph) &&
  974. (@nobreak == o.nobreak) &&
  975. (@abbreviations == o.abbreviations) &&
  976. (@nobreak_regex == o.nobreak_regex)
  977. end
  978. # The text to be manipulated. Note that value is optional, but if the
  979. # formatting functions are called without values, this text is what will
  980. # be formatted.
  981. #
  982. # *Default*:: <tt>[]</tt>
  983. # <b>Used in</b>:: All methods
  984. attr_accessor :text
  985. # The total width of the format area. The margins, indentation, and text
  986. # are formatted into this space.
  987. #
  988. # COLUMNS
  989. # <-------------------------------------------------------------->
  990. # <-----------><------><---------------------------><------------>
  991. # left margin indent text is formatted into here right margin
  992. #
  993. # *Default*:: <tt>72</tt>
  994. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  995. # <tt>#center</tt>
  996. attr_reader :columns
  997. # The total width of the format area. The margins, indentation, and text
  998. # are formatted into this space. The value provided is silently
  999. # converted to a positive integer.
  1000. #
  1001. # COLUMNS
  1002. # <-------------------------------------------------------------->
  1003. # <-----------><------><---------------------------><------------>
  1004. # left margin indent text is formatted into here right margin
  1005. #
  1006. # *Default*:: <tt>72</tt>
  1007. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1008. # <tt>#center</tt>
  1009. def columns=(c)
  1010. @columns = posint(c)
  1011. end
  1012. # The number of spaces used for the left margin.
  1013. #
  1014. # columns
  1015. # <-------------------------------------------------------------->
  1016. # <-----------><------><---------------------------><------------>
  1017. # LEFT MARGIN indent text is formatted into here right margin
  1018. #
  1019. # *Default*:: <tt>0</tt>
  1020. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1021. # <tt>#center</tt>
  1022. attr_reader :left_margin
  1023. # The number of spaces used for the left margin. The value provided is
  1024. # silently converted to a positive integer value.
  1025. #
  1026. # columns
  1027. # <-------------------------------------------------------------->
  1028. # <-----------><------><---------------------------><------------>
  1029. # LEFT MARGIN indent text is formatted into here right margin
  1030. #
  1031. # *Default*:: <tt>0</tt>
  1032. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1033. # <tt>#center</tt>
  1034. def left_margin=(left)
  1035. @left_margin = posint(left)
  1036. end
  1037. # The number of spaces used for the right margin.
  1038. #
  1039. # columns
  1040. # <-------------------------------------------------------------->
  1041. # <-----------><------><---------------------------><------------>
  1042. # left margin indent text is formatted into here RIGHT MARGIN
  1043. #
  1044. # *Default*:: <tt>0</tt>
  1045. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1046. # <tt>#center</tt>
  1047. attr_reader :right_margin
  1048. # The number of spaces used for the right margin. The value provided is
  1049. # silently converted to a positive integer value.
  1050. #
  1051. # columns
  1052. # <-------------------------------------------------------------->
  1053. # <-----------><------><---------------------------><------------>
  1054. # left margin indent text is formatted into here RIGHT MARGIN
  1055. #
  1056. # *Default*:: <tt>0</tt>
  1057. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1058. # <tt>#center</tt>
  1059. def right_margin=(r)
  1060. @right_margin = posint(r)
  1061. end
  1062. # The number of spaces to indent the first line of a paragraph.
  1063. #
  1064. # columns
  1065. # <-------------------------------------------------------------->
  1066. # <-----------><------><---------------------------><------------>
  1067. # left margin INDENT text is formatted into here right margin
  1068. #
  1069. # *Default*:: <tt>4</tt>
  1070. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1071. attr_reader :first_indent
  1072. # The number of spaces to indent the first line of a paragraph. The
  1073. # value provided is silently converted to a positive integer value.
  1074. #
  1075. # columns
  1076. # <-------------------------------------------------------------->
  1077. # <-----------><------><---------------------------><------------>
  1078. # left margin INDENT text is formatted into here right margin
  1079. #
  1080. # *Default*:: <tt>4</tt>
  1081. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1082. def first_indent=(f)
  1083. @first_indent = posint(f)
  1084. end
  1085. # The number of spaces to indent all lines after the first line of a
  1086. # paragraph.
  1087. #
  1088. # columns
  1089. # <-------------------------------------------------------------->
  1090. # <-----------><------><---------------------------><------------>
  1091. # left margin INDENT text is formatted into here right margin
  1092. #
  1093. # *Default*:: <tt>0</tt>
  1094. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1095. attr_reader :body_indent
  1096. # The number of spaces to indent all lines after the first line of
  1097. # a paragraph. The value provided is silently converted to a
  1098. # positive integer value.
  1099. #
  1100. # columns
  1101. # <-------------------------------------------------------------->
  1102. # <-----------><------><---------------------------><------------>
  1103. # left margin INDENT text is formatted into here right margin
  1104. #
  1105. # *Default*:: <tt>0</tt>
  1106. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1107. def body_indent=(b)
  1108. @body_indent = posint(b)
  1109. end
  1110. # Normally, words larger than the format area will be placed on a line
  1111. # by themselves. Setting this to +true+ will force words larger than the
  1112. # format area to be split into one or more "words" each at most the size
  1113. # of the format area. The first line and the original word will be
  1114. # placed into <tt>#split_words</tt>. Note that this will cause the
  1115. # output to look *similar* to a #format_style of JUSTIFY. (Lines will be
  1116. # filled as much as possible.)
  1117. #
  1118. # *Default*:: +false+
  1119. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1120. attr_accessor :hard_margins
  1121. # An array of words split during formatting if #hard_margins is set to
  1122. # +true+.
  1123. # #split_words << Text::Format::SplitWord.new(word, first, rest)
  1124. attr_reader :split_words
  1125. # The object responsible for hyphenating. It must respond to
  1126. # #hyphenate_to(word, size) or #hyphenate_to(word, size, formatter) and
  1127. # return an array of the word split into two parts; if there is a
  1128. # hyphenation mark to be applied, responsibility belongs to the
  1129. # hyphenator object. The size is the MAXIMUM size permitted, including
  1130. # any hyphenation marks. If the #hyphenate_to method has an arity of 3,
  1131. # the formatter will be provided to the method. This allows the
  1132. # hyphenator to make decisions about the hyphenation based on the
  1133. # formatting rules.
  1134. #
  1135. # *Default*:: +nil+
  1136. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1137. attr_reader :hyphenator
  1138. # The object responsible for hyphenating. It must respond to
  1139. # #hyphenate_to(word, size) and return an array of the word hyphenated
  1140. # into two parts. The size is the MAXIMUM size permitted, including any
  1141. # hyphenation marks.
  1142. #
  1143. # *Default*:: +nil+
  1144. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1145. def hyphenator=(h)
  1146. raise ArgumentError, "#{h.inspect} is not a valid hyphenator." unless h.respond_to?(:hyphenate_to)
  1147. arity = h.method(:hyphenate_to).arity
  1148. raise ArgumentError, "#{h.inspect} must have exactly two or three arguments." unless [2, 3].include?(arity)
  1149. @hyphenator = h
  1150. @hyphenator_arity = arity
  1151. end
  1152. # Specifies the split mode; used only when #hard_margins is set to
  1153. # +true+. Allowable values are:
  1154. # [+SPLIT_FIXED+] The word will be split at the number of
  1155. # characters needed, with no marking at all.
  1156. # repre
  1157. # senta
  1158. # ion
  1159. # [+SPLIT_CONTINUATION+] The word will be split at the number of
  1160. # characters needed, with a C-style continuation
  1161. # character. If a word is the only item on a
  1162. # line and it cannot be split into an
  1163. # appropriate size, SPLIT_FIXED will be used.
  1164. # repr\
  1165. # esen\
  1166. # tati\
  1167. # on
  1168. # [+SPLIT_HYPHENATION+] The word will be split according to the
  1169. # hyphenator specified in #hyphenator. If there
  1170. # is no #hyphenator specified, works like
  1171. # SPLIT_CONTINUATION. The example is using
  1172. # TeX::Hyphen. If a word is the only item on a
  1173. # line and it cannot be split into an
  1174. # appropriate size, SPLIT_CONTINUATION mode will
  1175. # be used.
  1176. # rep-
  1177. # re-
  1178. # sen-
  1179. # ta-
  1180. # tion
  1181. #
  1182. # *Default*:: <tt>Text::Format::SPLIT_FIXED</tt>
  1183. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1184. attr_reader :split_rules
  1185. # Specifies the split mode; used only when #hard_margins is set to
  1186. # +true+. Allowable values are:
  1187. # [+SPLIT_FIXED+] The word will be split at the number of
  1188. # characters needed, with no marking at all.
  1189. # repre
  1190. # senta
  1191. # ion
  1192. # [+SPLIT_CONTINUATION+] The word will be split at the number of
  1193. # characters needed, with a C-style continuation
  1194. # character.
  1195. # repr\
  1196. # esen\
  1197. # tati\
  1198. # on
  1199. # [+SPLIT_HYPHENATION+] The word will be split according to the
  1200. # hyphenator specified in #hyphenator. If there
  1201. # is no #hyphenator specified, works like
  1202. # SPLIT_CONTINUATION. The example is using
  1203. # TeX::Hyphen as the #hyphenator.
  1204. # rep-
  1205. # re-
  1206. # sen-
  1207. # ta-
  1208. # tion
  1209. #
  1210. # These values can be bitwise ORed together (e.g., <tt>SPLIT_FIXED |
  1211. # SPLIT_CONTINUATION</tt>) to provide fallback split methods. In the
  1212. # example given, an attempt will be made to split the word using the
  1213. # rules of SPLIT_CONTINUATION; if there is not enough room, the word
  1214. # will be split with the rules of SPLIT_FIXED. These combinations are
  1215. # also available as the following values:
  1216. # * +SPLIT_CONTINUATION_FIXED+
  1217. # * +SPLIT_HYPHENATION_FIXED+
  1218. # * +SPLIT_HYPHENATION_CONTINUATION+
  1219. # * +SPLIT_ALL+
  1220. #
  1221. # *Default*:: <tt>Text::Format::SPLIT_FIXED</tt>
  1222. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1223. def split_rules=(s)
  1224. raise ArgumentError, "Invalid value provided for split_rules." if ((s < SPLIT_FIXED) || (s > SPLIT_ALL))
  1225. @split_rules = s
  1226. end
  1227. # Indicates whether sentence terminators should be followed by a single
  1228. # space (+false+), or two spaces (+true+).
  1229. #
  1230. # *Default*:: +false+
  1231. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1232. attr_accessor :extra_space
  1233. # Defines the current abbreviations as an array. This is only used if
  1234. # extra_space is turned on.
  1235. #
  1236. # If one is abbreviating "President" as "Pres." (abbreviations =
  1237. # ["Pres"]), then the results of formatting will be as illustrated in
  1238. # the table below:
  1239. #
  1240. # extra_space | include? | !include?
  1241. # true | Pres. Lincoln | Pres. Lincoln
  1242. # false | Pres. Lincoln | Pres. Lincoln
  1243. #
  1244. # *Default*:: <tt>{}</tt>
  1245. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1246. attr_accessor :abbreviations
  1247. # Indicates whether the formatting of paragraphs should be done with
  1248. # tagged paragraphs. Useful only with <tt>#tag_text</tt>.
  1249. #
  1250. # *Default*:: +false+
  1251. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1252. attr_accessor :tag_paragraph
  1253. # The array of text to be placed before each paragraph when
  1254. # <tt>#tag_paragraph</tt> is +true+. When <tt>#format()</tt> is called,
  1255. # only the first element of the array is used. When <tt>#paragraphs</tt>
  1256. # is called, then each entry in the array will be used once, with
  1257. # corresponding paragraphs. If the tag elements are exhausted before the
  1258. # text is exhausted, then the remaining paragraphs will not be tagged.
  1259. # Regardless of indentation settings, a blank line will be inserted
  1260. # between all paragraphs when <tt>#tag_paragraph</tt> is +true+.
  1261. #
  1262. # *Default*:: <tt>[]</tt>
  1263. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1264. attr_accessor :tag_text
  1265. # Indicates whether or not the non-breaking space feature should be
  1266. # used.
  1267. #
  1268. # *Default*:: +false+
  1269. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1270. attr_accessor :nobreak
  1271. # A hash which holds the regular expressions on which spaces should not
  1272. # be broken. The hash is set up such that the key is the first word and
  1273. # the value is the second word.
  1274. #
  1275. # For example, if +nobreak_regex+ contains the following hash:
  1276. #
  1277. # { '^Mrs?\.$' => '\S+$', '^\S+$' => '^(?:S|J)r\.$'}
  1278. #
  1279. # Then "Mr. Jones", "Mrs. Jones", and "Jones Jr." would not be broken.
  1280. # If this simple matching algorithm indicates that there should not be a
  1281. # break at the current end of line, then a backtrack is done until there
  1282. # are two words on which line breaking is permitted. If two such words
  1283. # are not found, then the end of the line will be broken *regardless*.
  1284. # If there is a single word on the current line, then no backtrack is
  1285. # done and the word is stuck on the end.
  1286. #
  1287. # *Default*:: <tt>{}</tt>
  1288. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1289. attr_accessor :nobreak_regex
  1290. # Indicates the number of spaces that a single tab represents.
  1291. #
  1292. # *Default*:: <tt>8</tt>
  1293. # <b>Used in</b>:: <tt>#expand</tt>, <tt>#unexpand</tt>,
  1294. # <tt>#paragraphs</tt>
  1295. attr_reader :tabstop
  1296. # Indicates the number of spaces that a single tab represents.
  1297. #
  1298. # *Default*:: <tt>8</tt>
  1299. # <b>Used in</b>:: <tt>#expand</tt>, <tt>#unexpand</tt>,
  1300. # <tt>#paragraphs</tt>
  1301. def tabstop=(t)
  1302. @tabstop = posint(t)
  1303. end
  1304. # Specifies the format style. Allowable values are:
  1305. # [+LEFT_ALIGN+] Left justified, ragged right.
  1306. # |A paragraph that is|
  1307. # |left aligned.|
  1308. # [+RIGHT_ALIGN+] Right justified, ragged left.
  1309. # |A paragraph that is|
  1310. # | right aligned.|
  1311. # [+RIGHT_FILL+] Left justified, right ragged, filled to width by
  1312. # spaces. (Essentially the same as +LEFT_ALIGN+ except
  1313. # that lines are padded on the right.)
  1314. # |A paragraph that is|
  1315. # |left aligned. |
  1316. # [+JUSTIFY+] Fully justified, words filled to width by spaces,
  1317. # except the last line.
  1318. # |A paragraph that|
  1319. # |is justified.|
  1320. #
  1321. # *Default*:: <tt>Text::Format::LEFT_ALIGN</tt>
  1322. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1323. attr_reader :format_style
  1324. # Specifies the format style. Allowable values are:
  1325. # [+LEFT_ALIGN+] Left justified, ragged right.
  1326. # |A paragraph that is|
  1327. # |left aligned.|
  1328. # [+RIGHT_ALIGN+] Right justified, ragged left.
  1329. # |A paragraph that is|
  1330. # | right aligned.|
  1331. # [+RIGHT_FILL+] Left justified, right ragged, filled to width by
  1332. # spaces. (Essentially the same as +LEFT_ALIGN+ except
  1333. # that lines are padded on the right.)
  1334. # |A paragraph that is|
  1335. # |left aligned. |
  1336. # [+JUSTIFY+] Fully justified, words filled to width by spaces.
  1337. # |A paragraph that|
  1338. # |is justified.|
  1339. #
  1340. # *Default*:: <tt>Text::Format::LEFT_ALIGN</tt>
  1341. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1342. def format_style=(fs)
  1343. raise ArgumentError, "Invalid value provided for format_style." if ((fs < LEFT_ALIGN) || (fs > JUSTIFY))
  1344. @format_style = fs
  1345. end
  1346. # Indicates that the format style is left alignment.
  1347. #
  1348. # *Default*:: +true+
  1349. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1350. def left_align?
  1351. return @format_style == LEFT_ALIGN
  1352. end
  1353. # Indicates that the format style is right alignment.
  1354. #
  1355. # *Default*:: +false+
  1356. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1357. def right_align?
  1358. return @format_style == RIGHT_ALIGN
  1359. end
  1360. # Indicates that the format style is right fill.
  1361. #
  1362. # *Default*:: +false+
  1363. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1364. def right_fill?
  1365. return @format_style == RIGHT_FILL
  1366. end
  1367. # Indicates that the format style is full justification.
  1368. #
  1369. # *Default*:: +false+
  1370. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1371. def justify?
  1372. return @format_style == JUSTIFY
  1373. end
  1374. # The default implementation of #hyphenate_to implements
  1375. # SPLIT_CONTINUATION.
  1376. def hyphenate_to(word, size)
  1377. [word[0 .. (size - 2)] + "\\", word[(size - 1) .. -1]]
  1378. end
  1379. private
  1380. def __do_split_word(word, size) #:nodoc:
  1381. [word[0 .. (size - 1)], word[size .. -1]]
  1382. end
  1383. def __format(to_wrap) #:nodoc:
  1384. words = to_wrap.split(/\s+/).compact
  1385. words.shift if words[0].nil? or words[0].empty?
  1386. to_wrap = []
  1387. abbrev = false
  1388. width = @columns - @first_indent - @left_margin - @right_margin
  1389. indent_str = ' ' * @first_indent
  1390. first_line = true
  1391. line = words.shift
  1392. abbrev = __is_abbrev(line) unless line.nil? || line.empty?
  1393. while w = words.shift
  1394. if (w.size + line.size < (width - 1)) ||
  1395. ((line !~ LEQ_RE || abbrev) && (w.size + line.size < width))
  1396. line << " " if (line =~ LEQ_RE) && (not abbrev)
  1397. line << " #{w}"
  1398. else
  1399. line, w = __do_break(line, w) if @nobreak
  1400. line, w = __do_hyphenate(line, w, width) if @hard_margins
  1401. if w.index(/\s+/)
  1402. w, *w2 = w.split(/\s+/)
  1403. words.unshift(w2)
  1404. words.flatten!
  1405. end
  1406. to_wrap << __make_line(line, indent_str, width, w.nil?) unless line.nil?
  1407. if first_line
  1408. first_line = false
  1409. width = @columns - @body_indent - @left_margin - @right_margin
  1410. indent_str = ' ' * @body_indent
  1411. end
  1412. line = w
  1413. end
  1414. abbrev = __is_abbrev(w) unless w.nil?
  1415. end
  1416. loop do
  1417. break if line.nil? or line.empty?
  1418. line, w = __do_hyphenate(line, w, width) if @hard_margins
  1419. to_wrap << __make_line(line, indent_str, width, w.nil?)
  1420. line = w
  1421. end
  1422. if (@tag_paragraph && (to_wrap.size > 0)) then
  1423. clr = %r{`(\w+)'}.match([caller(1)].flatten[0])[1]
  1424. clr = "" if clr.nil?
  1425. if ((not @tag_text[0].nil?) && (@tag_cur.size < 1) &&
  1426. (clr != "__paragraphs")) then
  1427. @tag_cur = @tag_text[0]
  1428. end
  1429. fchar = /(\S)/.match(to_wrap[0])[1]
  1430. white = to_wrap[0].index(fchar)
  1431. if ((white - @left_margin - 1) > @tag_cur.size) then
  1432. white = @tag_cur.size + @left_margin
  1433. to_wrap[0].gsub!(/^ {#{white}}/, "#{' ' * @left_margin}#{@tag_cur}")
  1434. else
  1435. to_wrap.unshift("#{' ' * @left_margin}#{@tag_cur}\n")
  1436. end
  1437. end
  1438. to_wrap.join('')
  1439. end
  1440. # format lines in text into paragraphs with each element of @wrap a
  1441. # paragraph; uses Text::Format.format for the formatting
  1442. def __paragraphs(to_wrap) #:nodoc:
  1443. if ((@first_indent == @body_indent) || @tag_paragraph) then
  1444. p_end = "\n"
  1445. else
  1446. p_end = ''
  1447. end
  1448. cnt = 0
  1449. ret = []
  1450. to_wrap.each do |tw|
  1451. @tag_cur = @tag_text[cnt] if @tag_paragraph
  1452. @tag_cur = '' if @tag_cur.nil?
  1453. line = __format(tw)
  1454. ret << "#{line}#{p_end}" if (not line.nil?) && (line.size > 0)
  1455. cnt += 1
  1456. end
  1457. ret[-1].chomp! unless ret.empty?
  1458. ret.join('')
  1459. end
  1460. # center text using spaces on left side to pad it out empty lines
  1461. # are preserved
  1462. def __center(to_center) #:nodoc:
  1463. tabs = 0
  1464. width = @columns - @left_margin - @right_margin
  1465. centered = []
  1466. to_center.each do |tc|
  1467. s = tc.strip
  1468. tabs = s.count("\t")
  1469. tabs = 0 if tabs.nil?
  1470. ct = ((width - s.size - (tabs * @tabstop) + tabs) / 2)
  1471. ct = (width - @left_margin - @right_margin) - ct
  1472. centered << "#{s.rjust(ct)}\n"
  1473. end
  1474. centered.join('')
  1475. end
  1476. # expand tabs to spaces should be similar to Text::Tabs::expand
  1477. def __expand(to_expand) #:nodoc:
  1478. expanded = []
  1479. to_expand.split("\n").each { |te| expanded << te.gsub(/\t/, ' ' * @tabstop) }
  1480. expanded.join('')
  1481. end
  1482. def __unexpand(to_unexpand) #:nodoc:
  1483. unexpanded = []
  1484. to_unexpand.split("\n").each { |tu| unexpanded << tu.gsub(/ {#{@tabstop}}/, "\t") }
  1485. unexpanded.join('')
  1486. end
  1487. def __is_abbrev(word) #:nodoc:
  1488. # remove period if there is one.
  1489. w = word.gsub(/\.$/, '') unless word.nil?
  1490. return true if (!@extra_space || ABBREV.include?(w) || @abbreviations.include?(w))
  1491. false
  1492. end
  1493. def __make_line(line, indent, width, last = false) #:nodoc:
  1494. lmargin = " " * @left_margin
  1495. fill = " " * (width - line.size) if right_fill? && (line.size <= width)
  1496. if (justify? && ((not line.nil?) && (not line.empty?)) && line =~ /\S+\s+\S+/ && !last)
  1497. spaces = width - line.size
  1498. words = line.split(/(\s+)/)
  1499. ws = spaces / (words.size / 2)
  1500. spaces = spaces % (words.size / 2) if ws > 0
  1501. words.reverse.each do |rw|
  1502. next if (rw =~ /^\S/)
  1503. rw.sub!(/^/, " " * ws)
  1504. next unless (spaces > 0)
  1505. rw.sub!(/^/, " ")
  1506. spaces -= 1
  1507. end
  1508. line = words.join('')
  1509. end
  1510. line = "#{lmargin}#{indent}#{line}#{fill}\n" unless line.nil?
  1511. if right_align? && (not line.nil?)
  1512. line.sub(/^/, " " * (@columns - @right_margin - (line.size - 1)))
  1513. else
  1514. line
  1515. end
  1516. end
  1517. def __do_hyphenate(line, next_line, width) #:nodoc:
  1518. rline = line.dup rescue line
  1519. rnext = next_line.dup rescue next_line
  1520. loop do
  1521. if rline.size == width
  1522. break
  1523. elsif rline.size > width
  1524. words = rline.strip.split(/\s+/)
  1525. word = words[-1].dup
  1526. size = width - rline.size + word.size
  1527. if (size <= 0)
  1528. words[-1] = nil
  1529. rline = words.join(' ').strip
  1530. rnext = "#{word} #{rnext}".strip
  1531. next
  1532. end
  1533. first = rest = nil
  1534. if ((@split_rules & SPLIT_HYPHENATION) != 0)
  1535. if @hyphenator_arity == 2
  1536. first, rest = @hyphenator.hyphenate_to(word, size)
  1537. else
  1538. first, rest = @hyphenator.hyphenate_to(word, size, self)
  1539. end
  1540. end
  1541. if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
  1542. first, rest = self.hyphenate_to(word, size)
  1543. end
  1544. if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
  1545. first.nil? or @split_rules == SPLIT_FIXED
  1546. first, rest = __do_split_word(word, size)
  1547. end
  1548. if first.nil?
  1549. words[-1] = nil
  1550. rest = word
  1551. else
  1552. words[-1] = first
  1553. @split_words << SplitWord.new(word, first, rest)
  1554. end
  1555. rline = words.join(' ').strip
  1556. rnext = "#{rest} #{rnext}".strip
  1557. break
  1558. else
  1559. break if rnext.nil? or rnext.empty? or rline.nil? or rline.empty?
  1560. words = rnext.split(/\s+/)
  1561. word = words.shift
  1562. size = width - rline.size - 1
  1563. if (size <= 0)
  1564. rnext = "#{word} #{words.join(' ')}".strip
  1565. break
  1566. end
  1567. first = rest = nil
  1568. if ((@split_rules & SPLIT_HYPHENATION) != 0)
  1569. if @hyphenator_arity == 2
  1570. first, rest = @hyphenator.hyphenate_to(word, size)
  1571. else
  1572. first, rest = @hyphenator.hyphenate_to(word, size, self)
  1573. end
  1574. end
  1575. first, rest = self.hyphenate_to(word, size) if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
  1576. first, rest = __do_split_word(word, size) if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
  1577. if (rline.size + (first ? first.size : 0)) < width
  1578. @split_words << SplitWord.new(word, first, rest)
  1579. rline = "#{rline} #{first}".strip
  1580. rnext = "#{rest} #{words.join(' ')}".strip
  1581. end
  1582. break
  1583. end
  1584. end
  1585. [rline, rnext]
  1586. end
  1587. def __do_break(line, next_line) #:nodoc:
  1588. no_brk = false
  1589. words = []
  1590. words = line.split(/\s+/) unless line.nil?
  1591. last_word = words[-1]
  1592. @nobreak_regex.each { |k, v| no_brk = ((last_word =~ /#{k}/) and (next_line =~ /#{v}/)) }
  1593. if no_brk && words.size > 1
  1594. i = words.size
  1595. while i > 0
  1596. no_brk = false
  1597. @nobreak_regex.each { |k, v| no_brk = ((words[i + 1] =~ /#{k}/) && (words[i] =~ /#{v}/)) }
  1598. i -= 1
  1599. break if not no_brk
  1600. end
  1601. if i > 0
  1602. l = brk_re(i).match(line)
  1603. line.sub!(brk_re(i), l[1])
  1604. next_line = "#{l[2]} #{next_line}"
  1605. line.sub!(/\s+$/, '')
  1606. end
  1607. end
  1608. [line, next_line]
  1609. end
  1610. def __create(arg = nil, &block) #:nodoc:
  1611. # Format::Text.new(text-to-wrap)
  1612. @text = arg unless arg.nil?
  1613. # Defaults
  1614. @columns = 72
  1615. @tabstop = 8
  1616. @first_indent = 4
  1617. @body_indent = 0
  1618. @format_style = LEFT_ALIGN
  1619. @left_margin = 0
  1620. @right_margin = 0
  1621. @extra_space = false
  1622. @text = Array.new if @text.nil?
  1623. @tag_paragraph = false
  1624. @tag_text = Array.new
  1625. @tag_cur = ""
  1626. @abbreviations = Array.new
  1627. @nobreak = false
  1628. @nobreak_regex = Hash.new
  1629. @split_words = Array.new
  1630. @hard_margins = false
  1631. @split_rules = SPLIT_FIXED
  1632. @hyphenator = self
  1633. @hyphenator_arity = self.method(:hyphenate_to).arity
  1634. instance_eval(&block) unless block.nil?
  1635. end
  1636. public
  1637. # Formats text into a nice paragraph format. The text is separated
  1638. # into words and then reassembled a word at a time using the settings
  1639. # of this Format object. If a word is larger than the number of
  1640. # columns available for formatting, then that word will appear on the
  1641. # line by itself.
  1642. #
  1643. # If +to_wrap+ is +nil+, then the value of <tt>#text</tt> will be
  1644. # worked on.
  1645. def format(to_wrap = nil)
  1646. to_wrap = @text if to_wrap.nil?
  1647. if to_wrap.class == Array
  1648. __format(to_wrap[0])
  1649. else
  1650. __format(to_wrap)
  1651. end
  1652. end
  1653. # Considers each element of text (provided or internal) as a paragraph.
  1654. # If <tt>#first_indent</tt> is the same as <tt>#body_indent</tt>, then
  1655. # paragraphs will be separated by a single empty line in the result;
  1656. # otherwise, the paragraphs will follow immediately after each other.
  1657. # Uses <tt>#format</tt> to do the heavy lifting.
  1658. def paragraphs(to_wrap = nil)
  1659. to_wrap = @text if to_wrap.nil?
  1660. __paragraphs([to_wrap].flatten)
  1661. end
  1662. # Centers the text, preserving empty lines and tabs.
  1663. def center(to_center = nil)
  1664. to_center = @text if to_center.nil?
  1665. __center([to_center].flatten)
  1666. end
  1667. # Replaces all tab characters in the text with <tt>#tabstop</tt> spaces.
  1668. def expand(to_expand = nil)
  1669. to_expand = @text if to_expand.nil?
  1670. if to_expand.class == Array
  1671. to_expand.collect { |te| __expand(te) }
  1672. else
  1673. __expand(to_expand)
  1674. end
  1675. end
  1676. # Replaces all occurrences of <tt>#tabstop</tt> consecutive spaces
  1677. # with a tab character.
  1678. def unexpand(to_unexpand = nil)
  1679. to_unexpand = @text if to_unexpand.nil?
  1680. if to_unexpand.class == Array
  1681. to_unexpand.collect { |te| v << __unexpand(te) }
  1682. else
  1683. __unexpand(to_unexpand)
  1684. end
  1685. end
  1686. # This constructor takes advantage of a technique for Ruby object
  1687. # construction introduced by Andy Hunt and Dave Thomas (see reference),
  1688. # where optional values are set using commands in a block.
  1689. #
  1690. # Text::Format.new {
  1691. # columns = 72
  1692. # left_margin = 0
  1693. # right_margin = 0
  1694. # first_indent = 4
  1695. # body_indent = 0
  1696. # format_style = Text::Format::LEFT_ALIGN
  1697. # extra_space = false
  1698. # abbreviations = {}
  1699. # tag_paragraph = false
  1700. # tag_text = []
  1701. # nobreak = false
  1702. # nobreak_regex = {}
  1703. # tabstop = 8
  1704. # text = nil
  1705. # }
  1706. #
  1707. # As shown above, +arg+ is optional. If +arg+ is specified and is a
  1708. # +String+, then arg is used as the default value of <tt>#text</tt>.
  1709. # Alternately, an existing Text::Format object can be used or a Hash can
  1710. # be used. With all forms, a block can be specified.
  1711. #
  1712. # *Reference*:: "Object Construction and Blocks"
  1713. # <http://www.pragmaticprogrammer.com/ruby/articles/insteval.html>
  1714. #
  1715. def initialize(arg = nil, &block)
  1716. case arg
  1717. when Text::Format
  1718. __create(arg.text) do
  1719. @columns = arg.columns
  1720. @tabstop = arg.tabstop
  1721. @first_indent = arg.first_indent
  1722. @body_indent = arg.body_indent
  1723. @format_style = arg.format_style
  1724. @left_margin = arg.left_margin
  1725. @right_margin = arg.right_margin
  1726. @extra_space = arg.extra_space
  1727. @tag_paragraph = arg.tag_paragraph
  1728. @tag_text = arg.tag_text
  1729. @abbreviations = arg.abbreviations
  1730. @nobreak = arg.nobreak
  1731. @nobreak_regex = arg.nobreak_regex
  1732. @text = arg.text
  1733. @hard_margins = arg.hard_margins
  1734. @split_words = arg.split_words
  1735. @split_rules = arg.split_rules
  1736. @hyphenator = arg.hyphenator
  1737. end
  1738. instance_eval(&block) unless block.nil?
  1739. when Hash
  1740. __create do
  1741. @columns = arg[:columns] || arg['columns'] || @columns
  1742. @tabstop = arg[:tabstop] || arg['tabstop'] || @tabstop
  1743. @first_indent = arg[:first_indent] || arg['first_indent'] || @first_indent
  1744. @body_indent = arg[:body_indent] || arg['body_indent'] || @body_indent
  1745. @format_style = arg[:format_style] || arg['format_style'] || @format_style
  1746. @left_margin = arg[:left_margin] || arg['left_margin'] || @left_margin
  1747. @right_margin = arg[:right_margin] || arg['right_margin'] || @right_margin
  1748. @extra_space = arg[:extra_space] || arg['extra_space'] || @extra_space
  1749. @text = arg[:text] || arg['text'] || @text
  1750. @tag_paragraph = arg[:tag_paragraph] || arg['tag_paragraph'] || @tag_paragraph
  1751. @tag_text = arg[:tag_text] || arg['tag_text'] || @tag_text
  1752. @abbreviations = arg[:abbreviations] || arg['abbreviations'] || @abbreviations
  1753. @nobreak = arg[:nobreak] || arg['nobreak'] || @nobreak
  1754. @nobreak_regex = arg[:nobreak_regex] || arg['nobreak_regex'] || @nobreak_regex
  1755. @hard_margins = arg[:hard_margins] || arg['hard_margins'] || @hard_margins
  1756. @split_rules = arg[:split_rules] || arg['split_rules'] || @split_rules
  1757. @hyphenator = arg[:hyphenator] || arg['hyphenator'] || @hyphenator
  1758. end
  1759. instance_eval(&block) unless block.nil?
  1760. when String
  1761. __create(arg, &block)
  1762. when NilClass
  1763. __create(&block)
  1764. else
  1765. raise TypeError
  1766. end
  1767. end
  1768. end
  1769. end
  1770. if __FILE__ == $0
  1771. require 'test/unit'
  1772. class TestText__Format < Test::Unit::TestCase #:nodoc:
  1773. attr_accessor :format_o
  1774. GETTYSBURG = <<-'EOS'
  1775. Four score and seven years ago our fathers brought forth on this
  1776. continent a new nation, conceived in liberty and dedicated to the
  1777. proposition that all men are created equal. Now we are engaged in
  1778. a great civil war, testing whether that nation or any nation so
  1779. conceived and so dedicated can long endure. We are met on a great
  1780. battlefield of that war. We have come to dedicate a portion of
  1781. that field as a final resting-place for those who here gave their
  1782. lives that that nation might live. It is altogether fitting and
  1783. proper that we should do this. But in a larger sense, we cannot
  1784. dedicate, we cannot consecrate, we cannot hallow this ground.
  1785. The brave men, living and dead who struggled here have consecrated
  1786. it far above our poor power to add or detract. The world will
  1787. little note nor long remember what we say here, but it can never
  1788. forget what they did here. It is for us the living rather to be
  1789. dedicated here to the unfinished work which they who fought here
  1790. have thus far so nobly advanced. It is rather for us to be here
  1791. dedicated to the great task remaining before us--that from these
  1792. honored dead we take increased devotion to that cause for which
  1793. they gave the last full measure of devotion--that we here highly
  1794. resolve that these dead shall not have died in vain, that this
  1795. nation under God shall have a new birth of freedom, and that
  1796. government of the people, by the people, for the people shall
  1797. not perish from the earth.
  1798. -- Pres. Abraham Lincoln, 19 November 1863
  1799. EOS
  1800. FIVE_COL = "Four \nscore\nand s\neven \nyears\nago o\nur fa\nthers\nbroug\nht fo\nrth o\nn thi\ns con\ntinen\nt a n\new na\ntion,\nconce\nived \nin li\nberty\nand d\nedica\nted t\no the\npropo\nsitio\nn tha\nt all\nmen a\nre cr\neated\nequal\n. Now\nwe ar\ne eng\naged \nin a \ngreat\ncivil\nwar, \ntesti\nng wh\nether\nthat \nnatio\nn or \nany n\nation\nso co\nnceiv\ned an\nd so \ndedic\nated \ncan l\nong e\nndure\n. We \nare m\net on\na gre\nat ba\nttlef\nield \nof th\nat wa\nr. We\nhave \ncome \nto de\ndicat\ne a p\nortio\nn of \nthat \nfield\nas a \nfinal\nresti\nng-pl\nace f\nor th\nose w\nho he\nre ga\nve th\neir l\nives \nthat \nthat \nnatio\nn mig\nht li\nve. I\nt is \naltog\nether\nfitti\nng an\nd pro\nper t\nhat w\ne sho\nuld d\no thi\ns. Bu\nt in \na lar\nger s\nense,\nwe ca\nnnot \ndedic\nate, \nwe ca\nnnot \nconse\ncrate\n, we \ncanno\nt hal\nlow t\nhis g\nround\n. The\nbrave\nmen, \nlivin\ng and\ndead \nwho s\ntrugg\nled h\nere h\nave c\nonsec\nrated\nit fa\nr abo\nve ou\nr poo\nr pow\ner to\nadd o\nr det\nract.\nThe w\norld \nwill \nlittl\ne not\ne nor\nlong \nremem\nber w\nhat w\ne say\nhere,\nbut i\nt can\nnever\nforge\nt wha\nt the\ny did\nhere.\nIt is\nfor u\ns the\nlivin\ng rat\nher t\no be \ndedic\nated \nhere \nto th\ne unf\ninish\ned wo\nrk wh\nich t\nhey w\nho fo\nught \nhere \nhave \nthus \nfar s\no nob\nly ad\nvance\nd. It\nis ra\nther \nfor u\ns to \nbe he\nre de\ndicat\ned to\nthe g\nreat \ntask \nremai\nning \nbefor\ne us-\n-that\nfrom \nthese\nhonor\ned de\nad we\ntake \nincre\nased \ndevot\nion t\no tha\nt cau\nse fo\nr whi\nch th\ney ga\nve th\ne las\nt ful\nl mea\nsure \nof de\nvotio\nn--th\nat we\nhere \nhighl\ny res\nolve \nthat \nthese\ndead \nshall\nnot h\nave d\nied i\nn vai\nn, th\nat th\nis na\ntion \nunder\nGod s\nhall \nhave \na new\nbirth\nof fr\needom\n, and\nthat \ngover\nnment\nof th\ne peo\nple, \nby th\ne peo\nple, \nfor t\nhe pe\nople \nshall\nnot p\nerish\nfrom \nthe e\narth.\n-- Pr\nes. A\nbraha\nm Lin\ncoln,\n19 No\nvembe\nr 186\n3 \n"
  1801. FIVE_CNT = "Four \nscore\nand \nseven\nyears\nago \nour \nfath\\\ners \nbrou\\\nght \nforth\non t\\\nhis \ncont\\\ninent\na new\nnati\\\non, \nconc\\\neived\nin l\\\niber\\\nty a\\\nnd d\\\nedic\\\nated \nto t\\\nhe p\\\nropo\\\nsiti\\\non t\\\nhat \nall \nmen \nare \ncrea\\\nted \nequa\\\nl. N\\\now we\nare \nenga\\\nged \nin a \ngreat\ncivil\nwar, \ntest\\\ning \nwhet\\\nher \nthat \nnati\\\non or\nany \nnati\\\non so\nconc\\\neived\nand \nso d\\\nedic\\\nated \ncan \nlong \nendu\\\nre. \nWe a\\\nre m\\\net on\na gr\\\neat \nbatt\\\nlefi\\\neld \nof t\\\nhat \nwar. \nWe h\\\nave \ncome \nto d\\\nedic\\\nate a\nport\\\nion \nof t\\\nhat \nfield\nas a \nfinal\nrest\\\ning-\\\nplace\nfor \nthose\nwho \nhere \ngave \ntheir\nlives\nthat \nthat \nnati\\\non m\\\night \nlive.\nIt is\nalto\\\ngeth\\\ner f\\\nitti\\\nng a\\\nnd p\\\nroper\nthat \nwe s\\\nhould\ndo t\\\nhis. \nBut \nin a \nlarg\\\ner s\\\nense,\nwe c\\\nannot\ndedi\\\ncate,\nwe c\\\nannot\ncons\\\necra\\\nte, \nwe c\\\nannot\nhall\\\now t\\\nhis \ngrou\\\nnd. \nThe \nbrave\nmen, \nlivi\\\nng a\\\nnd d\\\nead \nwho \nstru\\\nggled\nhere \nhave \ncons\\\necra\\\nted \nit f\\\nar a\\\nbove \nour \npoor \npower\nto a\\\ndd or\ndetr\\\nact. \nThe \nworld\nwill \nlitt\\\nle n\\\note \nnor \nlong \nreme\\\nmber \nwhat \nwe s\\\nay h\\\nere, \nbut \nit c\\\nan n\\\never \nforg\\\net w\\\nhat \nthey \ndid \nhere.\nIt is\nfor \nus t\\\nhe l\\\niving\nrath\\\ner to\nbe d\\\nedic\\\nated \nhere \nto t\\\nhe u\\\nnfin\\\nished\nwork \nwhich\nthey \nwho \nfoug\\\nht h\\\nere \nhave \nthus \nfar \nso n\\\nobly \nadva\\\nnced.\nIt is\nrath\\\ner f\\\nor us\nto be\nhere \ndedi\\\ncated\nto t\\\nhe g\\\nreat \ntask \nrema\\\nining\nbefo\\\nre u\\\ns--t\\\nhat \nfrom \nthese\nhono\\\nred \ndead \nwe t\\\nake \nincr\\\neased\ndevo\\\ntion \nto t\\\nhat \ncause\nfor \nwhich\nthey \ngave \nthe \nlast \nfull \nmeas\\\nure \nof d\\\nevot\\\nion-\\\n-that\nwe h\\\nere \nhigh\\\nly r\\\nesol\\\nve t\\\nhat \nthese\ndead \nshall\nnot \nhave \ndied \nin v\\\nain, \nthat \nthis \nnati\\\non u\\\nnder \nGod \nshall\nhave \na new\nbirth\nof f\\\nreed\\\nom, \nand \nthat \ngove\\\nrnme\\\nnt of\nthe \npeop\\\nle, \nby t\\\nhe p\\\neopl\\\ne, f\\\nor t\\\nhe p\\\neople\nshall\nnot \nperi\\\nsh f\\\nrom \nthe \neart\\\nh. --\nPres.\nAbra\\\nham \nLinc\\\noln, \n19 N\\\novem\\\nber \n1863 \n"
  1802. # Tests both abbreviations and abbreviations=
  1803. def test_abbreviations
  1804. abbr = [" Pres. Abraham Lincoln\n", " Pres. Abraham Lincoln\n"]
  1805. assert_nothing_raised { @format_o = Text::Format.new }
  1806. assert_equal([], @format_o.abbreviations)
  1807. assert_nothing_raised { @format_o.abbreviations = [ 'foo', 'bar' ] }
  1808. assert_equal([ 'foo', 'bar' ], @format_o.abbreviations)
  1809. assert_equal(abbr[0], @format_o.format(abbr[0]))
  1810. assert_nothing_raised { @format_o.extra_space = true }
  1811. assert_equal(abbr[1], @format_o.format(abbr[0]))
  1812. assert_nothing_raised { @format_o.abbreviations = [ "Pres" ] }
  1813. assert_equal([ "Pres" ], @format_o.abbreviations)
  1814. assert_equal(abbr[0], @format_o.format(abbr[0]))
  1815. assert_nothing_raised { @format_o.extra_space = false }
  1816. assert_equal(abbr[0], @format_o.format(abbr[0]))
  1817. end
  1818. # Tests both body_indent and body_indent=
  1819. def test_body_indent
  1820. assert_nothing_raised { @format_o = Text::Format.new }
  1821. assert_equal(0, @format_o.body_indent)
  1822. assert_nothing_raised { @format_o.body_indent = 7 }
  1823. assert_equal(7, @format_o.body_indent)
  1824. assert_nothing_raised { @format_o.body_indent = -3 }
  1825. assert_equal(3, @format_o.body_indent)
  1826. assert_nothing_raised { @format_o.body_indent = "9" }
  1827. assert_equal(9, @format_o.body_indent)
  1828. assert_nothing_raised { @format_o.body_indent = "-2" }
  1829. assert_equal(2, @format_o.body_indent)
  1830. assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[1])
  1831. end
  1832. # Tests both columns and columns=
  1833. def test_columns
  1834. assert_nothing_raised { @format_o = Text::Format.new }
  1835. assert_equal(72, @format_o.columns)
  1836. assert_nothing_raised { @format_o.columns = 7 }
  1837. assert_equal(7, @format_o.columns)
  1838. assert_nothing_raised { @format_o.columns = -3 }
  1839. assert_equal(3, @format_o.columns)
  1840. assert_nothing_raised { @format_o.columns = "9" }
  1841. assert_equal(9, @format_o.columns)
  1842. assert_nothing_raised { @format_o.columns = "-2" }
  1843. assert_equal(2, @format_o.columns)
  1844. assert_nothing_raised { @format_o.columns = 40 }
  1845. assert_equal(40, @format_o.columns)
  1846. assert_match(/this continent$/,
  1847. @format_o.format(GETTYSBURG).split("\n")[1])
  1848. end
  1849. # Tests both extra_space and extra_space=
  1850. def test_extra_space
  1851. assert_nothing_raised { @format_o = Text::Format.new }
  1852. assert(!@format_o.extra_space)
  1853. assert_nothing_raised { @format_o.extra_space = true }
  1854. assert(@format_o.extra_space)
  1855. # The behaviour of extra_space is tested in test_abbreviations. There
  1856. # is no need to reproduce it here.
  1857. end
  1858. # Tests both first_indent and first_indent=
  1859. def test_first_indent
  1860. assert_nothing_raised { @format_o = Text::Format.new }
  1861. assert_equal(4, @format_o.first_indent)
  1862. assert_nothing_raised { @format_o.first_indent = 7 }
  1863. assert_equal(7, @format_o.first_indent)
  1864. assert_nothing_raised { @format_o.first_indent = -3 }
  1865. assert_equal(3, @format_o.first_indent)
  1866. assert_nothing_raised { @format_o.first_indent = "9" }
  1867. assert_equal(9, @format_o.first_indent)
  1868. assert_nothing_raised { @format_o.first_indent = "-2" }
  1869. assert_equal(2, @format_o.first_indent)
  1870. assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[0])
  1871. end
  1872. def test_format_style
  1873. assert_nothing_raised { @format_o = Text::Format.new }
  1874. assert_equal(Text::Format::LEFT_ALIGN, @format_o.format_style)
  1875. assert_match(/^November 1863$/,
  1876. @format_o.format(GETTYSBURG).split("\n")[-1])
  1877. assert_nothing_raised {
  1878. @format_o.format_style = Text::Format::RIGHT_ALIGN
  1879. }
  1880. assert_equal(Text::Format::RIGHT_ALIGN, @format_o.format_style)
  1881. assert_match(/^ +November 1863$/,
  1882. @format_o.format(GETTYSBURG).split("\n")[-1])
  1883. assert_nothing_raised {
  1884. @format_o.format_style = Text::Format::RIGHT_FILL
  1885. }
  1886. assert_equal(Text::Format::RIGHT_FILL, @format_o.format_style)
  1887. assert_match(/^November 1863 +$/,
  1888. @format_o.format(GETTYSBURG).split("\n")[-1])
  1889. assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
  1890. assert_equal(Text::Format::JUSTIFY, @format_o.format_style)
  1891. assert_match(/^of freedom, and that government of the people, by the people, for the$/,
  1892. @format_o.format(GETTYSBURG).split("\n")[-3])
  1893. assert_raises(ArgumentError) { @format_o.format_style = 33 }
  1894. end
  1895. def test_tag_paragraph
  1896. assert_nothing_raised { @format_o = Text::Format.new }
  1897. assert(!@format_o.tag_paragraph)
  1898. assert_nothing_raised { @format_o.tag_paragraph = true }
  1899. assert(@format_o.tag_paragraph)
  1900. assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
  1901. Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
  1902. end
  1903. def test_tag_text
  1904. assert_nothing_raised { @format_o = Text::Format.new }
  1905. assert_equal([], @format_o.tag_text)
  1906. assert_equal(@format_o.format(GETTYSBURG),
  1907. Text::Format.new.format(GETTYSBURG))
  1908. assert_nothing_raised {
  1909. @format_o.tag_paragraph = true
  1910. @format_o.tag_text = ["Gettysburg Address", "---"]
  1911. }
  1912. assert_not_equal(@format_o.format(GETTYSBURG),
  1913. Text::Format.new.format(GETTYSBURG))
  1914. assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
  1915. Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
  1916. assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG,
  1917. GETTYSBURG]),
  1918. Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG,
  1919. GETTYSBURG]))
  1920. end
  1921. def test_justify?
  1922. assert_nothing_raised { @format_o = Text::Format.new }
  1923. assert(!@format_o.justify?)
  1924. assert_nothing_raised {
  1925. @format_o.format_style = Text::Format::RIGHT_ALIGN
  1926. }
  1927. assert(!@format_o.justify?)
  1928. assert_nothing_raised {
  1929. @format_o.format_style = Text::Format::RIGHT_FILL
  1930. }
  1931. assert(!@format_o.justify?)
  1932. assert_nothing_raised {
  1933. @format_o.format_style = Text::Format::JUSTIFY
  1934. }
  1935. assert(@format_o.justify?)
  1936. # The format testing is done in test_format_style
  1937. end
  1938. def test_left_align?
  1939. assert_nothing_raised { @format_o = Text::Format.new }
  1940. assert(@format_o.left_align?)
  1941. assert_nothing_raised {
  1942. @format_o.format_style = Text::Format::RIGHT_ALIGN
  1943. }
  1944. assert(!@format_o.left_align?)
  1945. assert_nothing_raised {
  1946. @format_o.format_style = Text::Format::RIGHT_FILL
  1947. }
  1948. assert(!@format_o.left_align?)
  1949. assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
  1950. assert(!@format_o.left_align?)
  1951. # The format testing is done in test_format_style
  1952. end
  1953. def test_left_margin
  1954. assert_nothing_raised { @format_o = Text::Format.new }
  1955. assert_equal(0, @format_o.left_margin)
  1956. assert_nothing_raised { @format_o.left_margin = -3 }
  1957. assert_equal(3, @format_o.left_margin)
  1958. assert_nothing_raised { @format_o.left_margin = "9" }
  1959. assert_equal(9, @format_o.left_margin)
  1960. assert_nothing_raised { @format_o.left_margin = "-2" }
  1961. assert_equal(2, @format_o.left_margin)
  1962. assert_nothing_raised { @format_o.left_margin = 7 }
  1963. assert_equal(7, @format_o.left_margin)
  1964. assert_nothing_raised {
  1965. ft = @format_o.format(GETTYSBURG).split("\n")
  1966. assert_match(/^ {11}Four score/, ft[0])
  1967. assert_match(/^ {7}November/, ft[-1])
  1968. }
  1969. end
  1970. def test_hard_margins
  1971. assert_nothing_raised { @format_o = Text::Format.new }
  1972. assert(!@format_o.hard_margins)
  1973. assert_nothing_raised {
  1974. @format_o.hard_margins = true
  1975. @format_o.columns = 5
  1976. @format_o.first_indent = 0
  1977. @format_o.format_style = Text::Format::RIGHT_FILL
  1978. }
  1979. assert(@format_o.hard_margins)
  1980. assert_equal(FIVE_COL, @format_o.format(GETTYSBURG))
  1981. assert_nothing_raised {
  1982. @format_o.split_rules |= Text::Format::SPLIT_CONTINUATION
  1983. assert_equal(Text::Format::SPLIT_CONTINUATION_FIXED,
  1984. @format_o.split_rules)
  1985. }
  1986. assert_equal(FIVE_CNT, @format_o.format(GETTYSBURG))
  1987. end
  1988. # Tests both nobreak and nobreak_regex, since one is only useful
  1989. # with the other.
  1990. def test_nobreak
  1991. assert_nothing_raised { @format_o = Text::Format.new }
  1992. assert(!@format_o.nobreak)
  1993. assert(@format_o.nobreak_regex.empty?)
  1994. assert_nothing_raised {
  1995. @format_o.nobreak = true
  1996. @format_o.nobreak_regex = { '^this$' => '^continent$' }
  1997. @format_o.columns = 77
  1998. }
  1999. assert(@format_o.nobreak)
  2000. assert_equal({ '^this$' => '^continent$' }, @format_o.nobreak_regex)
  2001. assert_match(/^this continent/,
  2002. @format_o.format(GETTYSBURG).split("\n")[1])
  2003. end
  2004. def test_right_align?
  2005. assert_nothing_raised { @format_o = Text::Format.new }
  2006. assert(!@format_o.right_align?)
  2007. assert_nothing_raised {
  2008. @format_o.format_style = Text::Format::RIGHT_ALIGN
  2009. }
  2010. assert(@format_o.right_align?)
  2011. assert_nothing_raised {
  2012. @format_o.format_style = Text::Format::RIGHT_FILL
  2013. }
  2014. assert(!@format_o.right_align?)
  2015. assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
  2016. assert(!@format_o.right_align?)
  2017. # The format testing is done in test_format_style
  2018. end
  2019. def test_right_fill?
  2020. assert_nothing_raised { @format_o = Text::Format.new }
  2021. assert(!@format_o.right_fill?)
  2022. assert_nothing_raised {
  2023. @format_o.format_style = Text::Format::RIGHT_ALIGN
  2024. }
  2025. assert(!@format_o.right_fill?)
  2026. assert_nothing_raised {
  2027. @format_o.format_style = Text::Format::RIGHT_FILL
  2028. }
  2029. assert(@format_o.right_fill?)
  2030. assert_nothing_raised {
  2031. @format_o.format_style = Text::Format::JUSTIFY
  2032. }
  2033. assert(!@format_o.right_fill?)
  2034. # The format testing is done in test_format_style
  2035. end
  2036. def test_right_margin
  2037. assert_nothing_raised { @format_o = Text::Format.new }
  2038. assert_equal(0, @format_o.right_margin)
  2039. assert_nothing_raised { @format_o.right_margin = -3 }
  2040. assert_equal(3, @format_o.right_margin)
  2041. assert_nothing_raised { @format_o.right_margin = "9" }
  2042. assert_equal(9, @format_o.right_margin)
  2043. assert_nothing_raised { @format_o.right_margin = "-2" }
  2044. assert_equal(2, @format_o.right_margin)
  2045. assert_nothing_raised { @format_o.right_margin = 7 }
  2046. assert_equal(7, @format_o.right_margin)
  2047. assert_nothing_raised {
  2048. ft = @format_o.format(GETTYSBURG).split("\n")
  2049. assert_match(/^ {4}Four score.*forth on$/, ft[0])
  2050. assert_match(/^November/, ft[-1])
  2051. }
  2052. end
  2053. def test_tabstop
  2054. assert_nothing_raised { @format_o = Text::Format.new }
  2055. assert_equal(8, @format_o.tabstop)
  2056. assert_nothing_raised { @format_o.tabstop = 7 }
  2057. assert_equal(7, @format_o.tabstop)
  2058. assert_nothing_raised { @format_o.tabstop = -3 }
  2059. assert_equal(3, @format_o.tabstop)
  2060. assert_nothing_raised { @format_o.tabstop = "9" }
  2061. assert_equal(9, @format_o.tabstop)
  2062. assert_nothing_raised { @format_o.tabstop = "-2" }
  2063. assert_equal(2, @format_o.tabstop)
  2064. end
  2065. def test_text
  2066. assert_nothing_raised { @format_o = Text::Format.new }
  2067. assert_equal([], @format_o.text)
  2068. assert_nothing_raised { @format_o.text = "Test Text" }
  2069. assert_equal("Test Text", @format_o.text)
  2070. assert_nothing_raised { @format_o.text = ["Line 1", "Line 2"] }
  2071. assert_equal(["Line 1", "Line 2"], @format_o.text)
  2072. end
  2073. def test_s_new
  2074. # new(NilClass) { block }
  2075. assert_nothing_raised do
  2076. @format_o = Text::Format.new {
  2077. self.text = "Test 1, 2, 3"
  2078. }
  2079. end
  2080. assert_equal("Test 1, 2, 3", @format_o.text)
  2081. # new(Hash Symbols)
  2082. assert_nothing_raised { @format_o = Text::Format.new(:columns => 72) }
  2083. assert_equal(72, @format_o.columns)
  2084. # new(Hash String)
  2085. assert_nothing_raised { @format_o = Text::Format.new('columns' => 72) }
  2086. assert_equal(72, @format_o.columns)
  2087. # new(Hash) { block }
  2088. assert_nothing_raised do
  2089. @format_o = Text::Format.new('columns' => 80) {
  2090. self.text = "Test 4, 5, 6"
  2091. }
  2092. end
  2093. assert_equal("Test 4, 5, 6", @format_o.text)
  2094. assert_equal(80, @format_o.columns)
  2095. # new(Text::Format)
  2096. assert_nothing_raised do
  2097. fo = Text::Format.new(@format_o)
  2098. assert(fo == @format_o)
  2099. end
  2100. # new(Text::Format) { block }
  2101. assert_nothing_raised do
  2102. fo = Text::Format.new(@format_o) { self.columns = 79 }
  2103. assert(fo != @format_o)
  2104. end
  2105. # new(String)
  2106. assert_nothing_raised { @format_o = Text::Format.new("Test A, B, C") }
  2107. assert_equal("Test A, B, C", @format_o.text)
  2108. # new(String) { block }
  2109. assert_nothing_raised do
  2110. @format_o = Text::Format.new("Test X, Y, Z") { self.columns = -5 }
  2111. end
  2112. assert_equal("Test X, Y, Z", @format_o.text)
  2113. assert_equal(5, @format_o.columns)
  2114. end
  2115. def test_center
  2116. assert_nothing_raised { @format_o = Text::Format.new }
  2117. assert_nothing_raised do
  2118. ct = @format_o.center(GETTYSBURG.split("\n")).split("\n")
  2119. assert_match(/^ Four score and seven years ago our fathers brought forth on this/, ct[0])
  2120. assert_match(/^ not perish from the earth./, ct[-3])
  2121. end
  2122. end
  2123. def test_expand
  2124. assert_nothing_raised { @format_o = Text::Format.new }
  2125. assert_equal(" ", @format_o.expand("\t "))
  2126. assert_nothing_raised { @format_o.tabstop = 4 }
  2127. assert_equal(" ", @format_o.expand("\t "))
  2128. end
  2129. def test_unexpand
  2130. assert_nothing_raised { @format_o = Text::Format.new }
  2131. assert_equal("\t ", @format_o.unexpand(" "))
  2132. assert_nothing_raised { @format_o.tabstop = 4 }
  2133. assert_equal("\t ", @format_o.unexpand(" "))
  2134. end
  2135. def test_space_only
  2136. assert_equal("", Text::Format.new.format(" "))
  2137. assert_equal("", Text::Format.new.format("\n"))
  2138. assert_equal("", Text::Format.new.format(" "))
  2139. assert_equal("", Text::Format.new.format(" \n"))
  2140. assert_equal("", Text::Format.new.paragraphs("\n"))
  2141. assert_equal("", Text::Format.new.paragraphs(" "))
  2142. assert_equal("", Text::Format.new.paragraphs(" "))
  2143. assert_equal("", Text::Format.new.paragraphs(" \n"))
  2144. assert_equal("", Text::Format.new.paragraphs(["\n"]))
  2145. assert_equal("", Text::Format.new.paragraphs([" "]))
  2146. assert_equal("", Text::Format.new.paragraphs([" "]))
  2147. assert_equal("", Text::Format.new.paragraphs([" \n"]))
  2148. end
  2149. def test_splendiferous
  2150. h = nil
  2151. test = "This is a splendiferous test"
  2152. assert_nothing_raised { @format_o = Text::Format.new(:columns => 6, :left_margin => 0, :indent => 0, :first_indent => 0) }
  2153. assert_match(/^splendiferous$/, @format_o.format(test))
  2154. assert_nothing_raised { @format_o.hard_margins = true }
  2155. assert_match(/^lendif$/, @format_o.format(test))
  2156. assert_nothing_raised { h = Object.new }
  2157. assert_nothing_raised do
  2158. @format_o.split_rules = Text::Format::SPLIT_HYPHENATION
  2159. class << h #:nodoc:
  2160. def hyphenate_to(word, size)
  2161. return ["", word] if size < 2
  2162. [word[0 ... size], word[size .. -1]]
  2163. end
  2164. end
  2165. @format_o.hyphenator = h
  2166. end
  2167. assert_match(/^iferou$/, @format_o.format(test))
  2168. assert_nothing_raised { h = Object.new }
  2169. assert_nothing_raised do
  2170. class << h #:nodoc:
  2171. def hyphenate_to(word, size, formatter)
  2172. return ["", word] if word.size < formatter.columns
  2173. [word[0 ... size], word[size .. -1]]
  2174. end
  2175. end
  2176. @format_o.hyphenator = h
  2177. end
  2178. assert_match(/^ferous$/, @format_o.format(test))
  2179. end
  2180. end
  2181. end
  2182. #
  2183. # address.rb
  2184. #
  2185. #--
  2186. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2187. #
  2188. # Permission is hereby granted, free of charge, to any person obtaining
  2189. # a copy of this software and associated documentation files (the
  2190. # "Software"), to deal in the Software without restriction, including
  2191. # without limitation the rights to use, copy, modify, merge, publish,
  2192. # distribute, sublicense, and/or sell copies of the Software, and to
  2193. # permit persons to whom the Software is furnished to do so, subject to
  2194. # the following conditions:
  2195. #
  2196. # The above copyright notice and this permission notice shall be
  2197. # included in all copies or substantial portions of the Software.
  2198. #
  2199. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2200. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2201. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2202. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2203. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2204. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2205. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2206. #
  2207. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2208. # with permission of Minero Aoki.
  2209. #++
  2210. require 'tmail/encode'
  2211. require 'tmail/parser'
  2212. module TMail
  2213. class Address
  2214. include TextUtils
  2215. def Address.parse( str )
  2216. Parser.parse :ADDRESS, str
  2217. end
  2218. def address_group?
  2219. false
  2220. end
  2221. def initialize( local, domain )
  2222. if domain
  2223. domain.each do |s|
  2224. raise SyntaxError, 'empty word in domain' if s.empty?
  2225. end
  2226. end
  2227. @local = local
  2228. @domain = domain
  2229. @name = nil
  2230. @routes = []
  2231. end
  2232. attr_reader :name
  2233. def name=( str )
  2234. @name = str
  2235. @name = nil if str and str.empty?
  2236. end
  2237. alias phrase name
  2238. alias phrase= name=
  2239. attr_reader :routes
  2240. def inspect
  2241. "#<#{self.class} #{address()}>"
  2242. end
  2243. def local
  2244. return nil unless @local
  2245. return '""' if @local.size == 1 and @local[0].empty?
  2246. @local.map {|i| quote_atom(i) }.join('.')
  2247. end
  2248. def domain
  2249. return nil unless @domain
  2250. join_domain(@domain)
  2251. end
  2252. def spec
  2253. s = self.local
  2254. d = self.domain
  2255. if s and d
  2256. s + '@' + d
  2257. else
  2258. s
  2259. end
  2260. end
  2261. alias address spec
  2262. def ==( other )
  2263. other.respond_to? :spec and self.spec == other.spec
  2264. end
  2265. alias eql? ==
  2266. def hash
  2267. @local.hash ^ @domain.hash
  2268. end
  2269. def dup
  2270. obj = self.class.new(@local.dup, @domain.dup)
  2271. obj.name = @name.dup if @name
  2272. obj.routes.replace @routes
  2273. obj
  2274. end
  2275. include StrategyInterface
  2276. def accept( strategy, dummy1 = nil, dummy2 = nil )
  2277. unless @local
  2278. strategy.meta '<>' # empty return-path
  2279. return
  2280. end
  2281. spec_p = (not @name and @routes.empty?)
  2282. if @name
  2283. strategy.phrase @name
  2284. strategy.space
  2285. end
  2286. tmp = spec_p ? '' : '<'
  2287. unless @routes.empty?
  2288. tmp << @routes.map {|i| '@' + i }.join(',') << ':'
  2289. end
  2290. tmp << self.spec
  2291. tmp << '>' unless spec_p
  2292. strategy.meta tmp
  2293. strategy.lwsp ''
  2294. end
  2295. end
  2296. class AddressGroup
  2297. include Enumerable
  2298. def address_group?
  2299. true
  2300. end
  2301. def initialize( name, addrs )
  2302. @name = name
  2303. @addresses = addrs
  2304. end
  2305. attr_reader :name
  2306. def ==( other )
  2307. other.respond_to? :to_a and @addresses == other.to_a
  2308. end
  2309. alias eql? ==
  2310. def hash
  2311. map {|i| i.hash }.hash
  2312. end
  2313. def []( idx )
  2314. @addresses[idx]
  2315. end
  2316. def size
  2317. @addresses.size
  2318. end
  2319. def empty?
  2320. @addresses.empty?
  2321. end
  2322. def each( &block )
  2323. @addresses.each(&block)
  2324. end
  2325. def to_a
  2326. @addresses.dup
  2327. end
  2328. alias to_ary to_a
  2329. def include?( a )
  2330. @addresses.include? a
  2331. end
  2332. def flatten
  2333. set = []
  2334. @addresses.each do |a|
  2335. if a.respond_to? :flatten
  2336. set.concat a.flatten
  2337. else
  2338. set.push a
  2339. end
  2340. end
  2341. set
  2342. end
  2343. def each_address( &block )
  2344. flatten.each(&block)
  2345. end
  2346. def add( a )
  2347. @addresses.push a
  2348. end
  2349. alias push add
  2350. def delete( a )
  2351. @addresses.delete a
  2352. end
  2353. include StrategyInterface
  2354. def accept( strategy, dummy1 = nil, dummy2 = nil )
  2355. strategy.phrase @name
  2356. strategy.meta ':'
  2357. strategy.space
  2358. first = true
  2359. each do |mbox|
  2360. if first
  2361. first = false
  2362. else
  2363. strategy.meta ','
  2364. end
  2365. strategy.space
  2366. mbox.accept strategy
  2367. end
  2368. strategy.meta ';'
  2369. strategy.lwsp ''
  2370. end
  2371. end
  2372. end # module TMail
  2373. require 'stringio'
  2374. module TMail
  2375. class Attachment < StringIO
  2376. attr_accessor :original_filename, :content_type
  2377. end
  2378. class Mail
  2379. def has_attachments?
  2380. multipart? && parts.any? { |part| attachment?(part) }
  2381. end
  2382. def attachment?(part)
  2383. (part['content-disposition'] && part['content-disposition'].disposition == "attachment") ||
  2384. part.header['content-type'].main_type != "text"
  2385. end
  2386. def attachments
  2387. if multipart?
  2388. parts.collect { |part|
  2389. if attachment?(part)
  2390. content = part.body # unquoted automatically by TMail#body
  2391. file_name = (part['content-location'] &&
  2392. part['content-location'].body) ||
  2393. part.sub_header("content-type", "name") ||
  2394. part.sub_header("content-disposition", "filename")
  2395. next if file_name.blank? || content.blank?
  2396. attachment = Attachment.new(content)
  2397. attachment.original_filename = file_name.strip
  2398. attachment.content_type = part.content_type
  2399. attachment
  2400. end
  2401. }.compact
  2402. end
  2403. end
  2404. end
  2405. end
  2406. #
  2407. # base64.rb
  2408. #
  2409. #--
  2410. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2411. #
  2412. # Permission is hereby granted, free of charge, to any person obtaining
  2413. # a copy of this software and associated documentation files (the
  2414. # "Software"), to deal in the Software without restriction, including
  2415. # without limitation the rights to use, copy, modify, merge, publish,
  2416. # distribute, sublicense, and/or sell copies of the Software, and to
  2417. # permit persons to whom the Software is furnished to do so, subject to
  2418. # the following conditions:
  2419. #
  2420. # The above copyright notice and this permission notice shall be
  2421. # included in all copies or substantial portions of the Software.
  2422. #
  2423. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2424. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2425. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2426. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2427. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2428. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2429. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2430. #
  2431. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2432. # with permission of Minero Aoki.
  2433. #++
  2434. module TMail
  2435. module Base64
  2436. module_function
  2437. def rb_folding_encode( str, eol = "\n", limit = 60 )
  2438. [str].pack('m')
  2439. end
  2440. def rb_encode( str )
  2441. [str].pack('m').tr( "\r\n", '' )
  2442. end
  2443. def rb_decode( str, strict = false )
  2444. str.unpack('m')
  2445. end
  2446. begin
  2447. require 'tmail/base64.so'
  2448. alias folding_encode c_folding_encode
  2449. alias encode c_encode
  2450. alias decode c_decode
  2451. class << self
  2452. alias folding_encode c_folding_encode
  2453. alias encode c_encode
  2454. alias decode c_decode
  2455. end
  2456. rescue LoadError
  2457. alias folding_encode rb_folding_encode
  2458. alias encode rb_encode
  2459. alias decode rb_decode
  2460. class << self
  2461. alias folding_encode rb_folding_encode
  2462. alias encode rb_encode
  2463. alias decode rb_decode
  2464. end
  2465. end
  2466. end
  2467. end
  2468. #
  2469. # config.rb
  2470. #
  2471. #--
  2472. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2473. #
  2474. # Permission is hereby granted, free of charge, to any person obtaining
  2475. # a copy of this software and associated documentation files (the
  2476. # "Software"), to deal in the Software without restriction, including
  2477. # without limitation the rights to use, copy, modify, merge, publish,
  2478. # distribute, sublicense, and/or sell copies of the Software, and to
  2479. # permit persons to whom the Software is furnished to do so, subject to
  2480. # the following conditions:
  2481. #
  2482. # The above copyright notice and this permission notice shall be
  2483. # included in all copies or substantial portions of the Software.
  2484. #
  2485. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2486. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2487. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2488. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2489. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2490. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2491. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2492. #
  2493. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2494. # with permission of Minero Aoki.
  2495. #++
  2496. module TMail
  2497. class Config
  2498. def initialize( strict )
  2499. @strict_parse = strict
  2500. @strict_base64decode = strict
  2501. end
  2502. def strict_parse?
  2503. @strict_parse
  2504. end
  2505. attr_writer :strict_parse
  2506. def strict_base64decode?
  2507. @strict_base64decode
  2508. end
  2509. attr_writer :strict_base64decode
  2510. def new_body_port( mail )
  2511. StringPort.new
  2512. end
  2513. alias new_preamble_port new_body_port
  2514. alias new_part_port new_body_port
  2515. end
  2516. DEFAULT_CONFIG = Config.new(false)
  2517. DEFAULT_STRICT_CONFIG = Config.new(true)
  2518. def Config.to_config( arg )
  2519. return DEFAULT_STRICT_CONFIG if arg == true
  2520. return DEFAULT_CONFIG if arg == false
  2521. arg or DEFAULT_CONFIG
  2522. end
  2523. end
  2524. #
  2525. # encode.rb
  2526. #
  2527. #--
  2528. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2529. #
  2530. # Permission is hereby granted, free of charge, to any person obtaining
  2531. # a copy of this software and associated documentation files (the
  2532. # "Software"), to deal in the Software without restriction, including
  2533. # without limitation the rights to use, copy, modify, merge, publish,
  2534. # distribute, sublicense, and/or sell copies of the Software, and to
  2535. # permit persons to whom the Software is furnished to do so, subject to
  2536. # the following conditions:
  2537. #
  2538. # The above copyright notice and this permission notice shall be
  2539. # included in all copies or substantial portions of the Software.
  2540. #
  2541. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2542. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2543. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2544. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2545. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2546. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2547. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2548. #
  2549. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2550. # with permission of Minero Aoki.
  2551. #++
  2552. require 'nkf'
  2553. require 'tmail/base64.rb'
  2554. require 'tmail/stringio'
  2555. require 'tmail/utils'
  2556. module TMail
  2557. module StrategyInterface
  2558. def create_dest( obj )
  2559. case obj
  2560. when nil
  2561. StringOutput.new
  2562. when String
  2563. StringOutput.new(obj)
  2564. when IO, StringOutput
  2565. obj
  2566. else
  2567. raise TypeError, 'cannot handle this type of object for dest'
  2568. end
  2569. end
  2570. module_function :create_dest
  2571. def encoded( eol = "\r\n", charset = 'j', dest = nil )
  2572. accept_strategy Encoder, eol, charset, dest
  2573. end
  2574. def decoded( eol = "\n", charset = 'e', dest = nil )
  2575. accept_strategy Decoder, eol, charset, dest
  2576. end
  2577. alias to_s decoded
  2578. def accept_strategy( klass, eol, charset, dest = nil )
  2579. dest ||= ''
  2580. accept klass.new(create_dest(dest), charset, eol)
  2581. dest
  2582. end
  2583. end
  2584. ###
  2585. ### MIME B encoding decoder
  2586. ###
  2587. class Decoder
  2588. include TextUtils
  2589. encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
  2590. ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
  2591. OUTPUT_ENCODING = {
  2592. 'EUC' => 'e',
  2593. 'SJIS' => 's',
  2594. }
  2595. def self.decode( str, encoding = nil )
  2596. encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
  2597. opt = '-m' + encoding
  2598. str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
  2599. end
  2600. def initialize( dest, encoding = nil, eol = "\n" )
  2601. @f = StrategyInterface.create_dest(dest)
  2602. @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
  2603. @eol = eol
  2604. end
  2605. def decode( str )
  2606. self.class.decode(str, @encoding)
  2607. end
  2608. private :decode
  2609. def terminate
  2610. end
  2611. def header_line( str )
  2612. @f << decode(str)
  2613. end
  2614. def header_name( nm )
  2615. @f << nm << ': '
  2616. end
  2617. def header_body( str )
  2618. @f << decode(str)
  2619. end
  2620. def space
  2621. @f << ' '
  2622. end
  2623. alias spc space
  2624. def lwsp( str )
  2625. @f << str
  2626. end
  2627. def meta( str )
  2628. @f << str
  2629. end
  2630. def text( str )
  2631. @f << decode(str)
  2632. end
  2633. def phrase( str )
  2634. @f << quote_phrase(decode(str))
  2635. end
  2636. def kv_pair( k, v )
  2637. @f << k << '=' << v
  2638. end
  2639. def puts( str = nil )
  2640. @f << str if str
  2641. @f << @eol
  2642. end
  2643. def write( str )
  2644. @f << str
  2645. end
  2646. end
  2647. ###
  2648. ### MIME B-encoding encoder
  2649. ###
  2650. #
  2651. # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
  2652. #
  2653. class Encoder
  2654. include TextUtils
  2655. BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
  2656. def Encoder.encode( str )
  2657. e = new()
  2658. e.header_body str
  2659. e.terminate
  2660. e.dest.string
  2661. end
  2662. SPACER = "\t"
  2663. MAX_LINE_LEN = 70
  2664. OPTIONS = {
  2665. 'EUC' => '-Ej -m0',
  2666. 'SJIS' => '-Sj -m0',
  2667. 'UTF8' => nil, # FIXME
  2668. 'NONE' => nil
  2669. }
  2670. def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
  2671. @f = StrategyInterface.create_dest(dest)
  2672. @opt = OPTIONS[$KCODE]
  2673. @eol = eol
  2674. reset
  2675. end
  2676. def normalize_encoding( str )
  2677. if @opt
  2678. then NKF.nkf(@opt, str)
  2679. else str
  2680. end
  2681. end
  2682. def reset
  2683. @text = ''
  2684. @lwsp = ''
  2685. @curlen = 0
  2686. end
  2687. def terminate
  2688. add_lwsp ''
  2689. reset
  2690. end
  2691. def dest
  2692. @f
  2693. end
  2694. def puts( str = nil )
  2695. @f << str if str
  2696. @f << @eol
  2697. end
  2698. def write( str )
  2699. @f << str
  2700. end
  2701. #
  2702. # add
  2703. #
  2704. def header_line( line )
  2705. scanadd line
  2706. end
  2707. def header_name( name )
  2708. add_text name.split(/-/).map {|i| i.capitalize }.join('-')
  2709. add_text ':'
  2710. add_lwsp ' '
  2711. end
  2712. def header_body( str )
  2713. scanadd normalize_encoding(str)
  2714. end
  2715. def space
  2716. add_lwsp ' '
  2717. end
  2718. alias spc space
  2719. def lwsp( str )
  2720. add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
  2721. end
  2722. def meta( str )
  2723. add_text str
  2724. end
  2725. def text( str )
  2726. scanadd normalize_encoding(str)
  2727. end
  2728. def phrase( str )
  2729. str = normalize_encoding(str)
  2730. if CONTROL_CHAR === str
  2731. scanadd str
  2732. else
  2733. add_text quote_phrase(str)
  2734. end
  2735. end
  2736. # FIXME: implement line folding
  2737. #
  2738. def kv_pair( k, v )
  2739. return if v.nil?
  2740. v = normalize_encoding(v)
  2741. if token_safe?(v)
  2742. add_text k + '=' + v
  2743. elsif not CONTROL_CHAR === v
  2744. add_text k + '=' + quote_token(v)
  2745. else
  2746. # apply RFC2231 encoding
  2747. kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
  2748. add_text kv
  2749. end
  2750. end
  2751. def encode_value( str )
  2752. str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
  2753. end
  2754. private
  2755. def scanadd( str, force = false )
  2756. types = ''
  2757. strs = []
  2758. until str.empty?
  2759. if m = /\A[^\e\t\r\n ]+/.match(str)
  2760. types << (force ? 'j' : 'a')
  2761. strs.push m[0]
  2762. elsif m = /\A[\t\r\n ]+/.match(str)
  2763. types << 's'
  2764. strs.push m[0]
  2765. elsif m = /\A\e../.match(str)
  2766. esc = m[0]
  2767. str = m.post_match
  2768. if esc != "\e(B" and m = /\A[^\e]+/.match(str)
  2769. types << 'j'
  2770. strs.push m[0]
  2771. end
  2772. else
  2773. raise 'TMail FATAL: encoder scan fail'
  2774. end
  2775. (str = m.post_match) unless m.nil?
  2776. end
  2777. do_encode types, strs
  2778. end
  2779. def do_encode( types, strs )
  2780. #
  2781. # result : (A|E)(S(A|E))*
  2782. # E : W(SW)*
  2783. # W : (J|A)+ but must contain J # (J|A)*J(J|A)*
  2784. # A : <<A character string not to be encoded>>
  2785. # J : <<A character string to be encoded>>
  2786. # S : <<LWSP>>
  2787. #
  2788. # An encoding unit is `E'.
  2789. # Input (parameter `types') is (J|A)(J|A|S)*(J|A)
  2790. #
  2791. if BENCODE_DEBUG
  2792. puts
  2793. puts '-- do_encode ------------'
  2794. puts types.split(//).join(' ')
  2795. p strs
  2796. end
  2797. e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
  2798. while m = e.match(types)
  2799. pre = m.pre_match
  2800. concat_A_S pre, strs[0, pre.size] unless pre.empty?
  2801. concat_E m[0], strs[m.begin(0) ... m.end(0)]
  2802. types = m.post_match
  2803. strs.slice! 0, m.end(0)
  2804. end
  2805. concat_A_S types, strs
  2806. end
  2807. def concat_A_S( types, strs )
  2808. i = 0
  2809. types.each_byte do |t|
  2810. case t
  2811. when ?a then add_text strs[i]
  2812. when ?s then add_lwsp strs[i]
  2813. else
  2814. raise "TMail FATAL: unknown flag: #{t.chr}"
  2815. end
  2816. i += 1
  2817. end
  2818. end
  2819. METHOD_ID = {
  2820. ?j => :extract_J,
  2821. ?e => :extract_E,
  2822. ?a => :extract_A,
  2823. ?s => :extract_S
  2824. }
  2825. def concat_E( types, strs )
  2826. if BENCODE_DEBUG
  2827. puts '---- concat_E'
  2828. puts "types=#{types.split(//).join(' ')}"
  2829. puts "strs =#{strs.inspect}"
  2830. end
  2831. flush() unless @text.empty?
  2832. chunk = ''
  2833. strs.each_with_index do |s,i|
  2834. mid = METHOD_ID[types[i]]
  2835. until s.empty?
  2836. unless c = __send__(mid, chunk.size, s)
  2837. add_with_encode chunk unless chunk.empty?
  2838. flush
  2839. chunk = ''
  2840. fold
  2841. c = __send__(mid, 0, s)
  2842. raise 'TMail FATAL: extract fail' unless c
  2843. end
  2844. chunk << c
  2845. end
  2846. end
  2847. add_with_encode chunk unless chunk.empty?
  2848. end
  2849. def extract_J( chunksize, str )
  2850. size = max_bytes(chunksize, str.size) - 6
  2851. size = (size % 2 == 0) ? (size) : (size - 1)
  2852. return nil if size <= 0
  2853. "\e$B#{str.slice!(0, size)}\e(B"
  2854. end
  2855. def extract_A( chunksize, str )
  2856. size = max_bytes(chunksize, str.size)
  2857. return nil if size <= 0
  2858. str.slice!(0, size)
  2859. end
  2860. alias extract_S extract_A
  2861. def max_bytes( chunksize, ssize )
  2862. (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
  2863. end
  2864. #
  2865. # free length buffer
  2866. #
  2867. def add_text( str )
  2868. @text << str
  2869. # puts '---- text -------------------------------------'
  2870. # puts "+ #{str.inspect}"
  2871. # puts "txt >>>#{@text.inspect}<<<"
  2872. end
  2873. def add_with_encode( str )
  2874. @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
  2875. end
  2876. def add_lwsp( lwsp )
  2877. # puts '---- lwsp -------------------------------------'
  2878. # puts "+ #{lwsp.inspect}"
  2879. fold if restsize() <= 0
  2880. flush
  2881. @lwsp = lwsp
  2882. end
  2883. def flush
  2884. # puts '---- flush ----'
  2885. # puts "spc >>>#{@lwsp.inspect}<<<"
  2886. # puts "txt >>>#{@text.inspect}<<<"
  2887. @f << @lwsp << @text
  2888. @curlen += (@lwsp.size + @text.size)
  2889. @text = ''
  2890. @lwsp = ''
  2891. end
  2892. def fold
  2893. # puts '---- fold ----'
  2894. @f << @eol
  2895. @curlen = 0
  2896. @lwsp = SPACER
  2897. end
  2898. def restsize
  2899. MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
  2900. end
  2901. end
  2902. end # module TMail
  2903. #
  2904. # facade.rb
  2905. #
  2906. #--
  2907. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2908. #
  2909. # Permission is hereby granted, free of charge, to any person obtaining
  2910. # a copy of this software and associated documentation files (the
  2911. # "Software"), to deal in the Software without restriction, including
  2912. # without limitation the rights to use, copy, modify, merge, publish,
  2913. # distribute, sublicense, and/or sell copies of the Software, and to
  2914. # permit persons to whom the Software is furnished to do so, subject to
  2915. # the following conditions:
  2916. #
  2917. # The above copyright notice and this permission notice shall be
  2918. # included in all copies or substantial portions of the Software.
  2919. #
  2920. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2921. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2922. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2923. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2924. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2925. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2926. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2927. #
  2928. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2929. # with permission of Minero Aoki.
  2930. #++
  2931. require 'tmail/utils'
  2932. module TMail
  2933. class Mail
  2934. def header_string( name, default = nil )
  2935. h = @header[name.downcase] or return default
  2936. h.to_s
  2937. end
  2938. ###
  2939. ### attributes
  2940. ###
  2941. include TextUtils
  2942. def set_string_array_attr( key, strs )
  2943. strs.flatten!
  2944. if strs.empty?
  2945. @header.delete key.downcase
  2946. else
  2947. store key, strs.join(', ')
  2948. end
  2949. strs
  2950. end
  2951. private :set_string_array_attr
  2952. def set_string_attr( key, str )
  2953. if str
  2954. store key, str
  2955. else
  2956. @header.delete key.downcase
  2957. end
  2958. str
  2959. end
  2960. private :set_string_attr
  2961. def set_addrfield( name, arg )
  2962. if arg
  2963. h = HeaderField.internal_new(name, @config)
  2964. h.addrs.replace [arg].flatten
  2965. @header[name] = h
  2966. else
  2967. @header.delete name
  2968. end
  2969. arg
  2970. end
  2971. private :set_addrfield
  2972. def addrs2specs( addrs )
  2973. return nil unless addrs
  2974. list = addrs.map {|addr|
  2975. if addr.address_group?
  2976. then addr.map {|a| a.spec }
  2977. else addr.spec
  2978. end
  2979. }.flatten
  2980. return nil if list.empty?
  2981. list
  2982. end
  2983. private :addrs2specs
  2984. #
  2985. # date time
  2986. #
  2987. def date( default = nil )
  2988. if h = @header['date']
  2989. h.date
  2990. else
  2991. default
  2992. end
  2993. end
  2994. def date=( time )
  2995. if time
  2996. store 'Date', time2str(time)
  2997. else
  2998. @header.delete 'date'
  2999. end
  3000. time
  3001. end
  3002. def strftime( fmt, default = nil )
  3003. if t = date
  3004. t.strftime(fmt)
  3005. else
  3006. default
  3007. end
  3008. end
  3009. #
  3010. # destination
  3011. #
  3012. def to_addrs( default = nil )
  3013. if h = @header['to']
  3014. h.addrs
  3015. else
  3016. default
  3017. end
  3018. end
  3019. def cc_addrs( default = nil )
  3020. if h = @header['cc']
  3021. h.addrs
  3022. else
  3023. default
  3024. end
  3025. end
  3026. def bcc_addrs( default = nil )
  3027. if h = @header['bcc']
  3028. h.addrs
  3029. else
  3030. default
  3031. end
  3032. end
  3033. def to_addrs=( arg )
  3034. set_addrfield 'to', arg
  3035. end
  3036. def cc_addrs=( arg )
  3037. set_addrfield 'cc', arg
  3038. end
  3039. def bcc_addrs=( arg )
  3040. set_addrfield 'bcc', arg
  3041. end
  3042. def to( default = nil )
  3043. addrs2specs(to_addrs(nil)) || default
  3044. end
  3045. def cc( default = nil )
  3046. addrs2specs(cc_addrs(nil)) || default
  3047. end
  3048. def bcc( default = nil )
  3049. addrs2specs(bcc_addrs(nil)) || default
  3050. end
  3051. def to=( *strs )
  3052. set_string_array_attr 'To', strs
  3053. end
  3054. def cc=( *strs )
  3055. set_string_array_attr 'Cc', strs
  3056. end
  3057. def bcc=( *strs )
  3058. set_string_array_attr 'Bcc', strs
  3059. end
  3060. #
  3061. # originator
  3062. #
  3063. def from_addrs( default = nil )
  3064. if h = @header['from']
  3065. h.addrs
  3066. else
  3067. default
  3068. end
  3069. end
  3070. def from_addrs=( arg )
  3071. set_addrfield 'from', arg
  3072. end
  3073. def from( default = nil )
  3074. addrs2specs(from_addrs(nil)) || default
  3075. end
  3076. def from=( *strs )
  3077. set_string_array_attr 'From', strs
  3078. end
  3079. def friendly_from( default = nil )
  3080. h = @header['from']
  3081. a, = h.addrs
  3082. return default unless a
  3083. return a.phrase if a.phrase
  3084. return h.comments.join(' ') unless h.comments.empty?
  3085. a.spec
  3086. end
  3087. def reply_to_addrs( default = nil )
  3088. if h = @header['reply-to']
  3089. h.addrs
  3090. else
  3091. default
  3092. end
  3093. end
  3094. def reply_to_addrs=( arg )
  3095. set_addrfield 'reply-to', arg
  3096. end
  3097. def reply_to( default = nil )
  3098. addrs2specs(reply_to_addrs(nil)) || default
  3099. end
  3100. def reply_to=( *strs )
  3101. set_string_array_attr 'Reply-To', strs
  3102. end
  3103. def sender_addr( default = nil )
  3104. f = @header['sender'] or return default
  3105. f.addr or return default
  3106. end
  3107. def sender_addr=( addr )
  3108. if addr
  3109. h = HeaderField.internal_new('sender', @config)
  3110. h.addr = addr
  3111. @header['sender'] = h
  3112. else
  3113. @header.delete 'sender'
  3114. end
  3115. addr
  3116. end
  3117. def sender( default )
  3118. f = @header['sender'] or return default
  3119. a = f.addr or return default
  3120. a.spec
  3121. end
  3122. def sender=( str )
  3123. set_string_attr 'Sender', str
  3124. end
  3125. #
  3126. # subject
  3127. #
  3128. def subject( default = nil )
  3129. if h = @header['subject']
  3130. h.body
  3131. else
  3132. default
  3133. end
  3134. end
  3135. alias quoted_subject subject
  3136. def subject=( str )
  3137. set_string_attr 'Subject', str
  3138. end
  3139. #
  3140. # identity & threading
  3141. #
  3142. def message_id( default = nil )
  3143. if h = @header['message-id']
  3144. h.id || default
  3145. else
  3146. default
  3147. end
  3148. end
  3149. def message_id=( str )
  3150. set_string_attr 'Message-Id', str
  3151. end
  3152. def in_reply_to( default = nil )
  3153. if h = @header['in-reply-to']
  3154. h.ids
  3155. else
  3156. default
  3157. end
  3158. end
  3159. def in_reply_to=( *idstrs )
  3160. set_string_array_attr 'In-Reply-To', idstrs
  3161. end
  3162. def references( default = nil )
  3163. if h = @header['references']
  3164. h.refs
  3165. else
  3166. default
  3167. end
  3168. end
  3169. def references=( *strs )
  3170. set_string_array_attr 'References', strs
  3171. end
  3172. #
  3173. # MIME headers
  3174. #
  3175. def mime_version( default = nil )
  3176. if h = @header['mime-version']
  3177. h.version || default
  3178. else
  3179. default
  3180. end
  3181. end
  3182. def mime_version=( m, opt = nil )
  3183. if opt
  3184. if h = @header['mime-version']
  3185. h.major = m
  3186. h.minor = opt
  3187. else
  3188. store 'Mime-Version', "#{m}.#{opt}"
  3189. end
  3190. else
  3191. store 'Mime-Version', m
  3192. end
  3193. m
  3194. end
  3195. def content_type( default = nil )
  3196. if h = @header['content-type']
  3197. h.content_type || default
  3198. else
  3199. default
  3200. end
  3201. end
  3202. def main_type( default = nil )
  3203. if h = @header['content-type']
  3204. h.main_type || default
  3205. else
  3206. default
  3207. end
  3208. end
  3209. def sub_type( default = nil )
  3210. if h = @header['content-type']
  3211. h.sub_type || default
  3212. else
  3213. default
  3214. end
  3215. end
  3216. def set_content_type( str, sub = nil, param = nil )
  3217. if sub
  3218. main, sub = str, sub
  3219. else
  3220. main, sub = str.split(%r</>, 2)
  3221. raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
  3222. end
  3223. if h = @header['content-type']
  3224. h.main_type = main
  3225. h.sub_type = sub
  3226. h.params.clear
  3227. else
  3228. store 'Content-Type', "#{main}/#{sub}"
  3229. end
  3230. @header['content-type'].params.replace param if param
  3231. str
  3232. end
  3233. alias content_type= set_content_type
  3234. def type_param( name, default = nil )
  3235. if h = @header['content-type']
  3236. h[name] || default
  3237. else
  3238. default
  3239. end
  3240. end
  3241. def charset( default = nil )
  3242. if h = @header['content-type']
  3243. h['charset'] or default
  3244. else
  3245. default
  3246. end
  3247. end
  3248. def charset=( str )
  3249. if str
  3250. if h = @header[ 'content-type' ]
  3251. h['charset'] = str
  3252. else
  3253. store 'Content-Type', "text/plain; charset=#{str}"
  3254. end
  3255. end
  3256. str
  3257. end
  3258. def transfer_encoding( default = nil )
  3259. if h = @header['content-transfer-encoding']
  3260. h.encoding || default
  3261. else
  3262. default
  3263. end
  3264. end
  3265. def transfer_encoding=( str )
  3266. set_string_attr 'Content-Transfer-Encoding', str
  3267. end
  3268. alias encoding transfer_encoding
  3269. alias encoding= transfer_encoding=
  3270. alias content_transfer_encoding transfer_encoding
  3271. alias content_transfer_encoding= transfer_encoding=
  3272. def disposition( default = nil )
  3273. if h = @header['content-disposition']
  3274. h.disposition || default
  3275. else
  3276. default
  3277. end
  3278. end
  3279. alias content_disposition disposition
  3280. def set_disposition( str, params = nil )
  3281. if h = @header['content-disposition']
  3282. h.disposition = str
  3283. h.params.clear
  3284. else
  3285. store('Content-Disposition', str)
  3286. h = @header['content-disposition']
  3287. end
  3288. h.params.replace params if params
  3289. end
  3290. alias disposition= set_disposition
  3291. alias set_content_disposition set_disposition
  3292. alias content_disposition= set_disposition
  3293. def disposition_param( name, default = nil )
  3294. if h = @header['content-disposition']
  3295. h[name] || default
  3296. else
  3297. default
  3298. end
  3299. end
  3300. ###
  3301. ### utils
  3302. ###
  3303. def create_reply
  3304. mail = TMail::Mail.parse('')
  3305. mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
  3306. mail.to_addrs = reply_addresses([])
  3307. mail.in_reply_to = [message_id(nil)].compact
  3308. mail.references = references([]) + [message_id(nil)].compact
  3309. mail.mime_version = '1.0'
  3310. mail
  3311. end
  3312. def base64_encode
  3313. store 'Content-Transfer-Encoding', 'Base64'
  3314. self.body = Base64.folding_encode(self.body)
  3315. end
  3316. def base64_decode
  3317. if /base64/i === self.transfer_encoding('')
  3318. store 'Content-Transfer-Encoding', '8bit'
  3319. self.body = Base64.decode(self.body, @config.strict_base64decode?)
  3320. end
  3321. end
  3322. def destinations( default = nil )
  3323. ret = []
  3324. %w( to cc bcc ).each do |nm|
  3325. if h = @header[nm]
  3326. h.addrs.each {|i| ret.push i.address }
  3327. end
  3328. end
  3329. ret.empty? ? default : ret
  3330. end
  3331. def each_destination( &block )
  3332. destinations([]).each do |i|
  3333. if Address === i
  3334. yield i
  3335. else
  3336. i.each(&block)
  3337. end
  3338. end
  3339. end
  3340. alias each_dest each_destination
  3341. def reply_addresses( default = nil )
  3342. reply_to_addrs(nil) or from_addrs(nil) or default
  3343. end
  3344. def error_reply_addresses( default = nil )
  3345. if s = sender(nil)
  3346. [s]
  3347. else
  3348. from_addrs(default)
  3349. end
  3350. end
  3351. def multipart?
  3352. main_type('').downcase == 'multipart'
  3353. end
  3354. end # class Mail
  3355. end # module TMail
  3356. #
  3357. # header.rb
  3358. #
  3359. #--
  3360. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  3361. #
  3362. # Permission is hereby granted, free of charge, to any person obtaining
  3363. # a copy of this software and associated documentation files (the
  3364. # "Software"), to deal in the Software without restriction, including
  3365. # without limitation the rights to use, copy, modify, merge, publish,
  3366. # distribute, sublicense, and/or sell copies of the Software, and to
  3367. # permit persons to whom the Software is furnished to do so, subject to
  3368. # the following conditions:
  3369. #
  3370. # The above copyright notice and this permission notice shall be
  3371. # included in all copies or substantial portions of the Software.
  3372. #
  3373. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  3374. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  3375. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  3376. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  3377. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  3378. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  3379. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  3380. #
  3381. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  3382. # with permission of Minero Aoki.
  3383. #++
  3384. require 'tmail/encode'
  3385. require 'tmail/address'
  3386. require 'tmail/parser'
  3387. require 'tmail/config'
  3388. require 'tmail/utils'
  3389. module TMail
  3390. class HeaderField
  3391. include TextUtils
  3392. class << self
  3393. alias newobj new
  3394. def new( name, body, conf = DEFAULT_CONFIG )
  3395. klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
  3396. klass.newobj body, conf
  3397. end
  3398. def new_from_port( port, name, conf = DEFAULT_CONFIG )
  3399. re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
  3400. str = nil
  3401. port.ropen {|f|
  3402. f.each do |line|
  3403. if m = re.match(line) then str = m.post_match.strip
  3404. elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
  3405. elsif /\A-*\s*\z/ === line then break
  3406. elsif str then break
  3407. end
  3408. end
  3409. }
  3410. new(name, str, Config.to_config(conf))
  3411. end
  3412. def internal_new( name, conf )
  3413. FNAME_TO_CLASS[name].newobj('', conf, true)
  3414. end
  3415. end # class << self
  3416. def initialize( body, conf, intern = false )
  3417. @body = body
  3418. @config = conf
  3419. @illegal = false
  3420. @parsed = false
  3421. if intern
  3422. @parsed = true
  3423. parse_init
  3424. end
  3425. end
  3426. def inspect
  3427. "#<#{self.class} #{@body.inspect}>"
  3428. end
  3429. def illegal?
  3430. @illegal
  3431. end
  3432. def empty?
  3433. ensure_parsed
  3434. return true if @illegal
  3435. isempty?
  3436. end
  3437. private
  3438. def ensure_parsed
  3439. return if @parsed
  3440. @parsed = true
  3441. parse
  3442. end
  3443. # defabstract parse
  3444. # end
  3445. def clear_parse_status
  3446. @parsed = false
  3447. @illegal = false
  3448. end
  3449. public
  3450. def body
  3451. ensure_parsed
  3452. v = Decoder.new(s = '')
  3453. do_accept v
  3454. v.terminate
  3455. s
  3456. end
  3457. def body=( str )
  3458. @body = str
  3459. clear_parse_status
  3460. end
  3461. include StrategyInterface
  3462. def accept( strategy, dummy1 = nil, dummy2 = nil )
  3463. ensure_parsed
  3464. do_accept strategy
  3465. strategy.terminate
  3466. end
  3467. # abstract do_accept
  3468. end
  3469. class UnstructuredHeader < HeaderField
  3470. def body
  3471. ensure_parsed
  3472. @body
  3473. end
  3474. def body=( arg )
  3475. ensure_parsed
  3476. @body = arg
  3477. end
  3478. private
  3479. def parse_init
  3480. end
  3481. def parse
  3482. @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
  3483. end
  3484. def isempty?
  3485. not @body
  3486. end
  3487. def do_accept( strategy )
  3488. strategy.text @body
  3489. end
  3490. end
  3491. class StructuredHeader < HeaderField
  3492. def comments
  3493. ensure_parsed
  3494. @comments
  3495. end
  3496. private
  3497. def parse
  3498. save = nil
  3499. begin
  3500. parse_init
  3501. do_parse
  3502. rescue SyntaxError
  3503. if not save and mime_encoded? @body
  3504. save = @body
  3505. @body = Decoder.decode(save)
  3506. retry
  3507. elsif save
  3508. @body = save
  3509. end
  3510. @illegal = true
  3511. raise if @config.strict_parse?
  3512. end
  3513. end
  3514. def parse_init
  3515. @comments = []
  3516. init
  3517. end
  3518. def do_parse
  3519. obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
  3520. set obj if obj
  3521. end
  3522. end
  3523. class DateTimeHeader < StructuredHeader
  3524. PARSE_TYPE = :DATETIME
  3525. def date
  3526. ensure_parsed
  3527. @date
  3528. end
  3529. def date=( arg )
  3530. ensure_parsed
  3531. @date = arg
  3532. end
  3533. private
  3534. def init
  3535. @date = nil
  3536. end
  3537. def set( t )
  3538. @date = t
  3539. end
  3540. def isempty?
  3541. not @date
  3542. end
  3543. def do_accept( strategy )
  3544. strategy.meta time2str(@date)
  3545. end
  3546. end
  3547. class AddressHeader < StructuredHeader
  3548. PARSE_TYPE = :MADDRESS
  3549. def addrs
  3550. ensure_parsed
  3551. @addrs
  3552. end
  3553. private
  3554. def init
  3555. @addrs = []
  3556. end
  3557. def set( a )
  3558. @addrs = a
  3559. end
  3560. def isempty?
  3561. @addrs.empty?
  3562. end
  3563. def do_accept( strategy )
  3564. first = true
  3565. @addrs.each do |a|
  3566. if first
  3567. first = false
  3568. else
  3569. strategy.meta ','
  3570. strategy.space
  3571. end
  3572. a.accept strategy
  3573. end
  3574. @comments.each do |c|
  3575. strategy.space
  3576. strategy.meta '('
  3577. strategy.text c
  3578. strategy.meta ')'
  3579. end
  3580. end
  3581. end
  3582. class ReturnPathHeader < AddressHeader
  3583. PARSE_TYPE = :RETPATH
  3584. def addr
  3585. addrs()[0]
  3586. end
  3587. def spec
  3588. a = addr() or return nil
  3589. a.spec
  3590. end
  3591. def routes
  3592. a = addr() or return nil
  3593. a.routes
  3594. end
  3595. private
  3596. def do_accept( strategy )
  3597. a = addr()
  3598. strategy.meta '<'
  3599. unless a.routes.empty?
  3600. strategy.meta a.routes.map {|i| '@' + i }.join(',')
  3601. strategy.meta ':'
  3602. end
  3603. spec = a.spec
  3604. strategy.meta spec if spec
  3605. strategy.meta '>'
  3606. end
  3607. end
  3608. class SingleAddressHeader < AddressHeader
  3609. def addr
  3610. addrs()[0]
  3611. end
  3612. private
  3613. def do_accept( strategy )
  3614. a = addr()
  3615. a.accept strategy
  3616. @comments.each do |c|
  3617. strategy.space
  3618. strategy.meta '('
  3619. strategy.text c
  3620. strategy.meta ')'
  3621. end
  3622. end
  3623. end
  3624. class MessageIdHeader < StructuredHeader
  3625. def id
  3626. ensure_parsed
  3627. @id
  3628. end
  3629. def id=( arg )
  3630. ensure_parsed
  3631. @id = arg
  3632. end
  3633. private
  3634. def init
  3635. @id = nil
  3636. end
  3637. def isempty?
  3638. not @id
  3639. end
  3640. def do_parse
  3641. @id = @body.slice(MESSAGE_ID) or
  3642. raise SyntaxError, "wrong Message-ID format: #{@body}"
  3643. end
  3644. def do_accept( strategy )
  3645. strategy.meta @id
  3646. end
  3647. end
  3648. class ReferencesHeader < StructuredHeader
  3649. def refs
  3650. ensure_parsed
  3651. @refs
  3652. end
  3653. def each_id
  3654. self.refs.each do |i|
  3655. yield i if MESSAGE_ID === i
  3656. end
  3657. end
  3658. def ids
  3659. ensure_parsed
  3660. @ids
  3661. end
  3662. def each_phrase
  3663. self.refs.each do |i|
  3664. yield i unless MESSAGE_ID === i
  3665. end
  3666. end
  3667. def phrases
  3668. ret = []
  3669. each_phrase {|i| ret.push i }
  3670. ret
  3671. end
  3672. private
  3673. def init
  3674. @refs = []
  3675. @ids = []
  3676. end
  3677. def isempty?
  3678. @ids.empty?
  3679. end
  3680. def do_parse
  3681. str = @body
  3682. while m = MESSAGE_ID.match(str)
  3683. pre = m.pre_match.strip
  3684. @refs.push pre unless pre.empty?
  3685. @refs.push s = m[0]
  3686. @ids.push s
  3687. str = m.post_match
  3688. end
  3689. str = str.strip
  3690. @refs.push str unless str.empty?
  3691. end
  3692. def do_accept( strategy )
  3693. first = true
  3694. @ids.each do |i|
  3695. if first
  3696. first = false
  3697. else
  3698. strategy.space
  3699. end
  3700. strategy.meta i
  3701. end
  3702. end
  3703. end
  3704. class ReceivedHeader < StructuredHeader
  3705. PARSE_TYPE = :RECEIVED
  3706. def from
  3707. ensure_parsed
  3708. @from
  3709. end
  3710. def from=( arg )
  3711. ensure_parsed
  3712. @from = arg
  3713. end
  3714. def by
  3715. ensure_parsed
  3716. @by
  3717. end
  3718. def by=( arg )
  3719. ensure_parsed
  3720. @by = arg
  3721. end
  3722. def via
  3723. ensure_parsed
  3724. @via
  3725. end
  3726. def via=( arg )
  3727. ensure_parsed
  3728. @via = arg
  3729. end
  3730. def with
  3731. ensure_parsed
  3732. @with
  3733. end
  3734. def id
  3735. ensure_parsed
  3736. @id
  3737. end
  3738. def id=( arg )
  3739. ensure_parsed
  3740. @id = arg
  3741. end
  3742. def _for
  3743. ensure_parsed
  3744. @_for
  3745. end
  3746. def _for=( arg )
  3747. ensure_parsed
  3748. @_for = arg
  3749. end
  3750. def date
  3751. ensure_parsed
  3752. @date
  3753. end
  3754. def date=( arg )
  3755. ensure_parsed
  3756. @date = arg
  3757. end
  3758. private
  3759. def init
  3760. @from = @by = @via = @with = @id = @_for = nil
  3761. @with = []
  3762. @date = nil
  3763. end
  3764. def set( args )
  3765. @from, @by, @via, @with, @id, @_for, @date = *args
  3766. end
  3767. def isempty?
  3768. @with.empty? and not (@from or @by or @via or @id or @_for or @date)
  3769. end
  3770. def do_accept( strategy )
  3771. list = []
  3772. list.push 'from ' + @from if @from
  3773. list.push 'by ' + @by if @by
  3774. list.push 'via ' + @via if @via
  3775. @with.each do |i|
  3776. list.push 'with ' + i
  3777. end
  3778. list.push 'id ' + @id if @id
  3779. list.push 'for <' + @_for + '>' if @_for
  3780. first = true
  3781. list.each do |i|
  3782. strategy.space unless first
  3783. strategy.meta i
  3784. first = false
  3785. end
  3786. if @date
  3787. strategy.meta ';'
  3788. strategy.space
  3789. strategy.meta time2str(@date)
  3790. end
  3791. end
  3792. end
  3793. class KeywordsHeader < StructuredHeader
  3794. PARSE_TYPE = :KEYWORDS
  3795. def keys
  3796. ensure_parsed
  3797. @keys
  3798. end
  3799. private
  3800. def init
  3801. @keys = []
  3802. end
  3803. def set( a )
  3804. @keys = a
  3805. end
  3806. def isempty?
  3807. @keys.empty?
  3808. end
  3809. def do_accept( strategy )
  3810. first = true
  3811. @keys.each do |i|
  3812. if first
  3813. first = false
  3814. else
  3815. strategy.meta ','
  3816. end
  3817. strategy.meta i
  3818. end
  3819. end
  3820. end
  3821. class EncryptedHeader < StructuredHeader
  3822. PARSE_TYPE = :ENCRYPTED
  3823. def encrypter
  3824. ensure_parsed
  3825. @encrypter
  3826. end
  3827. def encrypter=( arg )
  3828. ensure_parsed
  3829. @encrypter = arg
  3830. end
  3831. def keyword
  3832. ensure_parsed
  3833. @keyword
  3834. end
  3835. def keyword=( arg )
  3836. ensure_parsed
  3837. @keyword = arg
  3838. end
  3839. private
  3840. def init
  3841. @encrypter = nil
  3842. @keyword = nil
  3843. end
  3844. def set( args )
  3845. @encrypter, @keyword = args
  3846. end
  3847. def isempty?
  3848. not (@encrypter or @keyword)
  3849. end
  3850. def do_accept( strategy )
  3851. if @key
  3852. strategy.meta @encrypter + ','
  3853. strategy.space
  3854. strategy.meta @keyword
  3855. else
  3856. strategy.meta @encrypter
  3857. end
  3858. end
  3859. end
  3860. class MimeVersionHeader < StructuredHeader
  3861. PARSE_TYPE = :MIMEVERSION
  3862. def major
  3863. ensure_parsed
  3864. @major
  3865. end
  3866. def major=( arg )
  3867. ensure_parsed
  3868. @major = arg
  3869. end
  3870. def minor
  3871. ensure_parsed
  3872. @minor
  3873. end
  3874. def minor=( arg )
  3875. ensure_parsed
  3876. @minor = arg
  3877. end
  3878. def version
  3879. sprintf('%d.%d', major, minor)
  3880. end
  3881. private
  3882. def init
  3883. @major = nil
  3884. @minor = nil
  3885. end
  3886. def set( args )
  3887. @major, @minor = *args
  3888. end
  3889. def isempty?
  3890. not (@major or @minor)
  3891. end
  3892. def do_accept( strategy )
  3893. strategy.meta sprintf('%d.%d', @major, @minor)
  3894. end
  3895. end
  3896. class ContentTypeHeader < StructuredHeader
  3897. PARSE_TYPE = :CTYPE
  3898. def main_type
  3899. ensure_parsed
  3900. @main
  3901. end
  3902. def main_type=( arg )
  3903. ensure_parsed
  3904. @main = arg.downcase
  3905. end
  3906. def sub_type
  3907. ensure_parsed
  3908. @sub
  3909. end
  3910. def sub_type=( arg )
  3911. ensure_parsed
  3912. @sub = arg.downcase
  3913. end
  3914. def content_type
  3915. ensure_parsed
  3916. @sub ? sprintf('%s/%s', @main, @sub) : @main
  3917. end
  3918. def params
  3919. ensure_parsed
  3920. @params
  3921. end
  3922. def []( key )
  3923. ensure_parsed
  3924. @params and @params[key]
  3925. end
  3926. def []=( key, val )
  3927. ensure_parsed
  3928. (@params ||= {})[key] = val
  3929. end
  3930. private
  3931. def init
  3932. @main = @sub = @params = nil
  3933. end
  3934. def set( args )
  3935. @main, @sub, @params = *args
  3936. end
  3937. def isempty?
  3938. not (@main or @sub)
  3939. end
  3940. def do_accept( strategy )
  3941. if @sub
  3942. strategy.meta sprintf('%s/%s', @main, @sub)
  3943. else
  3944. strategy.meta @main
  3945. end
  3946. @params.each do |k,v|
  3947. if v
  3948. strategy.meta ';'
  3949. strategy.space
  3950. strategy.kv_pair k, v
  3951. end
  3952. end
  3953. end
  3954. end
  3955. class ContentTransferEncodingHeader < StructuredHeader
  3956. PARSE_TYPE = :CENCODING
  3957. def encoding
  3958. ensure_parsed
  3959. @encoding
  3960. end
  3961. def encoding=( arg )
  3962. ensure_parsed
  3963. @encoding = arg
  3964. end
  3965. private
  3966. def init
  3967. @encoding = nil
  3968. end
  3969. def set( s )
  3970. @encoding = s
  3971. end
  3972. def isempty?
  3973. not @encoding
  3974. end
  3975. def do_accept( strategy )
  3976. strategy.meta @encoding.capitalize
  3977. end
  3978. end
  3979. class ContentDispositionHeader < StructuredHeader
  3980. PARSE_TYPE = :CDISPOSITION
  3981. def disposition
  3982. ensure_parsed
  3983. @disposition
  3984. end
  3985. def disposition=( str )
  3986. ensure_parsed
  3987. @disposition = str.downcase
  3988. end
  3989. def params
  3990. ensure_parsed
  3991. @params
  3992. end
  3993. def []( key )
  3994. ensure_parsed
  3995. @params and @params[key]
  3996. end
  3997. def []=( key, val )
  3998. ensure_parsed
  3999. (@params ||= {})[key] = val
  4000. end
  4001. private
  4002. def init
  4003. @disposition = @params = nil
  4004. end
  4005. def set( args )
  4006. @disposition, @params = *args
  4007. end
  4008. def isempty?
  4009. not @disposition and (not @params or @params.empty?)
  4010. end
  4011. def do_accept( strategy )
  4012. strategy.meta @disposition
  4013. @params.each do |k,v|
  4014. strategy.meta ';'
  4015. strategy.space
  4016. strategy.kv_pair k, v
  4017. end
  4018. end
  4019. end
  4020. class HeaderField # redefine
  4021. FNAME_TO_CLASS = {
  4022. 'date' => DateTimeHeader,
  4023. 'resent-date' => DateTimeHeader,
  4024. 'to' => AddressHeader,
  4025. 'cc' => AddressHeader,
  4026. 'bcc' => AddressHeader,
  4027. 'from' => AddressHeader,
  4028. 'reply-to' => AddressHeader,
  4029. 'resent-to' => AddressHeader,
  4030. 'resent-cc' => AddressHeader,
  4031. 'resent-bcc' => AddressHeader,
  4032. 'resent-from' => AddressHeader,
  4033. 'resent-reply-to' => AddressHeader,
  4034. 'sender' => SingleAddressHeader,
  4035. 'resent-sender' => SingleAddressHeader,
  4036. 'return-path' => ReturnPathHeader,
  4037. 'message-id' => MessageIdHeader,
  4038. 'resent-message-id' => MessageIdHeader,
  4039. 'in-reply-to' => ReferencesHeader,
  4040. 'received' => ReceivedHeader,
  4041. 'references' => ReferencesHeader,
  4042. 'keywords' => KeywordsHeader,
  4043. 'encrypted' => EncryptedHeader,
  4044. 'mime-version' => MimeVersionHeader,
  4045. 'content-type' => ContentTypeHeader,
  4046. 'content-transfer-encoding' => ContentTransferEncodingHeader,
  4047. 'content-disposition' => ContentDispositionHeader,
  4048. 'content-id' => MessageIdHeader,
  4049. 'subject' => UnstructuredHeader,
  4050. 'comments' => UnstructuredHeader,
  4051. 'content-description' => UnstructuredHeader
  4052. }
  4053. end
  4054. end # module TMail
  4055. #
  4056. # info.rb
  4057. #
  4058. #--
  4059. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4060. #
  4061. # Permission is hereby granted, free of charge, to any person obtaining
  4062. # a copy of this software and associated documentation files (the
  4063. # "Software"), to deal in the Software without restriction, including
  4064. # without limitation the rights to use, copy, modify, merge, publish,
  4065. # distribute, sublicense, and/or sell copies of the Software, and to
  4066. # permit persons to whom the Software is furnished to do so, subject to
  4067. # the following conditions:
  4068. #
  4069. # The above copyright notice and this permission notice shall be
  4070. # included in all copies or substantial portions of the Software.
  4071. #
  4072. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4073. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4074. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4075. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4076. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4077. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4078. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4079. #
  4080. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4081. # with permission of Minero Aoki.
  4082. #++
  4083. module TMail
  4084. Version = '0.10.7'
  4085. Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
  4086. end
  4087. require 'tmail/mailbox'
  4088. #
  4089. # mail.rb
  4090. #
  4091. #--
  4092. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4093. #
  4094. # Permission is hereby granted, free of charge, to any person obtaining
  4095. # a copy of this software and associated documentation files (the
  4096. # "Software"), to deal in the Software without restriction, including
  4097. # without limitation the rights to use, copy, modify, merge, publish,
  4098. # distribute, sublicense, and/or sell copies of the Software, and to
  4099. # permit persons to whom the Software is furnished to do so, subject to
  4100. # the following conditions:
  4101. #
  4102. # The above copyright notice and this permission notice shall be
  4103. # included in all copies or substantial portions of the Software.
  4104. #
  4105. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4106. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4107. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4108. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4109. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4110. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4111. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4112. #
  4113. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4114. # with permission of Minero Aoki.
  4115. #++
  4116. require 'tmail/facade'
  4117. require 'tmail/encode'
  4118. require 'tmail/header'
  4119. require 'tmail/port'
  4120. require 'tmail/config'
  4121. require 'tmail/utils'
  4122. require 'tmail/attachments'
  4123. require 'tmail/quoting'
  4124. require 'socket'
  4125. module TMail
  4126. class Mail
  4127. class << self
  4128. def load( fname )
  4129. new(FilePort.new(fname))
  4130. end
  4131. alias load_from load
  4132. alias loadfrom load
  4133. def parse( str )
  4134. new(StringPort.new(str))
  4135. end
  4136. end
  4137. def initialize( port = nil, conf = DEFAULT_CONFIG )
  4138. @port = port || StringPort.new
  4139. @config = Config.to_config(conf)
  4140. @header = {}
  4141. @body_port = nil
  4142. @body_parsed = false
  4143. @epilogue = ''
  4144. @parts = []
  4145. @port.ropen {|f|
  4146. parse_header f
  4147. parse_body f unless @port.reproducible?
  4148. }
  4149. end
  4150. attr_reader :port
  4151. def inspect
  4152. "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
  4153. end
  4154. #
  4155. # to_s interfaces
  4156. #
  4157. public
  4158. include StrategyInterface
  4159. def write_back( eol = "\n", charset = 'e' )
  4160. parse_body
  4161. @port.wopen {|stream| encoded eol, charset, stream }
  4162. end
  4163. def accept( strategy )
  4164. with_multipart_encoding(strategy) {
  4165. ordered_each do |name, field|
  4166. next if field.empty?
  4167. strategy.header_name canonical(name)
  4168. field.accept strategy
  4169. strategy.puts
  4170. end
  4171. strategy.puts
  4172. body_port().ropen {|r|
  4173. strategy.write r.read
  4174. }
  4175. }
  4176. end
  4177. private
  4178. def canonical( name )
  4179. name.split(/-/).map {|s| s.capitalize }.join('-')
  4180. end
  4181. def with_multipart_encoding( strategy )
  4182. if parts().empty? # DO NOT USE @parts
  4183. yield
  4184. else
  4185. bound = ::TMail.new_boundary
  4186. if @header.key? 'content-type'
  4187. @header['content-type'].params['boundary'] = bound
  4188. else
  4189. store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
  4190. end
  4191. yield
  4192. parts().each do |tm|
  4193. strategy.puts
  4194. strategy.puts '--' + bound
  4195. tm.accept strategy
  4196. end
  4197. strategy.puts
  4198. strategy.puts '--' + bound + '--'
  4199. strategy.write epilogue()
  4200. end
  4201. end
  4202. ###
  4203. ### header
  4204. ###
  4205. public
  4206. ALLOW_MULTIPLE = {
  4207. 'received' => true,
  4208. 'resent-date' => true,
  4209. 'resent-from' => true,
  4210. 'resent-sender' => true,
  4211. 'resent-to' => true,
  4212. 'resent-cc' => true,
  4213. 'resent-bcc' => true,
  4214. 'resent-message-id' => true,
  4215. 'comments' => true,
  4216. 'keywords' => true
  4217. }
  4218. USE_ARRAY = ALLOW_MULTIPLE
  4219. def header
  4220. @header.dup
  4221. end
  4222. def []( key )
  4223. @header[key.downcase]
  4224. end
  4225. def sub_header(key, param)
  4226. (hdr = self[key]) ? hdr[param] : nil
  4227. end
  4228. alias fetch []
  4229. def []=( key, val )
  4230. dkey = key.downcase
  4231. if val.nil?
  4232. @header.delete dkey
  4233. return nil
  4234. end
  4235. case val
  4236. when String
  4237. header = new_hf(key, val)
  4238. when HeaderField
  4239. ;
  4240. when Array
  4241. ALLOW_MULTIPLE.include? dkey or
  4242. raise ArgumentError, "#{key}: Header must not be multiple"
  4243. @header[dkey] = val
  4244. return val
  4245. else
  4246. header = new_hf(key, val.to_s)
  4247. end
  4248. if ALLOW_MULTIPLE.include? dkey
  4249. (@header[dkey] ||= []).push header
  4250. else
  4251. @header[dkey] = header
  4252. end
  4253. val
  4254. end
  4255. alias store []=
  4256. def each_header
  4257. @header.each do |key, val|
  4258. [val].flatten.each {|v| yield key, v }
  4259. end
  4260. end
  4261. alias each_pair each_header
  4262. def each_header_name( &block )
  4263. @header.each_key(&block)
  4264. end
  4265. alias each_key each_header_name
  4266. def each_field( &block )
  4267. @header.values.flatten.each(&block)
  4268. end
  4269. alias each_value each_field
  4270. FIELD_ORDER = %w(
  4271. return-path received
  4272. resent-date resent-from resent-sender resent-to
  4273. resent-cc resent-bcc resent-message-id
  4274. date from sender reply-to to cc bcc
  4275. message-id in-reply-to references
  4276. subject comments keywords
  4277. mime-version content-type content-transfer-encoding
  4278. content-disposition content-description
  4279. )
  4280. def ordered_each
  4281. list = @header.keys
  4282. FIELD_ORDER.each do |name|
  4283. if list.delete(name)
  4284. [@header[name]].flatten.each {|v| yield name, v }
  4285. end
  4286. end
  4287. list.each do |name|
  4288. [@header[name]].flatten.each {|v| yield name, v }
  4289. end
  4290. end
  4291. def clear
  4292. @header.clear
  4293. end
  4294. def delete( key )
  4295. @header.delete key.downcase
  4296. end
  4297. def delete_if
  4298. @header.delete_if do |key,val|
  4299. if Array === val
  4300. val.delete_if {|v| yield key, v }
  4301. val.empty?
  4302. else
  4303. yield key, val
  4304. end
  4305. end
  4306. end
  4307. def keys
  4308. @header.keys
  4309. end
  4310. def key?( key )
  4311. @header.key? key.downcase
  4312. end
  4313. def values_at( *args )
  4314. args.map {|k| @header[k.downcase] }.flatten
  4315. end
  4316. alias indexes values_at
  4317. alias indices values_at
  4318. private
  4319. def parse_header( f )
  4320. name = field = nil
  4321. unixfrom = nil
  4322. while line = f.gets
  4323. case line
  4324. when /\A[ \t]/ # continue from prev line
  4325. raise SyntaxError, 'mail is began by space' unless field
  4326. field << ' ' << line.strip
  4327. when /\A([^\: \t]+):\s*/ # new header line
  4328. add_hf name, field if field
  4329. name = $1
  4330. field = $' #.strip
  4331. when /\A\-*\s*\z/ # end of header
  4332. add_hf name, field if field
  4333. name = field = nil
  4334. break
  4335. when /\AFrom (\S+)/
  4336. unixfrom = $1
  4337. when /^charset=.*/
  4338. else
  4339. raise SyntaxError, "wrong mail header: '#{line.inspect}'"
  4340. end
  4341. end
  4342. add_hf name, field if name
  4343. if unixfrom
  4344. add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
  4345. end
  4346. end
  4347. def add_hf( name, field )
  4348. key = name.downcase
  4349. field = new_hf(name, field)
  4350. if ALLOW_MULTIPLE.include? key
  4351. (@header[key] ||= []).push field
  4352. else
  4353. @header[key] = field
  4354. end
  4355. end
  4356. def new_hf( name, field )
  4357. HeaderField.new(name, field, @config)
  4358. end
  4359. ###
  4360. ### body
  4361. ###
  4362. public
  4363. def body_port
  4364. parse_body
  4365. @body_port
  4366. end
  4367. def each( &block )
  4368. body_port().ropen {|f| f.each(&block) }
  4369. end
  4370. def quoted_body
  4371. parse_body
  4372. @body_port.ropen {|f|
  4373. return f.read
  4374. }
  4375. end
  4376. def body=( str )
  4377. parse_body
  4378. @body_port.wopen {|f| f.write str }
  4379. str
  4380. end
  4381. alias preamble body
  4382. alias preamble= body=
  4383. def epilogue
  4384. parse_body
  4385. @epilogue.dup
  4386. end
  4387. def epilogue=( str )
  4388. parse_body
  4389. @epilogue = str
  4390. str
  4391. end
  4392. def parts
  4393. parse_body
  4394. @parts
  4395. end
  4396. def each_part( &block )
  4397. parts().each(&block)
  4398. end
  4399. private
  4400. def parse_body( f = nil )
  4401. return if @body_parsed
  4402. if f
  4403. parse_body_0 f
  4404. else
  4405. @port.ropen {|f|
  4406. skip_header f
  4407. parse_body_0 f
  4408. }
  4409. end
  4410. @body_parsed = true
  4411. end
  4412. def skip_header( f )
  4413. while line = f.gets
  4414. return if /\A[\r\n]*\z/ === line
  4415. end
  4416. end
  4417. def parse_body_0( f )
  4418. if multipart?
  4419. read_multipart f
  4420. else
  4421. @body_port = @config.new_body_port(self)
  4422. @body_port.wopen {|w|
  4423. w.write f.read
  4424. }
  4425. end
  4426. end
  4427. def read_multipart( src )
  4428. bound = @header['content-type'].params['boundary']
  4429. is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
  4430. lastbound = "--#{bound}--"
  4431. ports = [ @config.new_preamble_port(self) ]
  4432. begin
  4433. f = ports.last.wopen
  4434. while line = src.gets
  4435. if is_sep === line
  4436. f.close
  4437. break if line.strip == lastbound
  4438. ports.push @config.new_part_port(self)
  4439. f = ports.last.wopen
  4440. else
  4441. f << line
  4442. end
  4443. end
  4444. @epilogue = (src.read || '')
  4445. ensure
  4446. f.close if f and not f.closed?
  4447. end
  4448. @body_port = ports.shift
  4449. @parts = ports.map {|p| self.class.new(p, @config) }
  4450. end
  4451. end # class Mail
  4452. end # module TMail
  4453. #
  4454. # mailbox.rb
  4455. #
  4456. #--
  4457. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4458. #
  4459. # Permission is hereby granted, free of charge, to any person obtaining
  4460. # a copy of this software and associated documentation files (the
  4461. # "Software"), to deal in the Software without restriction, including
  4462. # without limitation the rights to use, copy, modify, merge, publish,
  4463. # distribute, sublicense, and/or sell copies of the Software, and to
  4464. # permit persons to whom the Software is furnished to do so, subject to
  4465. # the following conditions:
  4466. #
  4467. # The above copyright notice and this permission notice shall be
  4468. # included in all copies or substantial portions of the Software.
  4469. #
  4470. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4471. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4472. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4473. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4474. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4475. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4476. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4477. #
  4478. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4479. # with permission of Minero Aoki.
  4480. #++
  4481. require 'tmail/port'
  4482. require 'socket'
  4483. require 'mutex_m'
  4484. unless [].respond_to?(:sort_by)
  4485. module Enumerable#:nodoc:
  4486. def sort_by
  4487. map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
  4488. end
  4489. end
  4490. end
  4491. module TMail
  4492. class MhMailbox
  4493. PORT_CLASS = MhPort
  4494. def initialize( dir )
  4495. edir = File.expand_path(dir)
  4496. raise ArgumentError, "not directory: #{dir}"\
  4497. unless FileTest.directory? edir
  4498. @dirname = edir
  4499. @last_file = nil
  4500. @last_atime = nil
  4501. end
  4502. def directory
  4503. @dirname
  4504. end
  4505. alias dirname directory
  4506. attr_accessor :last_atime
  4507. def inspect
  4508. "#<#{self.class} #{@dirname}>"
  4509. end
  4510. def close
  4511. end
  4512. def new_port
  4513. PORT_CLASS.new(next_file_name())
  4514. end
  4515. def each_port
  4516. mail_files().each do |path|
  4517. yield PORT_CLASS.new(path)
  4518. end
  4519. @last_atime = Time.now
  4520. end
  4521. alias each each_port
  4522. def reverse_each_port
  4523. mail_files().reverse_each do |path|
  4524. yield PORT_CLASS.new(path)
  4525. end
  4526. @last_atime = Time.now
  4527. end
  4528. alias reverse_each reverse_each_port
  4529. # old #each_mail returns Port
  4530. #def each_mail
  4531. # each_port do |port|
  4532. # yield Mail.new(port)
  4533. # end
  4534. #end
  4535. def each_new_port( mtime = nil, &block )
  4536. mtime ||= @last_atime
  4537. return each_port(&block) unless mtime
  4538. return unless File.mtime(@dirname) >= mtime
  4539. mail_files().each do |path|
  4540. yield PORT_CLASS.new(path) if File.mtime(path) > mtime
  4541. end
  4542. @last_atime = Time.now
  4543. end
  4544. private
  4545. def mail_files
  4546. Dir.entries(@dirname)\
  4547. .select {|s| /\A\d+\z/ === s }\
  4548. .map {|s| s.to_i }\
  4549. .sort\
  4550. .map {|i| "#{@dirname}/#{i}" }\
  4551. .select {|path| FileTest.file? path }
  4552. end
  4553. def next_file_name
  4554. unless n = @last_file
  4555. n = 0
  4556. Dir.entries(@dirname)\
  4557. .select {|s| /\A\d+\z/ === s }\
  4558. .map {|s| s.to_i }.sort\
  4559. .each do |i|
  4560. next unless FileTest.file? "#{@dirname}/#{i}"
  4561. n = i
  4562. end
  4563. end
  4564. begin
  4565. n += 1
  4566. end while FileTest.exist? "#{@dirname}/#{n}"
  4567. @last_file = n
  4568. "#{@dirname}/#{n}"
  4569. end
  4570. end # MhMailbox
  4571. MhLoader = MhMailbox
  4572. class UNIXMbox
  4573. def UNIXMbox.lock( fname )
  4574. begin
  4575. f = File.open(fname)
  4576. f.flock File::LOCK_EX
  4577. yield f
  4578. ensure
  4579. f.flock File::LOCK_UN
  4580. f.close if f and not f.closed?
  4581. end
  4582. end
  4583. class << self
  4584. alias newobj new
  4585. end
  4586. def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
  4587. tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
  4588. newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
  4589. end
  4590. def UNIXMbox.static_new( fname, dir, readonly = false )
  4591. newobj(fname, dir, readonly, true)
  4592. end
  4593. def initialize( fname, mhdir, readonly, static )
  4594. @filename = fname
  4595. @readonly = readonly
  4596. @closed = false
  4597. Dir.mkdir mhdir
  4598. @real = MhMailbox.new(mhdir)
  4599. @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
  4600. ObjectSpace.define_finalizer self, @finalizer
  4601. end
  4602. def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
  4603. lambda {
  4604. if writeback_p
  4605. lock(mboxfile) {|f|
  4606. mh.each_port do |port|
  4607. f.puts create_from_line(port)
  4608. port.ropen {|r|
  4609. f.puts r.read
  4610. }
  4611. end
  4612. }
  4613. end
  4614. if cleanup_p
  4615. Dir.foreach(mh.dirname) do |fname|
  4616. next if /\A\.\.?\z/ === fname
  4617. File.unlink "#{mh.dirname}/#{fname}"
  4618. end
  4619. Dir.rmdir mh.dirname
  4620. end
  4621. }
  4622. end
  4623. # make _From line
  4624. def UNIXMbox.create_from_line( port )
  4625. sprintf 'From %s %s',
  4626. fromaddr(), TextUtils.time2str(File.mtime(port.filename))
  4627. end
  4628. def UNIXMbox.fromaddr
  4629. h = HeaderField.new_from_port(port, 'Return-Path') ||
  4630. HeaderField.new_from_port(port, 'From') or return 'nobody'
  4631. a = h.addrs[0] or return 'nobody'
  4632. a.spec
  4633. end
  4634. private_class_method :fromaddr
  4635. def close
  4636. return if @closed
  4637. ObjectSpace.undefine_finalizer self
  4638. @finalizer.call
  4639. @finalizer = nil
  4640. @real = nil
  4641. @closed = true
  4642. @updated = nil
  4643. end
  4644. def each_port( &block )
  4645. close_check
  4646. update
  4647. @real.each_port(&block)
  4648. end
  4649. alias each each_port
  4650. def reverse_each_port( &block )
  4651. close_check
  4652. update
  4653. @real.reverse_each_port(&block)
  4654. end
  4655. alias reverse_each reverse_each_port
  4656. # old #each_mail returns Port
  4657. #def each_mail( &block )
  4658. # each_port do |port|
  4659. # yield Mail.new(port)
  4660. # end
  4661. #end
  4662. def each_new_port( mtime = nil )
  4663. close_check
  4664. update
  4665. @real.each_new_port(mtime) {|p| yield p }
  4666. end
  4667. def new_port
  4668. close_check
  4669. @real.new_port
  4670. end
  4671. private
  4672. def close_check
  4673. @closed and raise ArgumentError, 'accessing already closed mbox'
  4674. end
  4675. def update
  4676. return if FileTest.zero?(@filename)
  4677. return if @updated and File.mtime(@filename) < @updated
  4678. w = nil
  4679. port = nil
  4680. time = nil
  4681. UNIXMbox.lock(@filename) {|f|
  4682. begin
  4683. f.each do |line|
  4684. if /\AFrom / === line
  4685. w.close if w
  4686. File.utime time, time, port.filename if time
  4687. port = @real.new_port
  4688. w = port.wopen
  4689. time = fromline2time(line)
  4690. else
  4691. w.print line if w
  4692. end
  4693. end
  4694. ensure
  4695. if w and not w.closed?
  4696. w.close
  4697. File.utime time, time, port.filename if time
  4698. end
  4699. end
  4700. f.truncate(0) unless @readonly
  4701. @updated = Time.now
  4702. }
  4703. end
  4704. def fromline2time( line )
  4705. m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
  4706. or return nil
  4707. Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
  4708. end
  4709. end # UNIXMbox
  4710. MboxLoader = UNIXMbox
  4711. class Maildir
  4712. extend Mutex_m
  4713. PORT_CLASS = MaildirPort
  4714. @seq = 0
  4715. def Maildir.unique_number
  4716. synchronize {
  4717. @seq += 1
  4718. return @seq
  4719. }
  4720. end
  4721. def initialize( dir = nil )
  4722. @dirname = dir || ENV['MAILDIR']
  4723. raise ArgumentError, "not directory: #{@dirname}"\
  4724. unless FileTest.directory? @dirname
  4725. @new = "#{@dirname}/new"
  4726. @tmp = "#{@dirname}/tmp"
  4727. @cur = "#{@dirname}/cur"
  4728. end
  4729. def directory
  4730. @dirname
  4731. end
  4732. def inspect
  4733. "#<#{self.class} #{@dirname}>"
  4734. end
  4735. def close
  4736. end
  4737. def each_port
  4738. mail_files(@cur).each do |path|
  4739. yield PORT_CLASS.new(path)
  4740. end
  4741. end
  4742. alias each each_port
  4743. def reverse_each_port
  4744. mail_files(@cur).reverse_each do |path|
  4745. yield PORT_CLASS.new(path)
  4746. end
  4747. end
  4748. alias reverse_each reverse_each_port
  4749. def new_port
  4750. fname = nil
  4751. tmpfname = nil
  4752. newfname = nil
  4753. begin
  4754. fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
  4755. tmpfname = "#{@tmp}/#{fname}"
  4756. newfname = "#{@new}/#{fname}"
  4757. end while FileTest.exist? tmpfname
  4758. if block_given?
  4759. File.open(tmpfname, 'w') {|f| yield f }
  4760. File.rename tmpfname, newfname
  4761. PORT_CLASS.new(newfname)
  4762. else
  4763. File.open(tmpfname, 'w') {|f| f.write "\n\n" }
  4764. PORT_CLASS.new(tmpfname)
  4765. end
  4766. end
  4767. def each_new_port
  4768. mail_files(@new).each do |path|
  4769. dest = @cur + '/' + File.basename(path)
  4770. File.rename path, dest
  4771. yield PORT_CLASS.new(dest)
  4772. end
  4773. check_tmp
  4774. end
  4775. TOO_OLD = 60 * 60 * 36 # 36 hour
  4776. def check_tmp
  4777. old = Time.now.to_i - TOO_OLD
  4778. each_filename(@tmp) do |full, fname|
  4779. if FileTest.file? full and
  4780. File.stat(full).mtime.to_i < old
  4781. File.unlink full
  4782. end
  4783. end
  4784. end
  4785. private
  4786. def mail_files( dir )
  4787. Dir.entries(dir)\
  4788. .select {|s| s[0] != ?. }\
  4789. .sort_by {|s| s.slice(/\A\d+/).to_i }\
  4790. .map {|s| "#{dir}/#{s}" }\
  4791. .select {|path| FileTest.file? path }
  4792. end
  4793. def each_filename( dir )
  4794. Dir.foreach(dir) do |fname|
  4795. path = "#{dir}/#{fname}"
  4796. if fname[0] != ?. and FileTest.file? path
  4797. yield path, fname
  4798. end
  4799. end
  4800. end
  4801. end # Maildir
  4802. MaildirLoader = Maildir
  4803. end # module TMail
  4804. require 'tmail/mailbox'
  4805. #
  4806. # net.rb
  4807. #
  4808. #--
  4809. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4810. #
  4811. # Permission is hereby granted, free of charge, to any person obtaining
  4812. # a copy of this software and associated documentation files (the
  4813. # "Software"), to deal in the Software without restriction, including
  4814. # without limitation the rights to use, copy, modify, merge, publish,
  4815. # distribute, sublicense, and/or sell copies of the Software, and to
  4816. # permit persons to whom the Software is furnished to do so, subject to
  4817. # the following conditions:
  4818. #
  4819. # The above copyright notice and this permission notice shall be
  4820. # included in all copies or substantial portions of the Software.
  4821. #
  4822. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4823. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4824. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4825. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4826. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4827. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4828. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4829. #
  4830. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4831. # with permission of Minero Aoki.
  4832. #++
  4833. require 'nkf'
  4834. module TMail
  4835. class Mail
  4836. def send_to( smtp )
  4837. do_send_to(smtp) do
  4838. ready_to_send
  4839. end
  4840. end
  4841. def send_text_to( smtp )
  4842. do_send_to(smtp) do
  4843. ready_to_send
  4844. mime_encode
  4845. end
  4846. end
  4847. def do_send_to( smtp )
  4848. from = from_address or raise ArgumentError, 'no from address'
  4849. (dests = destinations).empty? and raise ArgumentError, 'no receipient'
  4850. yield
  4851. send_to_0 smtp, from, dests
  4852. end
  4853. private :do_send_to
  4854. def send_to_0( smtp, from, to )
  4855. smtp.ready(from, to) do |f|
  4856. encoded "\r\n", 'j', f, ''
  4857. end
  4858. end
  4859. def ready_to_send
  4860. delete_no_send_fields
  4861. add_message_id
  4862. add_date
  4863. end
  4864. NOSEND_FIELDS = %w(
  4865. received
  4866. bcc
  4867. )
  4868. def delete_no_send_fields
  4869. NOSEND_FIELDS.each do |nm|
  4870. delete nm
  4871. end
  4872. delete_if {|n,v| v.empty? }
  4873. end
  4874. def add_message_id( fqdn = nil )
  4875. self.message_id = ::TMail::new_message_id(fqdn)
  4876. end
  4877. def add_date
  4878. self.date = Time.now
  4879. end
  4880. def mime_encode
  4881. if parts.empty?
  4882. mime_encode_singlepart
  4883. else
  4884. mime_encode_multipart true
  4885. end
  4886. end
  4887. def mime_encode_singlepart
  4888. self.mime_version = '1.0'
  4889. b = body
  4890. if NKF.guess(b) != NKF::BINARY
  4891. mime_encode_text b
  4892. else
  4893. mime_encode_binary b
  4894. end
  4895. end
  4896. def mime_encode_text( body )
  4897. self.body = NKF.nkf('-j -m0', body)
  4898. self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
  4899. self.encoding = '7bit'
  4900. end
  4901. def mime_encode_binary( body )
  4902. self.body = [body].pack('m')
  4903. self.set_content_type 'application', 'octet-stream'
  4904. self.encoding = 'Base64'
  4905. end
  4906. def mime_encode_multipart( top = true )
  4907. self.mime_version = '1.0' if top
  4908. self.set_content_type 'multipart', 'mixed'
  4909. e = encoding(nil)
  4910. if e and not /\A(?:7bit|8bit|binary)\z/i === e
  4911. raise ArgumentError,
  4912. 'using C.T.Encoding with multipart mail is not permitted'
  4913. end
  4914. end
  4915. def create_empty_mail
  4916. self.class.new(StringPort.new(''), @config)
  4917. end
  4918. def create_reply
  4919. setup_reply create_empty_mail()
  4920. end
  4921. def setup_reply( m )
  4922. if tmp = reply_addresses(nil)
  4923. m.to_addrs = tmp
  4924. end
  4925. mid = message_id(nil)
  4926. tmp = references(nil) || []
  4927. tmp.push mid if mid
  4928. m.in_reply_to = [mid] if mid
  4929. m.references = tmp unless tmp.empty?
  4930. m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
  4931. m
  4932. end
  4933. def create_forward
  4934. setup_forward create_empty_mail()
  4935. end
  4936. def setup_forward( mail )
  4937. m = Mail.new(StringPort.new(''))
  4938. m.body = decoded
  4939. m.set_content_type 'message', 'rfc822'
  4940. m.encoding = encoding('7bit')
  4941. mail.parts.push m
  4942. end
  4943. end
  4944. class DeleteFields
  4945. NOSEND_FIELDS = %w(
  4946. received
  4947. bcc
  4948. )
  4949. def initialize( nosend = nil, delempty = true )
  4950. @no_send_fields = nosend || NOSEND_FIELDS.dup
  4951. @delete_empty_fields = delempty
  4952. end
  4953. attr :no_send_fields
  4954. attr :delete_empty_fields, true
  4955. def exec( mail )
  4956. @no_send_fields.each do |nm|
  4957. delete nm
  4958. end
  4959. delete_if {|n,v| v.empty? } if @delete_empty_fields
  4960. end
  4961. end
  4962. class AddMessageId
  4963. def initialize( fqdn = nil )
  4964. @fqdn = fqdn
  4965. end
  4966. attr :fqdn, true
  4967. def exec( mail )
  4968. mail.message_id = ::TMail::new_msgid(@fqdn)
  4969. end
  4970. end
  4971. class AddDate
  4972. def exec( mail )
  4973. mail.date = Time.now
  4974. end
  4975. end
  4976. class MimeEncodeAuto
  4977. def initialize( s = nil, m = nil )
  4978. @singlepart_composer = s || MimeEncodeSingle.new
  4979. @multipart_composer = m || MimeEncodeMulti.new
  4980. end
  4981. attr :singlepart_composer
  4982. attr :multipart_composer
  4983. def exec( mail )
  4984. if mail._builtin_multipart?
  4985. then @multipart_composer
  4986. else @singlepart_composer end.exec mail
  4987. end
  4988. end
  4989. class MimeEncodeSingle
  4990. def exec( mail )
  4991. mail.mime_version = '1.0'
  4992. b = mail.body
  4993. if NKF.guess(b) != NKF::BINARY
  4994. on_text b
  4995. else
  4996. on_binary b
  4997. end
  4998. end
  4999. def on_text( body )
  5000. mail.body = NKF.nkf('-j -m0', body)
  5001. mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
  5002. mail.encoding = '7bit'
  5003. end
  5004. def on_binary( body )
  5005. mail.body = [body].pack('m')
  5006. mail.set_content_type 'application', 'octet-stream'
  5007. mail.encoding = 'Base64'
  5008. end
  5009. end
  5010. class MimeEncodeMulti
  5011. def exec( mail, top = true )
  5012. mail.mime_version = '1.0' if top
  5013. mail.set_content_type 'multipart', 'mixed'
  5014. e = encoding(nil)
  5015. if e and not /\A(?:7bit|8bit|binary)\z/i === e
  5016. raise ArgumentError,
  5017. 'using C.T.Encoding with multipart mail is not permitted'
  5018. end
  5019. mail.parts.each do |m|
  5020. exec m, false if m._builtin_multipart?
  5021. end
  5022. end
  5023. end
  5024. end # module TMail
  5025. #
  5026. # obsolete.rb
  5027. #
  5028. #--
  5029. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  5030. #
  5031. # Permission is hereby granted, free of charge, to any person obtaining
  5032. # a copy of this software and associated documentation files (the
  5033. # "Software"), to deal in the Software without restriction, including
  5034. # without limitation the rights to use, copy, modify, merge, publish,
  5035. # distribute, sublicense, and/or sell copies of the Software, and to
  5036. # permit persons to whom the Software is furnished to do so, subject to
  5037. # the following conditions:
  5038. #
  5039. # The above copyright notice and this permission notice shall be
  5040. # included in all copies or substantial portions of the Software.
  5041. #
  5042. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  5043. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  5044. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  5045. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  5046. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  5047. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  5048. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  5049. #
  5050. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  5051. # with permission of Minero Aoki.
  5052. #++
  5053. module TMail
  5054. # mail.rb
  5055. class Mail
  5056. alias include? key?
  5057. alias has_key? key?
  5058. def values
  5059. ret = []
  5060. each_field {|v| ret.push v }
  5061. ret
  5062. end
  5063. def value?( val )
  5064. HeaderField === val or return false
  5065. [ @header[val.name.downcase] ].flatten.include? val
  5066. end
  5067. alias has_value? value?
  5068. end
  5069. # facade.rb
  5070. class Mail
  5071. def from_addr( default = nil )
  5072. addr, = from_addrs(nil)
  5073. addr || default
  5074. end
  5075. def from_address( default = nil )
  5076. if a = from_addr(nil)
  5077. a.spec
  5078. else
  5079. default
  5080. end
  5081. end
  5082. alias from_address= from_addrs=
  5083. def from_phrase( default = nil )
  5084. if a = from_addr(nil)
  5085. a.phrase
  5086. else
  5087. default
  5088. end
  5089. end
  5090. alias msgid message_id
  5091. alias msgid= message_id=
  5092. alias each_dest each_destination
  5093. end
  5094. # address.rb
  5095. class Address
  5096. alias route routes
  5097. alias addr spec
  5098. def spec=( str )
  5099. @local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
  5100. end
  5101. alias addr= spec=
  5102. alias address= spec=
  5103. end
  5104. # mbox.rb
  5105. class MhMailbox
  5106. alias new_mail new_port
  5107. alias each_mail each_port
  5108. alias each_newmail each_new_port
  5109. end
  5110. class UNIXMbox
  5111. alias new_mail new_port
  5112. alias each_mail each_port
  5113. alias each_newmail each_new_port
  5114. end
  5115. class Maildir
  5116. alias new_mail new_port
  5117. alias each_mail each_port
  5118. alias each_newmail each_new_port
  5119. end
  5120. # utils.rb
  5121. extend TextUtils
  5122. class << self
  5123. alias msgid? message_id?
  5124. alias boundary new_boundary
  5125. alias msgid new_message_id
  5126. alias new_msgid new_message_id
  5127. end
  5128. def Mail.boundary
  5129. ::TMail.new_boundary
  5130. end
  5131. def Mail.msgid
  5132. ::TMail.new_message_id
  5133. end
  5134. end # module TMail
  5135. #
  5136. # DO NOT MODIFY!!!!
  5137. # This file is automatically generated by racc 1.4.3
  5138. # from racc grammer file "parser.y".
  5139. #
  5140. #
  5141. # parser.rb: generated by racc (runtime embedded)
  5142. #
  5143. ###### racc/parser.rb
  5144. unless $".index 'racc/parser.rb'
  5145. $".push 'racc/parser.rb'
  5146. self.class.module_eval <<'..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d', '/home/aamine/lib/ruby/racc/parser.rb', 1
  5147. #
  5148. # parser.rb
  5149. #
  5150. # Copyright (c) 1999-2003 Minero Aoki <aamine@loveruby.net>
  5151. #
  5152. # This program is free software.
  5153. # You can distribute/modify this program under the same terms of ruby.
  5154. #
  5155. # As a special exception, when this code is copied by Racc
  5156. # into a Racc output file, you may use that output file
  5157. # without restriction.
  5158. #
  5159. # $Id: parser.rb,v 1.1.1.1 2004/10/14 11:59:58 webster132 Exp $
  5160. #
  5161. unless defined? NotImplementedError
  5162. NotImplementedError = NotImplementError
  5163. end
  5164. module Racc
  5165. class ParseError < StandardError; end
  5166. end
  5167. unless defined?(::ParseError)
  5168. ParseError = Racc::ParseError
  5169. end
  5170. module Racc
  5171. unless defined? Racc_No_Extentions
  5172. Racc_No_Extentions = false
  5173. end
  5174. class Parser
  5175. Racc_Runtime_Version = '1.4.3'
  5176. Racc_Runtime_Revision = '$Revision: 1.1.1.1 $'.split(/\s+/)[1]
  5177. Racc_Runtime_Core_Version_R = '1.4.3'
  5178. Racc_Runtime_Core_Revision_R = '$Revision: 1.1.1.1 $'.split(/\s+/)[1]
  5179. begin
  5180. require 'racc/cparse'
  5181. # Racc_Runtime_Core_Version_C = (defined in extention)
  5182. Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split(/\s+/)[2]
  5183. unless new.respond_to?(:_racc_do_parse_c, true)
  5184. raise LoadError, 'old cparse.so'
  5185. end
  5186. if Racc_No_Extentions
  5187. raise LoadError, 'selecting ruby version of racc runtime core'
  5188. end
  5189. Racc_Main_Parsing_Routine = :_racc_do_parse_c
  5190. Racc_YY_Parse_Method = :_racc_yyparse_c
  5191. Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C
  5192. Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C
  5193. Racc_Runtime_Type = 'c'
  5194. rescue LoadError
  5195. Racc_Main_Parsing_Routine = :_racc_do_parse_rb
  5196. Racc_YY_Parse_Method = :_racc_yyparse_rb
  5197. Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R
  5198. Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R
  5199. Racc_Runtime_Type = 'ruby'
  5200. end
  5201. def self.racc_runtime_type
  5202. Racc_Runtime_Type
  5203. end
  5204. private
  5205. def _racc_setup
  5206. @yydebug = false unless self.class::Racc_debug_parser
  5207. @yydebug = false unless defined? @yydebug
  5208. if @yydebug
  5209. @racc_debug_out = $stderr unless defined? @racc_debug_out
  5210. @racc_debug_out ||= $stderr
  5211. end
  5212. arg = self.class::Racc_arg
  5213. arg[13] = true if arg.size < 14
  5214. arg
  5215. end
  5216. def _racc_init_sysvars
  5217. @racc_state = [0]
  5218. @racc_tstack = []
  5219. @racc_vstack = []
  5220. @racc_t = nil
  5221. @racc_val = nil
  5222. @racc_read_next = true
  5223. @racc_user_yyerror = false
  5224. @racc_error_status = 0
  5225. end
  5226. ###
  5227. ### do_parse
  5228. ###
  5229. def do_parse
  5230. __send__ Racc_Main_Parsing_Routine, _racc_setup(), false
  5231. end
  5232. def next_token
  5233. raise NotImplementedError, "#{self.class}\#next_token is not defined"
  5234. end
  5235. def _racc_do_parse_rb( arg, in_debug )
  5236. action_table, action_check, action_default, action_pointer,
  5237. goto_table, goto_check, goto_default, goto_pointer,
  5238. nt_base, reduce_table, token_table, shift_n,
  5239. reduce_n, use_result, * = arg
  5240. _racc_init_sysvars
  5241. tok = act = i = nil
  5242. nerr = 0
  5243. catch(:racc_end_parse) {
  5244. while true
  5245. if i = action_pointer[@racc_state[-1]]
  5246. if @racc_read_next
  5247. if @racc_t != 0 # not EOF
  5248. tok, @racc_val = next_token()
  5249. unless tok # EOF
  5250. @racc_t = 0
  5251. else
  5252. @racc_t = (token_table[tok] or 1) # error token
  5253. end
  5254. racc_read_token(@racc_t, tok, @racc_val) if @yydebug
  5255. @racc_read_next = false
  5256. end
  5257. end
  5258. i += @racc_t
  5259. if i >= 0 and
  5260. act = action_table[i] and
  5261. action_check[i] == @racc_state[-1]
  5262. ;
  5263. else
  5264. act = action_default[@racc_state[-1]]
  5265. end
  5266. else
  5267. act = action_default[@racc_state[-1]]
  5268. end
  5269. while act = _racc_evalact(act, arg)
  5270. end
  5271. end
  5272. }
  5273. end
  5274. ###
  5275. ### yyparse
  5276. ###
  5277. def yyparse( recv, mid )
  5278. __send__ Racc_YY_Parse_Method, recv, mid, _racc_setup(), true
  5279. end
  5280. def _racc_yyparse_rb( recv, mid, arg, c_debug )
  5281. action_table, action_check, action_default, action_pointer,
  5282. goto_table, goto_check, goto_default, goto_pointer,
  5283. nt_base, reduce_table, token_table, shift_n,
  5284. reduce_n, use_result, * = arg
  5285. _racc_init_sysvars
  5286. tok = nil
  5287. act = nil
  5288. i = nil
  5289. nerr = 0
  5290. catch(:racc_end_parse) {
  5291. until i = action_pointer[@racc_state[-1]]
  5292. while act = _racc_evalact(action_default[@racc_state[-1]], arg)
  5293. end
  5294. end
  5295. recv.__send__(mid) do |tok, val|
  5296. # $stderr.puts "rd: tok=#{tok}, val=#{val}"
  5297. unless tok
  5298. @racc_t = 0
  5299. else
  5300. @racc_t = (token_table[tok] or 1) # error token
  5301. end
  5302. @racc_val = val
  5303. @racc_read_next = false
  5304. i += @racc_t
  5305. if i >= 0 and
  5306. act = action_table[i] and
  5307. action_check[i] == @racc_state[-1]
  5308. ;
  5309. # $stderr.puts "01: act=#{act}"
  5310. else
  5311. act = action_default[@racc_state[-1]]
  5312. # $stderr.puts "02: act=#{act}"
  5313. # $stderr.puts "curstate=#{@racc_state[-1]}"
  5314. end
  5315. while act = _racc_evalact(act, arg)
  5316. end
  5317. while not (i = action_pointer[@racc_state[-1]]) or
  5318. not @racc_read_next or
  5319. @racc_t == 0 # $
  5320. if i and i += @racc_t and
  5321. i >= 0 and
  5322. act = action_table[i] and
  5323. action_check[i] == @racc_state[-1]
  5324. ;
  5325. # $stderr.puts "03: act=#{act}"
  5326. else
  5327. # $stderr.puts "04: act=#{act}"
  5328. act = action_default[@racc_state[-1]]
  5329. end
  5330. while act = _racc_evalact(act, arg)
  5331. end
  5332. end
  5333. end
  5334. }
  5335. end
  5336. ###
  5337. ### common
  5338. ###
  5339. def _racc_evalact( act, arg )
  5340. # $stderr.puts "ea: act=#{act}"
  5341. action_table, action_check, action_default, action_pointer,
  5342. goto_table, goto_check, goto_default, goto_pointer,
  5343. nt_base, reduce_table, token_table, shift_n,
  5344. reduce_n, use_result, * = arg
  5345. nerr = 0 # tmp
  5346. if act > 0 and act < shift_n
  5347. #
  5348. # shift
  5349. #
  5350. if @racc_error_status > 0
  5351. @racc_error_status -= 1 unless @racc_t == 1 # error token
  5352. end
  5353. @racc_vstack.push @racc_val
  5354. @racc_state.push act
  5355. @racc_read_next = true
  5356. if @yydebug
  5357. @racc_tstack.push @racc_t
  5358. racc_shift @racc_t, @racc_tstack, @racc_vstack
  5359. end
  5360. elsif act < 0 and act > -reduce_n
  5361. #
  5362. # reduce
  5363. #
  5364. code = catch(:racc_jump) {
  5365. @racc_state.push _racc_do_reduce(arg, act)
  5366. false
  5367. }
  5368. if code
  5369. case code
  5370. when 1 # yyerror
  5371. @racc_user_yyerror = true # user_yyerror
  5372. return -reduce_n
  5373. when 2 # yyaccept
  5374. return shift_n
  5375. else
  5376. raise RuntimeError, '[Racc Bug] unknown jump code'
  5377. end
  5378. end
  5379. elsif act == shift_n
  5380. #
  5381. # accept
  5382. #
  5383. racc_accept if @yydebug
  5384. throw :racc_end_parse, @racc_vstack[0]
  5385. elsif act == -reduce_n
  5386. #
  5387. # error
  5388. #
  5389. case @racc_error_status
  5390. when 0
  5391. unless arg[21] # user_yyerror
  5392. nerr += 1
  5393. on_error @racc_t, @racc_val, @racc_vstack
  5394. end
  5395. when 3
  5396. if @racc_t == 0 # is $
  5397. throw :racc_end_parse, nil
  5398. end
  5399. @racc_read_next = true
  5400. end
  5401. @racc_user_yyerror = false
  5402. @racc_error_status = 3
  5403. while true
  5404. if i = action_pointer[@racc_state[-1]]
  5405. i += 1 # error token
  5406. if i >= 0 and
  5407. (act = action_table[i]) and
  5408. action_check[i] == @racc_state[-1]
  5409. break
  5410. end
  5411. end
  5412. throw :racc_end_parse, nil if @racc_state.size < 2
  5413. @racc_state.pop
  5414. @racc_vstack.pop
  5415. if @yydebug
  5416. @racc_tstack.pop
  5417. racc_e_pop @racc_state, @racc_tstack, @racc_vstack
  5418. end
  5419. end
  5420. return act
  5421. else
  5422. raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}"
  5423. end
  5424. racc_next_state(@racc_state[-1], @racc_state) if @yydebug
  5425. nil
  5426. end
  5427. def _racc_do_reduce( arg, act )
  5428. action_table, action_check, action_default, action_pointer,
  5429. goto_table, goto_check, goto_default, goto_pointer,
  5430. nt_base, reduce_table, token_table, shift_n,
  5431. reduce_n, use_result, * = arg
  5432. state = @racc_state
  5433. vstack = @racc_vstack
  5434. tstack = @racc_tstack
  5435. i = act * -3
  5436. len = reduce_table[i]
  5437. reduce_to = reduce_table[i+1]
  5438. method_id = reduce_table[i+2]
  5439. void_array = []
  5440. tmp_t = tstack[-len, len] if @yydebug
  5441. tmp_v = vstack[-len, len]
  5442. tstack[-len, len] = void_array if @yydebug
  5443. vstack[-len, len] = void_array
  5444. state[-len, len] = void_array
  5445. # tstack must be updated AFTER method call
  5446. if use_result
  5447. vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
  5448. else
  5449. vstack.push __send__(method_id, tmp_v, vstack)
  5450. end
  5451. tstack.push reduce_to
  5452. racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
  5453. k1 = reduce_to - nt_base
  5454. if i = goto_pointer[k1]
  5455. i += state[-1]
  5456. if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
  5457. return curstate
  5458. end
  5459. end
  5460. goto_default[k1]
  5461. end
  5462. def on_error( t, val, vstack )
  5463. raise ParseError, sprintf("\nparse error on value %s (%s)",
  5464. val.inspect, token_to_str(t) || '?')
  5465. end
  5466. def yyerror
  5467. throw :racc_jump, 1
  5468. end
  5469. def yyaccept
  5470. throw :racc_jump, 2
  5471. end
  5472. def yyerrok
  5473. @racc_error_status = 0
  5474. end
  5475. # for debugging output
  5476. def racc_read_token( t, tok, val )
  5477. @racc_debug_out.print 'read '
  5478. @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
  5479. @racc_debug_out.puts val.inspect
  5480. @racc_debug_out.puts
  5481. end
  5482. def racc_shift( tok, tstack, vstack )
  5483. @racc_debug_out.puts "shift #{racc_token2str tok}"
  5484. racc_print_stacks tstack, vstack
  5485. @racc_debug_out.puts
  5486. end
  5487. def racc_reduce( toks, sim, tstack, vstack )
  5488. out = @racc_debug_out
  5489. out.print 'reduce '
  5490. if toks.empty?
  5491. out.print ' <none>'
  5492. else
  5493. toks.each {|t| out.print ' ', racc_token2str(t) }
  5494. end
  5495. out.puts " --> #{racc_token2str(sim)}"
  5496. racc_print_stacks tstack, vstack
  5497. @racc_debug_out.puts
  5498. end
  5499. def racc_accept
  5500. @racc_debug_out.puts 'accept'
  5501. @racc_debug_out.puts
  5502. end
  5503. def racc_e_pop( state, tstack, vstack )
  5504. @racc_debug_out.puts 'error recovering mode: pop token'
  5505. racc_print_states state
  5506. racc_print_stacks tstack, vstack
  5507. @racc_debug_out.puts
  5508. end
  5509. def racc_next_state( curstate, state )
  5510. @racc_debug_out.puts "goto #{curstate}"
  5511. racc_print_states state
  5512. @racc_debug_out.puts
  5513. end
  5514. def racc_print_stacks( t, v )
  5515. out = @racc_debug_out
  5516. out.print ' ['
  5517. t.each_index do |i|
  5518. out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
  5519. end
  5520. out.puts ' ]'
  5521. end
  5522. def racc_print_states( s )
  5523. out = @racc_debug_out
  5524. out.print ' ['
  5525. s.each {|st| out.print ' ', st }
  5526. out.puts ' ]'
  5527. end
  5528. def racc_token2str( tok )
  5529. self.class::Racc_token_to_s_table[tok] or
  5530. raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string"
  5531. end
  5532. def token_to_str( t )
  5533. self.class::Racc_token_to_s_table[t]
  5534. end
  5535. end
  5536. end
  5537. ..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d
  5538. end # end of racc/parser.rb
  5539. #
  5540. # parser.rb
  5541. #
  5542. #--
  5543. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  5544. #
  5545. # Permission is hereby granted, free of charge, to any person obtaining
  5546. # a copy of this software and associated documentation files (the
  5547. # "Software"), to deal in the Software without restriction, including
  5548. # without limitation the rights to use, copy, modify, merge, publish,
  5549. # distribute, sublicense, and/or sell copies of the Software, and to
  5550. # permit persons to whom the Software is furnished to do so, subject to
  5551. # the following conditions:
  5552. #
  5553. # The above copyright notice and this permission notice shall be
  5554. # included in all copies or substantial portions of the Software.
  5555. #
  5556. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  5557. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  5558. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  5559. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  5560. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  5561. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  5562. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  5563. #
  5564. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  5565. # with permission of Minero Aoki.
  5566. #++
  5567. require 'tmail/scanner'
  5568. require 'tmail/utils'
  5569. module TMail
  5570. class Parser < Racc::Parser
  5571. module_eval <<'..end parser.y modeval..id43721faf1c', 'parser.y', 331
  5572. include TextUtils
  5573. def self.parse( ident, str, cmt = nil )
  5574. new.parse(ident, str, cmt)
  5575. end
  5576. MAILP_DEBUG = false
  5577. def initialize
  5578. self.debug = MAILP_DEBUG
  5579. end
  5580. def debug=( flag )
  5581. @yydebug = flag && Racc_debug_parser
  5582. @scanner_debug = flag
  5583. end
  5584. def debug
  5585. @yydebug
  5586. end
  5587. def parse( ident, str, comments = nil )
  5588. @scanner = Scanner.new(str, ident, comments)
  5589. @scanner.debug = @scanner_debug
  5590. @first = [ident, ident]
  5591. result = yyparse(self, :parse_in)
  5592. comments.map! {|c| to_kcode(c) } if comments
  5593. result
  5594. end
  5595. private
  5596. def parse_in( &block )
  5597. yield @first
  5598. @scanner.scan(&block)
  5599. end
  5600. def on_error( t, val, vstack )
  5601. raise SyntaxError, "parse error on token #{racc_token2str t}"
  5602. end
  5603. ..end parser.y modeval..id43721faf1c
  5604. ##### racc 1.4.3 generates ###
  5605. racc_reduce_table = [
  5606. 0, 0, :racc_error,
  5607. 2, 35, :_reduce_1,
  5608. 2, 35, :_reduce_2,
  5609. 2, 35, :_reduce_3,
  5610. 2, 35, :_reduce_4,
  5611. 2, 35, :_reduce_5,
  5612. 2, 35, :_reduce_6,
  5613. 2, 35, :_reduce_7,
  5614. 2, 35, :_reduce_8,
  5615. 2, 35, :_reduce_9,
  5616. 2, 35, :_reduce_10,
  5617. 2, 35, :_reduce_11,
  5618. 2, 35, :_reduce_12,
  5619. 6, 36, :_reduce_13,
  5620. 0, 48, :_reduce_none,
  5621. 2, 48, :_reduce_none,
  5622. 3, 49, :_reduce_16,
  5623. 5, 49, :_reduce_17,
  5624. 1, 50, :_reduce_18,
  5625. 7, 37, :_reduce_19,
  5626. 0, 51, :_reduce_none,
  5627. 2, 51, :_reduce_21,
  5628. 0, 52, :_reduce_none,
  5629. 2, 52, :_reduce_23,
  5630. 1, 58, :_reduce_24,
  5631. 3, 58, :_reduce_25,
  5632. 2, 58, :_reduce_26,
  5633. 0, 53, :_reduce_none,
  5634. 2, 53, :_reduce_28,
  5635. 0, 54, :_reduce_29,
  5636. 3, 54, :_reduce_30,
  5637. 0, 55, :_reduce_none,
  5638. 2, 55, :_reduce_32,
  5639. 2, 55, :_reduce_33,
  5640. 0, 56, :_reduce_none,
  5641. 2, 56, :_reduce_35,
  5642. 1, 61, :_reduce_36,
  5643. 1, 61, :_reduce_37,
  5644. 0, 57, :_reduce_none,
  5645. 2, 57, :_reduce_39,
  5646. 1, 38, :_reduce_none,
  5647. 1, 38, :_reduce_none,
  5648. 3, 38, :_reduce_none,
  5649. 1, 46, :_reduce_none,
  5650. 1, 46, :_reduce_none,
  5651. 1, 46, :_reduce_none,
  5652. 1, 39, :_reduce_none,
  5653. 2, 39, :_reduce_47,
  5654. 1, 64, :_reduce_48,
  5655. 3, 64, :_reduce_49,
  5656. 1, 68, :_reduce_none,
  5657. 1, 68, :_reduce_none,
  5658. 1, 69, :_reduce_52,
  5659. 3, 69, :_reduce_53,
  5660. 1, 47, :_reduce_none,
  5661. 1, 47, :_reduce_none,
  5662. 2, 47, :_reduce_56,
  5663. 2, 67, :_reduce_none,
  5664. 3, 65, :_reduce_58,
  5665. 2, 65, :_reduce_59,
  5666. 1, 70, :_reduce_60,
  5667. 2, 70, :_reduce_61,
  5668. 4, 62, :_reduce_62,
  5669. 3, 62, :_reduce_63,
  5670. 2, 72, :_reduce_none,
  5671. 2, 73, :_reduce_65,
  5672. 4, 73, :_reduce_66,
  5673. 3, 63, :_reduce_67,
  5674. 1, 63, :_reduce_68,
  5675. 1, 74, :_reduce_none,
  5676. 2, 74, :_reduce_70,
  5677. 1, 71, :_reduce_71,
  5678. 3, 71, :_reduce_72,
  5679. 1, 59, :_reduce_73,
  5680. 3, 59, :_reduce_74,
  5681. 1, 76, :_reduce_75,
  5682. 2, 76, :_reduce_76,
  5683. 1, 75, :_reduce_none,
  5684. 1, 75, :_reduce_none,
  5685. 1, 75, :_reduce_none,
  5686. 1, 77, :_reduce_none,
  5687. 1, 77, :_reduce_none,
  5688. 1, 77, :_reduce_none,
  5689. 1, 66, :_reduce_none,
  5690. 2, 66, :_reduce_none,
  5691. 3, 60, :_reduce_85,
  5692. 1, 40, :_reduce_86,
  5693. 3, 40, :_reduce_87,
  5694. 1, 79, :_reduce_none,
  5695. 2, 79, :_reduce_89,
  5696. 1, 41, :_reduce_90,
  5697. 2, 41, :_reduce_91,
  5698. 3, 42, :_reduce_92,
  5699. 5, 43, :_reduce_93,
  5700. 3, 43, :_reduce_94,
  5701. 0, 80, :_reduce_95,
  5702. 5, 80, :_reduce_96,
  5703. 1, 82, :_reduce_none,
  5704. 1, 82, :_reduce_none,
  5705. 1, 44, :_reduce_99,
  5706. 3, 45, :_reduce_100,
  5707. 0, 81, :_reduce_none,
  5708. 1, 81, :_reduce_none,
  5709. 1, 78, :_reduce_none,
  5710. 1, 78, :_reduce_none,
  5711. 1, 78, :_reduce_none,
  5712. 1, 78, :_reduce_none,
  5713. 1, 78, :_reduce_none,
  5714. 1, 78, :_reduce_none,
  5715. 1, 78, :_reduce_none ]
  5716. racc_reduce_n = 110
  5717. racc_shift_n = 168
  5718. racc_action_table = [
  5719. -70, -69, 23, 25, 146, 147, 29, 31, 105, 106,
  5720. 16, 17, 20, 22, 136, 27, -70, -69, 32, 101,
  5721. -70, -69, 154, 100, 113, 115, -70, -69, -70, 109,
  5722. 75, 23, 25, 101, 155, 29, 31, 142, 143, 16,
  5723. 17, 20, 22, 107, 27, 23, 25, 32, 98, 29,
  5724. 31, 96, 94, 16, 17, 20, 22, 78, 27, 23,
  5725. 25, 32, 112, 29, 31, 74, 91, 16, 17, 20,
  5726. 22, 88, 117, 92, 81, 32, 23, 25, 80, 123,
  5727. 29, 31, 100, 125, 16, 17, 20, 22, 126, 23,
  5728. 25, 109, 32, 29, 31, 91, 128, 16, 17, 20,
  5729. 22, 129, 27, 23, 25, 32, 101, 29, 31, 101,
  5730. 130, 16, 17, 20, 22, 79, 52, 23, 25, 32,
  5731. 78, 29, 31, 133, 78, 16, 17, 20, 22, 77,
  5732. 23, 25, 75, 32, 29, 31, 65, 62, 16, 17,
  5733. 20, 22, 139, 23, 25, 101, 32, 29, 31, 60,
  5734. 100, 16, 17, 20, 22, 44, 27, 101, 148, 32,
  5735. 23, 25, 120, 149, 29, 31, 152, 153, 16, 17,
  5736. 20, 22, 42, 27, 157, 159, 32, 23, 25, 120,
  5737. 40, 29, 31, 15, 164, 16, 17, 20, 22, 40,
  5738. 27, 23, 25, 32, 68, 29, 31, 166, 167, 16,
  5739. 17, 20, 22, nil, 27, 23, 25, 32, nil, 29,
  5740. 31, 74, nil, 16, 17, 20, 22, nil, 23, 25,
  5741. nil, 32, 29, 31, nil, nil, 16, 17, 20, 22,
  5742. nil, 23, 25, nil, 32, 29, 31, nil, nil, 16,
  5743. 17, 20, 22, nil, 23, 25, nil, 32, 29, 31,
  5744. nil, nil, 16, 17, 20, 22, nil, 23, 25, nil,
  5745. 32, 29, 31, nil, nil, 16, 17, 20, 22, nil,
  5746. 27, 23, 25, 32, nil, 29, 31, nil, nil, 16,
  5747. 17, 20, 22, nil, 23, 25, nil, 32, 29, 31,
  5748. nil, nil, 16, 17, 20, 22, nil, 23, 25, nil,
  5749. 32, 29, 31, nil, nil, 16, 17, 20, 22, nil,
  5750. 84, 25, nil, 32, 29, 31, nil, 87, 16, 17,
  5751. 20, 22, 4, 6, 7, 8, 9, 10, 11, 12,
  5752. 13, 1, 2, 3, 84, 25, nil, nil, 29, 31,
  5753. nil, 87, 16, 17, 20, 22, 84, 25, nil, nil,
  5754. 29, 31, nil, 87, 16, 17, 20, 22, 84, 25,
  5755. nil, nil, 29, 31, nil, 87, 16, 17, 20, 22,
  5756. 84, 25, nil, nil, 29, 31, nil, 87, 16, 17,
  5757. 20, 22, 84, 25, nil, nil, 29, 31, nil, 87,
  5758. 16, 17, 20, 22, 84, 25, nil, nil, 29, 31,
  5759. nil, 87, 16, 17, 20, 22 ]
  5760. racc_action_check = [
  5761. 75, 28, 68, 68, 136, 136, 68, 68, 72, 72,
  5762. 68, 68, 68, 68, 126, 68, 75, 28, 68, 67,
  5763. 75, 28, 143, 66, 86, 86, 75, 28, 75, 75,
  5764. 28, 3, 3, 86, 143, 3, 3, 134, 134, 3,
  5765. 3, 3, 3, 73, 3, 152, 152, 3, 62, 152,
  5766. 152, 60, 56, 152, 152, 152, 152, 51, 152, 52,
  5767. 52, 152, 80, 52, 52, 52, 50, 52, 52, 52,
  5768. 52, 45, 89, 52, 42, 52, 71, 71, 41, 96,
  5769. 71, 71, 97, 98, 71, 71, 71, 71, 100, 7,
  5770. 7, 101, 71, 7, 7, 102, 104, 7, 7, 7,
  5771. 7, 105, 7, 8, 8, 7, 108, 8, 8, 111,
  5772. 112, 8, 8, 8, 8, 40, 8, 9, 9, 8,
  5773. 36, 9, 9, 117, 121, 9, 9, 9, 9, 33,
  5774. 10, 10, 70, 9, 10, 10, 13, 12, 10, 10,
  5775. 10, 10, 130, 2, 2, 131, 10, 2, 2, 11,
  5776. 135, 2, 2, 2, 2, 6, 2, 138, 139, 2,
  5777. 90, 90, 90, 140, 90, 90, 141, 142, 90, 90,
  5778. 90, 90, 5, 90, 148, 151, 90, 127, 127, 127,
  5779. 4, 127, 127, 1, 157, 127, 127, 127, 127, 159,
  5780. 127, 26, 26, 127, 26, 26, 26, 163, 164, 26,
  5781. 26, 26, 26, nil, 26, 27, 27, 26, nil, 27,
  5782. 27, 27, nil, 27, 27, 27, 27, nil, 155, 155,
  5783. nil, 27, 155, 155, nil, nil, 155, 155, 155, 155,
  5784. nil, 122, 122, nil, 155, 122, 122, nil, nil, 122,
  5785. 122, 122, 122, nil, 76, 76, nil, 122, 76, 76,
  5786. nil, nil, 76, 76, 76, 76, nil, 38, 38, nil,
  5787. 76, 38, 38, nil, nil, 38, 38, 38, 38, nil,
  5788. 38, 55, 55, 38, nil, 55, 55, nil, nil, 55,
  5789. 55, 55, 55, nil, 94, 94, nil, 55, 94, 94,
  5790. nil, nil, 94, 94, 94, 94, nil, 59, 59, nil,
  5791. 94, 59, 59, nil, nil, 59, 59, 59, 59, nil,
  5792. 114, 114, nil, 59, 114, 114, nil, 114, 114, 114,
  5793. 114, 114, 0, 0, 0, 0, 0, 0, 0, 0,
  5794. 0, 0, 0, 0, 77, 77, nil, nil, 77, 77,
  5795. nil, 77, 77, 77, 77, 77, 44, 44, nil, nil,
  5796. 44, 44, nil, 44, 44, 44, 44, 44, 113, 113,
  5797. nil, nil, 113, 113, nil, 113, 113, 113, 113, 113,
  5798. 88, 88, nil, nil, 88, 88, nil, 88, 88, 88,
  5799. 88, 88, 74, 74, nil, nil, 74, 74, nil, 74,
  5800. 74, 74, 74, 74, 129, 129, nil, nil, 129, 129,
  5801. nil, 129, 129, 129, 129, 129 ]
  5802. racc_action_pointer = [
  5803. 320, 152, 129, 17, 165, 172, 137, 75, 89, 103,
  5804. 116, 135, 106, 105, nil, nil, nil, nil, nil, nil,
  5805. nil, nil, nil, nil, nil, nil, 177, 191, 1, nil,
  5806. nil, nil, nil, 109, nil, nil, 94, nil, 243, nil,
  5807. 99, 64, 74, nil, 332, 52, nil, nil, nil, nil,
  5808. 50, 31, 45, nil, nil, 257, 36, nil, nil, 283,
  5809. 22, nil, 16, nil, nil, nil, -3, -10, -12, nil,
  5810. 103, 62, -8, 15, 368, 0, 230, 320, nil, nil,
  5811. 47, nil, nil, nil, nil, nil, 4, nil, 356, 50,
  5812. 146, nil, nil, nil, 270, nil, 65, 56, 52, nil,
  5813. 57, 62, 79, nil, 68, 81, nil, nil, 77, nil,
  5814. nil, 80, 96, 344, 296, nil, nil, 108, nil, nil,
  5815. nil, 98, 217, nil, nil, nil, -19, 163, nil, 380,
  5816. 128, 116, nil, nil, 14, 124, -26, nil, 128, 141,
  5817. 148, 141, 152, 7, nil, nil, nil, nil, 160, nil,
  5818. nil, 149, 31, nil, nil, 204, nil, 167, nil, 174,
  5819. nil, nil, nil, 169, 184, nil, nil, nil ]
  5820. racc_action_default = [
  5821. -110, -110, -110, -110, -14, -110, -20, -110, -110, -110,
  5822. -110, -110, -110, -110, -10, -95, -106, -107, -77, -44,
  5823. -108, -11, -109, -79, -43, -103, -110, -110, -60, -104,
  5824. -55, -105, -78, -68, -54, -71, -45, -12, -110, -1,
  5825. -110, -110, -110, -2, -110, -22, -51, -48, -50, -3,
  5826. -40, -41, -110, -46, -4, -86, -5, -88, -6, -90,
  5827. -110, -7, -95, -8, -9, -99, -101, -61, -59, -56,
  5828. -69, -110, -110, -110, -110, -75, -110, -110, -57, -15,
  5829. -110, 168, -73, -80, -82, -21, -24, -81, -110, -27,
  5830. -110, -83, -47, -89, -110, -91, -110, -101, -110, -100,
  5831. -102, -75, -58, -52, -110, -110, -64, -63, -65, -76,
  5832. -72, -67, -110, -110, -110, -26, -23, -110, -29, -49,
  5833. -84, -42, -87, -92, -94, -95, -110, -110, -62, -110,
  5834. -110, -25, -74, -28, -31, -101, -110, -53, -66, -110,
  5835. -110, -34, -110, -110, -93, -96, -98, -97, -110, -18,
  5836. -13, -38, -110, -30, -33, -110, -32, -16, -19, -14,
  5837. -35, -36, -37, -110, -110, -39, -85, -17 ]
  5838. racc_goto_table = [
  5839. 39, 67, 70, 73, 24, 37, 69, 66, 36, 38,
  5840. 57, 59, 55, 67, 108, 83, 90, 111, 69, 99,
  5841. 85, 49, 53, 76, 158, 134, 141, 70, 73, 151,
  5842. 118, 89, 45, 156, 160, 150, 140, 21, 14, 19,
  5843. 119, 102, 64, 63, 61, 83, 70, 104, 83, 58,
  5844. 124, 132, 56, 131, 97, 54, 93, 43, 5, 83,
  5845. 95, 145, 76, nil, 116, 76, nil, nil, 127, 138,
  5846. 103, nil, nil, nil, 38, nil, nil, 110, nil, nil,
  5847. nil, nil, nil, nil, 83, 83, nil, nil, 144, nil,
  5848. nil, nil, nil, nil, nil, 57, 121, 122, nil, nil,
  5849. 83, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5850. nil, nil, nil, nil, nil, nil, nil, 135, nil, nil,
  5851. nil, nil, nil, 93, nil, nil, nil, 70, 162, 137,
  5852. 70, 163, 161, 38, nil, nil, nil, nil, nil, nil,
  5853. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5854. nil, nil, nil, nil, nil, 165 ]
  5855. racc_goto_check = [
  5856. 2, 37, 37, 29, 13, 13, 28, 46, 31, 36,
  5857. 41, 41, 45, 37, 25, 44, 32, 25, 28, 47,
  5858. 24, 4, 4, 42, 23, 20, 21, 37, 29, 22,
  5859. 19, 18, 17, 26, 27, 16, 15, 12, 11, 33,
  5860. 34, 35, 10, 9, 8, 44, 37, 29, 44, 7,
  5861. 47, 43, 6, 25, 46, 5, 41, 3, 1, 44,
  5862. 41, 48, 42, nil, 24, 42, nil, nil, 32, 25,
  5863. 13, nil, nil, nil, 36, nil, nil, 41, nil, nil,
  5864. nil, nil, nil, nil, 44, 44, nil, nil, 47, nil,
  5865. nil, nil, nil, nil, nil, 41, 31, 45, nil, nil,
  5866. 44, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5867. nil, nil, nil, nil, nil, nil, nil, 46, nil, nil,
  5868. nil, nil, nil, 41, nil, nil, nil, 37, 29, 13,
  5869. 37, 29, 28, 36, nil, nil, nil, nil, nil, nil,
  5870. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5871. nil, nil, nil, nil, nil, 2 ]
  5872. racc_goto_pointer = [
  5873. nil, 58, -4, 51, 14, 47, 43, 39, 33, 31,
  5874. 29, 37, 35, 2, nil, -94, -105, 26, -14, -59,
  5875. -93, -108, -112, -127, -24, -60, -110, -118, -20, -24,
  5876. nil, 6, -34, 37, -50, -27, 6, -25, nil, nil,
  5877. nil, 1, -5, -63, -29, 3, -8, -47, -75 ]
  5878. racc_goto_default = [
  5879. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5880. nil, nil, nil, 48, 41, nil, nil, nil, nil, nil,
  5881. nil, nil, nil, nil, nil, 86, nil, nil, 30, 34,
  5882. 50, 51, nil, 46, 47, nil, 26, 28, 71, 72,
  5883. 33, 35, 114, 82, 18, nil, nil, nil, nil ]
  5884. racc_token_table = {
  5885. false => 0,
  5886. Object.new => 1,
  5887. :DATETIME => 2,
  5888. :RECEIVED => 3,
  5889. :MADDRESS => 4,
  5890. :RETPATH => 5,
  5891. :KEYWORDS => 6,
  5892. :ENCRYPTED => 7,
  5893. :MIMEVERSION => 8,
  5894. :CTYPE => 9,
  5895. :CENCODING => 10,
  5896. :CDISPOSITION => 11,
  5897. :ADDRESS => 12,
  5898. :MAILBOX => 13,
  5899. :DIGIT => 14,
  5900. :ATOM => 15,
  5901. "," => 16,
  5902. ":" => 17,
  5903. :FROM => 18,
  5904. :BY => 19,
  5905. "@" => 20,
  5906. :DOMLIT => 21,
  5907. :VIA => 22,
  5908. :WITH => 23,
  5909. :ID => 24,
  5910. :FOR => 25,
  5911. ";" => 26,
  5912. "<" => 27,
  5913. ">" => 28,
  5914. "." => 29,
  5915. :QUOTED => 30,
  5916. :TOKEN => 31,
  5917. "/" => 32,
  5918. "=" => 33 }
  5919. racc_use_result_var = false
  5920. racc_nt_base = 34
  5921. Racc_arg = [
  5922. racc_action_table,
  5923. racc_action_check,
  5924. racc_action_default,
  5925. racc_action_pointer,
  5926. racc_goto_table,
  5927. racc_goto_check,
  5928. racc_goto_default,
  5929. racc_goto_pointer,
  5930. racc_nt_base,
  5931. racc_reduce_table,
  5932. racc_token_table,
  5933. racc_shift_n,
  5934. racc_reduce_n,
  5935. racc_use_result_var ]
  5936. Racc_token_to_s_table = [
  5937. '$end',
  5938. 'error',
  5939. 'DATETIME',
  5940. 'RECEIVED',
  5941. 'MADDRESS',
  5942. 'RETPATH',
  5943. 'KEYWORDS',
  5944. 'ENCRYPTED',
  5945. 'MIMEVERSION',
  5946. 'CTYPE',
  5947. 'CENCODING',
  5948. 'CDISPOSITION',
  5949. 'ADDRESS',
  5950. 'MAILBOX',
  5951. 'DIGIT',
  5952. 'ATOM',
  5953. '","',
  5954. '":"',
  5955. 'FROM',
  5956. 'BY',
  5957. '"@"',
  5958. 'DOMLIT',
  5959. 'VIA',
  5960. 'WITH',
  5961. 'ID',
  5962. 'FOR',
  5963. '";"',
  5964. '"<"',
  5965. '">"',
  5966. '"."',
  5967. 'QUOTED',
  5968. 'TOKEN',
  5969. '"/"',
  5970. '"="',
  5971. '$start',
  5972. 'content',
  5973. 'datetime',
  5974. 'received',
  5975. 'addrs_TOP',
  5976. 'retpath',
  5977. 'keys',
  5978. 'enc',
  5979. 'version',
  5980. 'ctype',
  5981. 'cencode',
  5982. 'cdisp',
  5983. 'addr_TOP',
  5984. 'mbox',
  5985. 'day',
  5986. 'hour',
  5987. 'zone',
  5988. 'from',
  5989. 'by',
  5990. 'via',
  5991. 'with',
  5992. 'id',
  5993. 'for',
  5994. 'received_datetime',
  5995. 'received_domain',
  5996. 'domain',
  5997. 'msgid',
  5998. 'received_addrspec',
  5999. 'routeaddr',
  6000. 'spec',
  6001. 'addrs',
  6002. 'group_bare',
  6003. 'commas',
  6004. 'group',
  6005. 'addr',
  6006. 'mboxes',
  6007. 'addr_phrase',
  6008. 'local_head',
  6009. 'routes',
  6010. 'at_domains',
  6011. 'local',
  6012. 'word',
  6013. 'dots',
  6014. 'domword',
  6015. 'atom',
  6016. 'phrase',
  6017. 'params',
  6018. 'opt_semicolon',
  6019. 'value']
  6020. Racc_debug_parser = false
  6021. ##### racc system variables end #####
  6022. # reduce 0 omitted
  6023. module_eval <<'.,.,', 'parser.y', 16
  6024. def _reduce_1( val, _values)
  6025. val[1]
  6026. end
  6027. .,.,
  6028. module_eval <<'.,.,', 'parser.y', 17
  6029. def _reduce_2( val, _values)
  6030. val[1]
  6031. end
  6032. .,.,
  6033. module_eval <<'.,.,', 'parser.y', 18
  6034. def _reduce_3( val, _values)
  6035. val[1]
  6036. end
  6037. .,.,
  6038. module_eval <<'.,.,', 'parser.y', 19
  6039. def _reduce_4( val, _values)
  6040. val[1]
  6041. end
  6042. .,.,
  6043. module_eval <<'.,.,', 'parser.y', 20
  6044. def _reduce_5( val, _values)
  6045. val[1]
  6046. end
  6047. .,.,
  6048. module_eval <<'.,.,', 'parser.y', 21
  6049. def _reduce_6( val, _values)
  6050. val[1]
  6051. end
  6052. .,.,
  6053. module_eval <<'.,.,', 'parser.y', 22
  6054. def _reduce_7( val, _values)
  6055. val[1]
  6056. end
  6057. .,.,
  6058. module_eval <<'.,.,', 'parser.y', 23
  6059. def _reduce_8( val, _values)
  6060. val[1]
  6061. end
  6062. .,.,
  6063. module_eval <<'.,.,', 'parser.y', 24
  6064. def _reduce_9( val, _values)
  6065. val[1]
  6066. end
  6067. .,.,
  6068. module_eval <<'.,.,', 'parser.y', 25
  6069. def _reduce_10( val, _values)
  6070. val[1]
  6071. end
  6072. .,.,
  6073. module_eval <<'.,.,', 'parser.y', 26
  6074. def _reduce_11( val, _values)
  6075. val[1]
  6076. end
  6077. .,.,
  6078. module_eval <<'.,.,', 'parser.y', 27
  6079. def _reduce_12( val, _values)
  6080. val[1]
  6081. end
  6082. .,.,
  6083. module_eval <<'.,.,', 'parser.y', 33
  6084. def _reduce_13( val, _values)
  6085. t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0)
  6086. (t + val[4] - val[5]).localtime
  6087. end
  6088. .,.,
  6089. # reduce 14 omitted
  6090. # reduce 15 omitted
  6091. module_eval <<'.,.,', 'parser.y', 42
  6092. def _reduce_16( val, _values)
  6093. (val[0].to_i * 60 * 60) +
  6094. (val[2].to_i * 60)
  6095. end
  6096. .,.,
  6097. module_eval <<'.,.,', 'parser.y', 47
  6098. def _reduce_17( val, _values)
  6099. (val[0].to_i * 60 * 60) +
  6100. (val[2].to_i * 60) +
  6101. (val[4].to_i)
  6102. end
  6103. .,.,
  6104. module_eval <<'.,.,', 'parser.y', 54
  6105. def _reduce_18( val, _values)
  6106. timezone_string_to_unixtime(val[0])
  6107. end
  6108. .,.,
  6109. module_eval <<'.,.,', 'parser.y', 59
  6110. def _reduce_19( val, _values)
  6111. val
  6112. end
  6113. .,.,
  6114. # reduce 20 omitted
  6115. module_eval <<'.,.,', 'parser.y', 65
  6116. def _reduce_21( val, _values)
  6117. val[1]
  6118. end
  6119. .,.,
  6120. # reduce 22 omitted
  6121. module_eval <<'.,.,', 'parser.y', 71
  6122. def _reduce_23( val, _values)
  6123. val[1]
  6124. end
  6125. .,.,
  6126. module_eval <<'.,.,', 'parser.y', 77
  6127. def _reduce_24( val, _values)
  6128. join_domain(val[0])
  6129. end
  6130. .,.,
  6131. module_eval <<'.,.,', 'parser.y', 81
  6132. def _reduce_25( val, _values)
  6133. join_domain(val[2])
  6134. end
  6135. .,.,
  6136. module_eval <<'.,.,', 'parser.y', 85
  6137. def _reduce_26( val, _values)
  6138. join_domain(val[0])
  6139. end
  6140. .,.,
  6141. # reduce 27 omitted
  6142. module_eval <<'.,.,', 'parser.y', 91
  6143. def _reduce_28( val, _values)
  6144. val[1]
  6145. end
  6146. .,.,
  6147. module_eval <<'.,.,', 'parser.y', 96
  6148. def _reduce_29( val, _values)
  6149. []
  6150. end
  6151. .,.,
  6152. module_eval <<'.,.,', 'parser.y', 100
  6153. def _reduce_30( val, _values)
  6154. val[0].push val[2]
  6155. val[0]
  6156. end
  6157. .,.,
  6158. # reduce 31 omitted
  6159. module_eval <<'.,.,', 'parser.y', 107
  6160. def _reduce_32( val, _values)
  6161. val[1]
  6162. end
  6163. .,.,
  6164. module_eval <<'.,.,', 'parser.y', 111
  6165. def _reduce_33( val, _values)
  6166. val[1]
  6167. end
  6168. .,.,
  6169. # reduce 34 omitted
  6170. module_eval <<'.,.,', 'parser.y', 117
  6171. def _reduce_35( val, _values)
  6172. val[1]
  6173. end
  6174. .,.,
  6175. module_eval <<'.,.,', 'parser.y', 123
  6176. def _reduce_36( val, _values)
  6177. val[0].spec
  6178. end
  6179. .,.,
  6180. module_eval <<'.,.,', 'parser.y', 127
  6181. def _reduce_37( val, _values)
  6182. val[0].spec
  6183. end
  6184. .,.,
  6185. # reduce 38 omitted
  6186. module_eval <<'.,.,', 'parser.y', 134
  6187. def _reduce_39( val, _values)
  6188. val[1]
  6189. end
  6190. .,.,
  6191. # reduce 40 omitted
  6192. # reduce 41 omitted
  6193. # reduce 42 omitted
  6194. # reduce 43 omitted
  6195. # reduce 44 omitted
  6196. # reduce 45 omitted
  6197. # reduce 46 omitted
  6198. module_eval <<'.,.,', 'parser.y', 146
  6199. def _reduce_47( val, _values)
  6200. [ Address.new(nil, nil) ]
  6201. end
  6202. .,.,
  6203. module_eval <<'.,.,', 'parser.y', 148
  6204. def _reduce_48( val, _values)
  6205. val
  6206. end
  6207. .,.,
  6208. module_eval <<'.,.,', 'parser.y', 149
  6209. def _reduce_49( val, _values)
  6210. val[0].push val[2]; val[0]
  6211. end
  6212. .,.,
  6213. # reduce 50 omitted
  6214. # reduce 51 omitted
  6215. module_eval <<'.,.,', 'parser.y', 156
  6216. def _reduce_52( val, _values)
  6217. val
  6218. end
  6219. .,.,
  6220. module_eval <<'.,.,', 'parser.y', 160
  6221. def _reduce_53( val, _values)
  6222. val[0].push val[2]
  6223. val[0]
  6224. end
  6225. .,.,
  6226. # reduce 54 omitted
  6227. # reduce 55 omitted
  6228. module_eval <<'.,.,', 'parser.y', 168
  6229. def _reduce_56( val, _values)
  6230. val[1].phrase = Decoder.decode(val[0])
  6231. val[1]
  6232. end
  6233. .,.,
  6234. # reduce 57 omitted
  6235. module_eval <<'.,.,', 'parser.y', 176
  6236. def _reduce_58( val, _values)
  6237. AddressGroup.new(val[0], val[2])
  6238. end
  6239. .,.,
  6240. module_eval <<'.,.,', 'parser.y', 178
  6241. def _reduce_59( val, _values)
  6242. AddressGroup.new(val[0], [])
  6243. end
  6244. .,.,
  6245. module_eval <<'.,.,', 'parser.y', 181
  6246. def _reduce_60( val, _values)
  6247. val[0].join('.')
  6248. end
  6249. .,.,
  6250. module_eval <<'.,.,', 'parser.y', 182
  6251. def _reduce_61( val, _values)
  6252. val[0] << ' ' << val[1].join('.')
  6253. end
  6254. .,.,
  6255. module_eval <<'.,.,', 'parser.y', 186
  6256. def _reduce_62( val, _values)
  6257. val[2].routes.replace val[1]
  6258. val[2]
  6259. end
  6260. .,.,
  6261. module_eval <<'.,.,', 'parser.y', 191
  6262. def _reduce_63( val, _values)
  6263. val[1]
  6264. end
  6265. .,.,
  6266. # reduce 64 omitted
  6267. module_eval <<'.,.,', 'parser.y', 196
  6268. def _reduce_65( val, _values)
  6269. [ val[1].join('.') ]
  6270. end
  6271. .,.,
  6272. module_eval <<'.,.,', 'parser.y', 197
  6273. def _reduce_66( val, _values)
  6274. val[0].push val[3].join('.'); val[0]
  6275. end
  6276. .,.,
  6277. module_eval <<'.,.,', 'parser.y', 199
  6278. def _reduce_67( val, _values)
  6279. Address.new( val[0], val[2] )
  6280. end
  6281. .,.,
  6282. module_eval <<'.,.,', 'parser.y', 200
  6283. def _reduce_68( val, _values)
  6284. Address.new( val[0], nil )
  6285. end
  6286. .,.,
  6287. # reduce 69 omitted
  6288. module_eval <<'.,.,', 'parser.y', 203
  6289. def _reduce_70( val, _values)
  6290. val[0].push ''; val[0]
  6291. end
  6292. .,.,
  6293. module_eval <<'.,.,', 'parser.y', 206
  6294. def _reduce_71( val, _values)
  6295. val
  6296. end
  6297. .,.,
  6298. module_eval <<'.,.,', 'parser.y', 209
  6299. def _reduce_72( val, _values)
  6300. val[1].times do
  6301. val[0].push ''
  6302. end
  6303. val[0].push val[2]
  6304. val[0]
  6305. end
  6306. .,.,
  6307. module_eval <<'.,.,', 'parser.y', 217
  6308. def _reduce_73( val, _values)
  6309. val
  6310. end
  6311. .,.,
  6312. module_eval <<'.,.,', 'parser.y', 220
  6313. def _reduce_74( val, _values)
  6314. val[1].times do
  6315. val[0].push ''
  6316. end
  6317. val[0].push val[2]
  6318. val[0]
  6319. end
  6320. .,.,
  6321. module_eval <<'.,.,', 'parser.y', 227
  6322. def _reduce_75( val, _values)
  6323. 0
  6324. end
  6325. .,.,
  6326. module_eval <<'.,.,', 'parser.y', 228
  6327. def _reduce_76( val, _values)
  6328. 1
  6329. end
  6330. .,.,
  6331. # reduce 77 omitted
  6332. # reduce 78 omitted
  6333. # reduce 79 omitted
  6334. # reduce 80 omitted
  6335. # reduce 81 omitted
  6336. # reduce 82 omitted
  6337. # reduce 83 omitted
  6338. # reduce 84 omitted
  6339. module_eval <<'.,.,', 'parser.y', 243
  6340. def _reduce_85( val, _values)
  6341. val[1] = val[1].spec
  6342. val.join('')
  6343. end
  6344. .,.,
  6345. module_eval <<'.,.,', 'parser.y', 247
  6346. def _reduce_86( val, _values)
  6347. val
  6348. end
  6349. .,.,
  6350. module_eval <<'.,.,', 'parser.y', 248
  6351. def _reduce_87( val, _values)
  6352. val[0].push val[2]; val[0]
  6353. end
  6354. .,.,
  6355. # reduce 88 omitted
  6356. module_eval <<'.,.,', 'parser.y', 251
  6357. def _reduce_89( val, _values)
  6358. val[0] << ' ' << val[1]
  6359. end
  6360. .,.,
  6361. module_eval <<'.,.,', 'parser.y', 255
  6362. def _reduce_90( val, _values)
  6363. val.push nil
  6364. val
  6365. end
  6366. .,.,
  6367. module_eval <<'.,.,', 'parser.y', 260
  6368. def _reduce_91( val, _values)
  6369. val
  6370. end
  6371. .,.,
  6372. module_eval <<'.,.,', 'parser.y', 265
  6373. def _reduce_92( val, _values)
  6374. [ val[0].to_i, val[2].to_i ]
  6375. end
  6376. .,.,
  6377. module_eval <<'.,.,', 'parser.y', 270
  6378. def _reduce_93( val, _values)
  6379. [ val[0].downcase, val[2].downcase, decode_params(val[3]) ]
  6380. end
  6381. .,.,
  6382. module_eval <<'.,.,', 'parser.y', 274
  6383. def _reduce_94( val, _values)
  6384. [ val[0].downcase, nil, decode_params(val[1]) ]
  6385. end
  6386. .,.,
  6387. module_eval <<'.,.,', 'parser.y', 279
  6388. def _reduce_95( val, _values)
  6389. {}
  6390. end
  6391. .,.,
  6392. module_eval <<'.,.,', 'parser.y', 283
  6393. def _reduce_96( val, _values)
  6394. val[0][ val[2].downcase ] = val[4]
  6395. val[0]
  6396. end
  6397. .,.,
  6398. # reduce 97 omitted
  6399. # reduce 98 omitted
  6400. module_eval <<'.,.,', 'parser.y', 292
  6401. def _reduce_99( val, _values)
  6402. val[0].downcase
  6403. end
  6404. .,.,
  6405. module_eval <<'.,.,', 'parser.y', 297
  6406. def _reduce_100( val, _values)
  6407. [ val[0].downcase, decode_params(val[1]) ]
  6408. end
  6409. .,.,
  6410. # reduce 101 omitted
  6411. # reduce 102 omitted
  6412. # reduce 103 omitted
  6413. # reduce 104 omitted
  6414. # reduce 105 omitted
  6415. # reduce 106 omitted
  6416. # reduce 107 omitted
  6417. # reduce 108 omitted
  6418. # reduce 109 omitted
  6419. def _reduce_none( val, _values)
  6420. val[0]
  6421. end
  6422. end # class Parser
  6423. end # module TMail
  6424. #
  6425. # port.rb
  6426. #
  6427. #--
  6428. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  6429. #
  6430. # Permission is hereby granted, free of charge, to any person obtaining
  6431. # a copy of this software and associated documentation files (the
  6432. # "Software"), to deal in the Software without restriction, including
  6433. # without limitation the rights to use, copy, modify, merge, publish,
  6434. # distribute, sublicense, and/or sell copies of the Software, and to
  6435. # permit persons to whom the Software is furnished to do so, subject to
  6436. # the following conditions:
  6437. #
  6438. # The above copyright notice and this permission notice shall be
  6439. # included in all copies or substantial portions of the Software.
  6440. #
  6441. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  6442. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  6443. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  6444. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  6445. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  6446. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  6447. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6448. #
  6449. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  6450. # with permission of Minero Aoki.
  6451. #++
  6452. require 'tmail/stringio'
  6453. module TMail
  6454. class Port
  6455. def reproducible?
  6456. false
  6457. end
  6458. end
  6459. ###
  6460. ### FilePort
  6461. ###
  6462. class FilePort < Port
  6463. def initialize( fname )
  6464. @filename = File.expand_path(fname)
  6465. super()
  6466. end
  6467. attr_reader :filename
  6468. alias ident filename
  6469. def ==( other )
  6470. other.respond_to?(:filename) and @filename == other.filename
  6471. end
  6472. alias eql? ==
  6473. def hash
  6474. @filename.hash
  6475. end
  6476. def inspect
  6477. "#<#{self.class}:#{@filename}>"
  6478. end
  6479. def reproducible?
  6480. true
  6481. end
  6482. def size
  6483. File.size @filename
  6484. end
  6485. def ropen( &block )
  6486. File.open(@filename, &block)
  6487. end
  6488. def wopen( &block )
  6489. File.open(@filename, 'w', &block)
  6490. end
  6491. def aopen( &block )
  6492. File.open(@filename, 'a', &block)
  6493. end
  6494. def read_all
  6495. ropen {|f|
  6496. return f.read
  6497. }
  6498. end
  6499. def remove
  6500. File.unlink @filename
  6501. end
  6502. def move_to( port )
  6503. begin
  6504. File.link @filename, port.filename
  6505. rescue Errno::EXDEV
  6506. copy_to port
  6507. end
  6508. File.unlink @filename
  6509. end
  6510. alias mv move_to
  6511. def copy_to( port )
  6512. if FilePort === port
  6513. copy_file @filename, port.filename
  6514. else
  6515. File.open(@filename) {|r|
  6516. port.wopen {|w|
  6517. while s = r.sysread(4096)
  6518. w.write << s
  6519. end
  6520. } }
  6521. end
  6522. end
  6523. alias cp copy_to
  6524. private
  6525. # from fileutils.rb
  6526. def copy_file( src, dest )
  6527. st = r = w = nil
  6528. File.open(src, 'rb') {|r|
  6529. File.open(dest, 'wb') {|w|
  6530. st = r.stat
  6531. begin
  6532. while true
  6533. w.write r.sysread(st.blksize)
  6534. end
  6535. rescue EOFError
  6536. end
  6537. } }
  6538. end
  6539. end
  6540. module MailFlags
  6541. def seen=( b )
  6542. set_status 'S', b
  6543. end
  6544. def seen?
  6545. get_status 'S'
  6546. end
  6547. def replied=( b )
  6548. set_status 'R', b
  6549. end
  6550. def replied?
  6551. get_status 'R'
  6552. end
  6553. def flagged=( b )
  6554. set_status 'F', b
  6555. end
  6556. def flagged?
  6557. get_status 'F'
  6558. end
  6559. private
  6560. def procinfostr( str, tag, true_p )
  6561. a = str.upcase.split(//)
  6562. a.push true_p ? tag : nil
  6563. a.delete tag unless true_p
  6564. a.compact.sort.join('').squeeze
  6565. end
  6566. end
  6567. class MhPort < FilePort
  6568. include MailFlags
  6569. private
  6570. def set_status( tag, flag )
  6571. begin
  6572. tmpfile = @filename + '.tmailtmp.' + $$.to_s
  6573. File.open(tmpfile, 'w') {|f|
  6574. write_status f, tag, flag
  6575. }
  6576. File.unlink @filename
  6577. File.link tmpfile, @filename
  6578. ensure
  6579. File.unlink tmpfile
  6580. end
  6581. end
  6582. def write_status( f, tag, flag )
  6583. stat = ''
  6584. File.open(@filename) {|r|
  6585. while line = r.gets
  6586. if line.strip.empty?
  6587. break
  6588. elsif m = /\AX-TMail-Status:/i.match(line)
  6589. stat = m.post_match.strip
  6590. else
  6591. f.print line
  6592. end
  6593. end
  6594. s = procinfostr(stat, tag, flag)
  6595. f.puts 'X-TMail-Status: ' + s unless s.empty?
  6596. f.puts
  6597. while s = r.read(2048)
  6598. f.write s
  6599. end
  6600. }
  6601. end
  6602. def get_status( tag )
  6603. File.foreach(@filename) {|line|
  6604. return false if line.strip.empty?
  6605. if m = /\AX-TMail-Status:/i.match(line)
  6606. return m.post_match.strip.include?(tag[0])
  6607. end
  6608. }
  6609. false
  6610. end
  6611. end
  6612. class MaildirPort < FilePort
  6613. def move_to_new
  6614. new = replace_dir(@filename, 'new')
  6615. File.rename @filename, new
  6616. @filename = new
  6617. end
  6618. def move_to_cur
  6619. new = replace_dir(@filename, 'cur')
  6620. File.rename @filename, new
  6621. @filename = new
  6622. end
  6623. def replace_dir( path, dir )
  6624. "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
  6625. end
  6626. private :replace_dir
  6627. include MailFlags
  6628. private
  6629. MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
  6630. def set_status( tag, flag )
  6631. if m = MAIL_FILE.match(File.basename(@filename))
  6632. s, uniq, type, info, = m.to_a
  6633. return if type and type != '2' # do not change anything
  6634. newname = File.dirname(@filename) + '/' +
  6635. uniq + ':2,' + procinfostr(info.to_s, tag, flag)
  6636. else
  6637. newname = @filename + ':2,' + tag
  6638. end
  6639. File.link @filename, newname
  6640. File.unlink @filename
  6641. @filename = newname
  6642. end
  6643. def get_status( tag )
  6644. m = MAIL_FILE.match(File.basename(@filename)) or return false
  6645. m[2] == '2' and m[3].to_s.include?(tag[0])
  6646. end
  6647. end
  6648. ###
  6649. ### StringPort
  6650. ###
  6651. class StringPort < Port
  6652. def initialize( str = '' )
  6653. @buffer = str
  6654. super()
  6655. end
  6656. def string
  6657. @buffer
  6658. end
  6659. def to_s
  6660. @buffer.dup
  6661. end
  6662. alias read_all to_s
  6663. def size
  6664. @buffer.size
  6665. end
  6666. def ==( other )
  6667. StringPort === other and @buffer.equal? other.string
  6668. end
  6669. alias eql? ==
  6670. def hash
  6671. @buffer.object_id.hash
  6672. end
  6673. def inspect
  6674. "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
  6675. end
  6676. def reproducible?
  6677. true
  6678. end
  6679. def ropen( &block )
  6680. @buffer or raise Errno::ENOENT, "#{inspect} is already removed"
  6681. StringInput.open(@buffer, &block)
  6682. end
  6683. def wopen( &block )
  6684. @buffer = ''
  6685. StringOutput.new(@buffer, &block)
  6686. end
  6687. def aopen( &block )
  6688. @buffer ||= ''
  6689. StringOutput.new(@buffer, &block)
  6690. end
  6691. def remove
  6692. @buffer = nil
  6693. end
  6694. alias rm remove
  6695. def copy_to( port )
  6696. port.wopen {|f|
  6697. f.write @buffer
  6698. }
  6699. end
  6700. alias cp copy_to
  6701. def move_to( port )
  6702. if StringPort === port
  6703. str = @buffer
  6704. port.instance_eval { @buffer = str }
  6705. else
  6706. copy_to port
  6707. end
  6708. remove
  6709. end
  6710. end
  6711. end # module TMail
  6712. module TMail
  6713. class Mail
  6714. def subject(to_charset = 'utf-8')
  6715. Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
  6716. end
  6717. def unquoted_body(to_charset = 'utf-8')
  6718. from_charset = sub_header("content-type", "charset")
  6719. case (content_transfer_encoding || "7bit").downcase
  6720. when "quoted-printable"
  6721. Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
  6722. to_charset, from_charset, true)
  6723. when "base64"
  6724. Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
  6725. from_charset)
  6726. when "7bit", "8bit"
  6727. Unquoter.convert_to(quoted_body, to_charset, from_charset)
  6728. when "binary"
  6729. quoted_body
  6730. else
  6731. quoted_body
  6732. end
  6733. end
  6734. def body(to_charset = 'utf-8', &block)
  6735. attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
  6736. if multipart?
  6737. parts.collect { |part|
  6738. header = part["content-type"]
  6739. if part.multipart?
  6740. part.body(to_charset, &attachment_presenter)
  6741. elsif header.nil?
  6742. ""
  6743. elsif !attachment?(part)
  6744. part.unquoted_body(to_charset)
  6745. else
  6746. attachment_presenter.call(header["name"] || "(unnamed)")
  6747. end
  6748. }.join
  6749. else
  6750. unquoted_body(to_charset)
  6751. end
  6752. end
  6753. end
  6754. class Unquoter
  6755. class << self
  6756. def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
  6757. return "" if text.nil?
  6758. if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/
  6759. from_charset = $1
  6760. quoting_method = $2
  6761. text = $3
  6762. case quoting_method.upcase
  6763. when "Q" then
  6764. unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
  6765. when "B" then
  6766. unquote_base64_and_convert_to(text, to_charset, from_charset)
  6767. else
  6768. raise "unknown quoting method #{quoting_method.inspect}"
  6769. end
  6770. else
  6771. convert_to(text, to_charset, from_charset)
  6772. end
  6773. end
  6774. def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
  6775. text = text.gsub(/_/, " ") unless preserve_underscores
  6776. convert_to(text.unpack("M*").first, to, from)
  6777. end
  6778. def unquote_base64_and_convert_to(text, to, from)
  6779. convert_to(Base64.decode(text).first, to, from)
  6780. end
  6781. begin
  6782. require 'iconv'
  6783. def convert_to(text, to, from)
  6784. return text unless to && from
  6785. text ? Iconv.iconv(to, from, text).first : ""
  6786. rescue Iconv::IllegalSequence, Errno::EINVAL
  6787. # the 'from' parameter specifies a charset other than what the text
  6788. # actually is...not much we can do in this case but just return the
  6789. # unconverted text.
  6790. #
  6791. # Ditto if either parameter represents an unknown charset, like
  6792. # X-UNKNOWN.
  6793. text
  6794. end
  6795. rescue LoadError
  6796. # Not providing quoting support
  6797. def convert_to(text, to, from)
  6798. warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
  6799. text
  6800. end
  6801. end
  6802. end
  6803. end
  6804. end
  6805. if __FILE__ == $0
  6806. require 'test/unit'
  6807. class TC_Unquoter < Test::Unit::TestCase
  6808. def test_unquote_quoted_printable
  6809. a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
  6810. b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
  6811. assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
  6812. end
  6813. def test_unquote_base64
  6814. a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
  6815. b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
  6816. assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
  6817. end
  6818. def test_unquote_without_charset
  6819. a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
  6820. b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
  6821. assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
  6822. end
  6823. end
  6824. end
  6825. #
  6826. # scanner.rb
  6827. #
  6828. #--
  6829. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  6830. #
  6831. # Permission is hereby granted, free of charge, to any person obtaining
  6832. # a copy of this software and associated documentation files (the
  6833. # "Software"), to deal in the Software without restriction, including
  6834. # without limitation the rights to use, copy, modify, merge, publish,
  6835. # distribute, sublicense, and/or sell copies of the Software, and to
  6836. # permit persons to whom the Software is furnished to do so, subject to
  6837. # the following conditions:
  6838. #
  6839. # The above copyright notice and this permission notice shall be
  6840. # included in all copies or substantial portions of the Software.
  6841. #
  6842. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  6843. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  6844. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  6845. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  6846. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  6847. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  6848. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6849. #
  6850. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  6851. # with permission of Minero Aoki.
  6852. #++
  6853. require 'tmail/utils'
  6854. module TMail
  6855. require 'tmail/scanner_r.rb'
  6856. begin
  6857. raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
  6858. require 'tmail/scanner_c.so'
  6859. Scanner = Scanner_C
  6860. rescue LoadError
  6861. Scanner = Scanner_R
  6862. end
  6863. end
  6864. #
  6865. # scanner_r.rb
  6866. #
  6867. #--
  6868. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  6869. #
  6870. # Permission is hereby granted, free of charge, to any person obtaining
  6871. # a copy of this software and associated documentation files (the
  6872. # "Software"), to deal in the Software without restriction, including
  6873. # without limitation the rights to use, copy, modify, merge, publish,
  6874. # distribute, sublicense, and/or sell copies of the Software, and to
  6875. # permit persons to whom the Software is furnished to do so, subject to
  6876. # the following conditions:
  6877. #
  6878. # The above copyright notice and this permission notice shall be
  6879. # included in all copies or substantial portions of the Software.
  6880. #
  6881. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  6882. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  6883. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  6884. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  6885. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  6886. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  6887. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6888. #
  6889. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  6890. # with permission of Minero Aoki.
  6891. #++
  6892. require 'tmail/config'
  6893. module TMail
  6894. class Scanner_R
  6895. Version = '0.10.7'
  6896. Version.freeze
  6897. MIME_HEADERS = {
  6898. :CTYPE => true,
  6899. :CENCODING => true,
  6900. :CDISPOSITION => true
  6901. }
  6902. alnum = 'a-zA-Z0-9'
  6903. atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip
  6904. tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
  6905. atomchars = alnum + Regexp.quote(atomsyms)
  6906. tokenchars = alnum + Regexp.quote(tokensyms)
  6907. iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
  6908. eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
  6909. sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
  6910. utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
  6911. quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
  6912. domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
  6913. comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
  6914. quoted_without_iso2022 = /\A[^\\"]+/n
  6915. domlit_without_iso2022 = /\A[^\\\]]+/n
  6916. comment_without_iso2022 = /\A[^\\()]+/n
  6917. PATTERN_TABLE = {}
  6918. PATTERN_TABLE['EUC'] =
  6919. [
  6920. /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
  6921. /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
  6922. quoted_with_iso2022,
  6923. domlit_with_iso2022,
  6924. comment_with_iso2022
  6925. ]
  6926. PATTERN_TABLE['SJIS'] =
  6927. [
  6928. /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
  6929. /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
  6930. quoted_with_iso2022,
  6931. domlit_with_iso2022,
  6932. comment_with_iso2022
  6933. ]
  6934. PATTERN_TABLE['UTF8'] =
  6935. [
  6936. /\A(?:[#{atomchars}]+|#{utf8str})+/n,
  6937. /\A(?:[#{tokenchars}]+|#{utf8str})+/n,
  6938. quoted_without_iso2022,
  6939. domlit_without_iso2022,
  6940. comment_without_iso2022
  6941. ]
  6942. PATTERN_TABLE['NONE'] =
  6943. [
  6944. /\A[#{atomchars}]+/n,
  6945. /\A[#{tokenchars}]+/n,
  6946. quoted_without_iso2022,
  6947. domlit_without_iso2022,
  6948. comment_without_iso2022
  6949. ]
  6950. def initialize( str, scantype, comments )
  6951. init_scanner str
  6952. @comments = comments || []
  6953. @debug = false
  6954. # fix scanner mode
  6955. @received = (scantype == :RECEIVED)
  6956. @is_mime_header = MIME_HEADERS[scantype]
  6957. atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
  6958. @word_re = (MIME_HEADERS[scantype] ? token : atom)
  6959. end
  6960. attr_accessor :debug
  6961. def scan( &block )
  6962. if @debug
  6963. scan_main do |arr|
  6964. s, v = arr
  6965. printf "%7d %-10s %s\n",
  6966. rest_size(),
  6967. s.respond_to?(:id2name) ? s.id2name : s.inspect,
  6968. v.inspect
  6969. yield arr
  6970. end
  6971. else
  6972. scan_main(&block)
  6973. end
  6974. end
  6975. private
  6976. RECV_TOKEN = {
  6977. 'from' => :FROM,
  6978. 'by' => :BY,
  6979. 'via' => :VIA,
  6980. 'with' => :WITH,
  6981. 'id' => :ID,
  6982. 'for' => :FOR
  6983. }
  6984. def scan_main
  6985. until eof?
  6986. if skip(/\A[\n\r\t ]+/n) # LWSP
  6987. break if eof?
  6988. end
  6989. if s = readstr(@word_re)
  6990. if @is_mime_header
  6991. yield :TOKEN, s
  6992. else
  6993. # atom
  6994. if /\A\d+\z/ === s
  6995. yield :DIGIT, s
  6996. elsif @received
  6997. yield RECV_TOKEN[s.downcase] || :ATOM, s
  6998. else
  6999. yield :ATOM, s
  7000. end
  7001. end
  7002. elsif skip(/\A"/)
  7003. yield :QUOTED, scan_quoted_word()
  7004. elsif skip(/\A\[/)
  7005. yield :DOMLIT, scan_domain_literal()
  7006. elsif skip(/\A\(/)
  7007. @comments.push scan_comment()
  7008. else
  7009. c = readchar()
  7010. yield c, c
  7011. end
  7012. end
  7013. yield false, '$'
  7014. end
  7015. def scan_quoted_word
  7016. scan_qstr(@quoted_re, /\A"/, 'quoted-word')
  7017. end
  7018. def scan_domain_literal
  7019. '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
  7020. end
  7021. def scan_qstr( pattern, terminal, type )
  7022. result = ''
  7023. until eof?
  7024. if s = readstr(pattern) then result << s
  7025. elsif skip(terminal) then return result
  7026. elsif skip(/\A\\/) then result << readchar()
  7027. else
  7028. raise "TMail FATAL: not match in #{type}"
  7029. end
  7030. end
  7031. scan_error! "found unterminated #{type}"
  7032. end
  7033. def scan_comment
  7034. result = ''
  7035. nest = 1
  7036. content = @comment_re
  7037. until eof?
  7038. if s = readstr(content) then result << s
  7039. elsif skip(/\A\)/) then nest -= 1
  7040. return result if nest == 0
  7041. result << ')'
  7042. elsif skip(/\A\(/) then nest += 1
  7043. result << '('
  7044. elsif skip(/\A\\/) then result << readchar()
  7045. else
  7046. raise 'TMail FATAL: not match in comment'
  7047. end
  7048. end
  7049. scan_error! 'found unterminated comment'
  7050. end
  7051. # string scanner
  7052. def init_scanner( str )
  7053. @src = str
  7054. end
  7055. def eof?
  7056. @src.empty?
  7057. end
  7058. def rest_size
  7059. @src.size
  7060. end
  7061. def readstr( re )
  7062. if m = re.match(@src)
  7063. @src = m.post_match
  7064. m[0]
  7065. else
  7066. nil
  7067. end
  7068. end
  7069. def readchar
  7070. readstr(/\A./)
  7071. end
  7072. def skip( re )
  7073. if m = re.match(@src)
  7074. @src = m.post_match
  7075. true
  7076. else
  7077. false
  7078. end
  7079. end
  7080. def scan_error!( msg )
  7081. raise SyntaxError, msg
  7082. end
  7083. end
  7084. end # module TMail
  7085. #
  7086. # stringio.rb
  7087. #
  7088. #--
  7089. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  7090. #
  7091. # Permission is hereby granted, free of charge, to any person obtaining
  7092. # a copy of this software and associated documentation files (the
  7093. # "Software"), to deal in the Software without restriction, including
  7094. # without limitation the rights to use, copy, modify, merge, publish,
  7095. # distribute, sublicense, and/or sell copies of the Software, and to
  7096. # permit persons to whom the Software is furnished to do so, subject to
  7097. # the following conditions:
  7098. #
  7099. # The above copyright notice and this permission notice shall be
  7100. # included in all copies or substantial portions of the Software.
  7101. #
  7102. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7103. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  7104. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  7105. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  7106. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  7107. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  7108. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  7109. #
  7110. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  7111. # with permission of Minero Aoki.
  7112. #++
  7113. class StringInput#:nodoc:
  7114. include Enumerable
  7115. class << self
  7116. def new( str )
  7117. if block_given?
  7118. begin
  7119. f = super
  7120. yield f
  7121. ensure
  7122. f.close if f
  7123. end
  7124. else
  7125. super
  7126. end
  7127. end
  7128. alias open new
  7129. end
  7130. def initialize( str )
  7131. @src = str
  7132. @pos = 0
  7133. @closed = false
  7134. @lineno = 0
  7135. end
  7136. attr_reader :lineno
  7137. def string
  7138. @src
  7139. end
  7140. def inspect
  7141. "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
  7142. end
  7143. def close
  7144. stream_check!
  7145. @pos = nil
  7146. @closed = true
  7147. end
  7148. def closed?
  7149. @closed
  7150. end
  7151. def pos
  7152. stream_check!
  7153. [@pos, @src.size].min
  7154. end
  7155. alias tell pos
  7156. def seek( offset, whence = IO::SEEK_SET )
  7157. stream_check!
  7158. case whence
  7159. when IO::SEEK_SET
  7160. @pos = offset
  7161. when IO::SEEK_CUR
  7162. @pos += offset
  7163. when IO::SEEK_END
  7164. @pos = @src.size - offset
  7165. else
  7166. raise ArgumentError, "unknown seek flag: #{whence}"
  7167. end
  7168. @pos = 0 if @pos < 0
  7169. @pos = [@pos, @src.size + 1].min
  7170. offset
  7171. end
  7172. def rewind
  7173. stream_check!
  7174. @pos = 0
  7175. end
  7176. def eof?
  7177. stream_check!
  7178. @pos > @src.size
  7179. end
  7180. def each( &block )
  7181. stream_check!
  7182. begin
  7183. @src.each(&block)
  7184. ensure
  7185. @pos = 0
  7186. end
  7187. end
  7188. def gets
  7189. stream_check!
  7190. if idx = @src.index(?\n, @pos)
  7191. idx += 1 # "\n".size
  7192. line = @src[ @pos ... idx ]
  7193. @pos = idx
  7194. @pos += 1 if @pos == @src.size
  7195. else
  7196. line = @src[ @pos .. -1 ]
  7197. @pos = @src.size + 1
  7198. end
  7199. @lineno += 1
  7200. line
  7201. end
  7202. def getc
  7203. stream_check!
  7204. ch = @src[@pos]
  7205. @pos += 1
  7206. @pos += 1 if @pos == @src.size
  7207. ch
  7208. end
  7209. def read( len = nil )
  7210. stream_check!
  7211. return read_all unless len
  7212. str = @src[@pos, len]
  7213. @pos += len
  7214. @pos += 1 if @pos == @src.size
  7215. str
  7216. end
  7217. alias sysread read
  7218. def read_all
  7219. stream_check!
  7220. return nil if eof?
  7221. rest = @src[@pos ... @src.size]
  7222. @pos = @src.size + 1
  7223. rest
  7224. end
  7225. def stream_check!
  7226. @closed and raise IOError, 'closed stream'
  7227. end
  7228. end
  7229. class StringOutput#:nodoc:
  7230. class << self
  7231. def new( str = '' )
  7232. if block_given?
  7233. begin
  7234. f = super
  7235. yield f
  7236. ensure
  7237. f.close if f
  7238. end
  7239. else
  7240. super
  7241. end
  7242. end
  7243. alias open new
  7244. end
  7245. def initialize( str = '' )
  7246. @dest = str
  7247. @closed = false
  7248. end
  7249. def close
  7250. @closed = true
  7251. end
  7252. def closed?
  7253. @closed
  7254. end
  7255. def string
  7256. @dest
  7257. end
  7258. alias value string
  7259. alias to_str string
  7260. def size
  7261. @dest.size
  7262. end
  7263. alias pos size
  7264. def inspect
  7265. "#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
  7266. end
  7267. def print( *args )
  7268. stream_check!
  7269. raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
  7270. args.each do |s|
  7271. raise ArgumentError, 'nil not allowed' if s.nil?
  7272. @dest << s.to_s
  7273. end
  7274. nil
  7275. end
  7276. def puts( *args )
  7277. stream_check!
  7278. args.each do |str|
  7279. @dest << (s = str.to_s)
  7280. @dest << "\n" unless s[-1] == ?\n
  7281. end
  7282. @dest << "\n" if args.empty?
  7283. nil
  7284. end
  7285. def putc( ch )
  7286. stream_check!
  7287. @dest << ch.chr
  7288. nil
  7289. end
  7290. def printf( *args )
  7291. stream_check!
  7292. @dest << sprintf(*args)
  7293. nil
  7294. end
  7295. def write( str )
  7296. stream_check!
  7297. s = str.to_s
  7298. @dest << s
  7299. s.size
  7300. end
  7301. alias syswrite write
  7302. def <<( str )
  7303. stream_check!
  7304. @dest << str.to_s
  7305. self
  7306. end
  7307. private
  7308. def stream_check!
  7309. @closed and raise IOError, 'closed stream'
  7310. end
  7311. end
  7312. require 'tmail'
  7313. #
  7314. # utils.rb
  7315. #
  7316. #--
  7317. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  7318. #
  7319. # Permission is hereby granted, free of charge, to any person obtaining
  7320. # a copy of this software and associated documentation files (the
  7321. # "Software"), to deal in the Software without restriction, including
  7322. # without limitation the rights to use, copy, modify, merge, publish,
  7323. # distribute, sublicense, and/or sell copies of the Software, and to
  7324. # permit persons to whom the Software is furnished to do so, subject to
  7325. # the following conditions:
  7326. #
  7327. # The above copyright notice and this permission notice shall be
  7328. # included in all copies or substantial portions of the Software.
  7329. #
  7330. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7331. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  7332. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  7333. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  7334. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  7335. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  7336. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  7337. #
  7338. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  7339. # with permission of Minero Aoki.
  7340. #++
  7341. module TMail
  7342. class SyntaxError < StandardError; end
  7343. def TMail.new_boundary
  7344. 'mimepart_' + random_tag
  7345. end
  7346. def TMail.new_message_id( fqdn = nil )
  7347. fqdn ||= ::Socket.gethostname
  7348. "<#{random_tag()}@#{fqdn}.tmail>"
  7349. end
  7350. def TMail.random_tag
  7351. @uniq += 1
  7352. t = Time.now
  7353. sprintf('%x%x_%x%x%d%x',
  7354. t.to_i, t.tv_usec,
  7355. $$, Thread.current.object_id, @uniq, rand(255))
  7356. end
  7357. private_class_method :random_tag
  7358. @uniq = 0
  7359. module TextUtils
  7360. aspecial = '()<>[]:;.\\,"'
  7361. tspecial = '()<>[];:\\,"/?='
  7362. lwsp = " \t\r\n"
  7363. control = '\x00-\x1f\x7f-\xff'
  7364. ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
  7365. PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
  7366. TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
  7367. CONTROL_CHAR = /[#{control}]/n
  7368. def atom_safe?( str )
  7369. not ATOM_UNSAFE === str
  7370. end
  7371. def quote_atom( str )
  7372. (ATOM_UNSAFE === str) ? dquote(str) : str
  7373. end
  7374. def quote_phrase( str )
  7375. (PHRASE_UNSAFE === str) ? dquote(str) : str
  7376. end
  7377. def token_safe?( str )
  7378. not TOKEN_UNSAFE === str
  7379. end
  7380. def quote_token( str )
  7381. (TOKEN_UNSAFE === str) ? dquote(str) : str
  7382. end
  7383. def dquote( str )
  7384. '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
  7385. end
  7386. private :dquote
  7387. def join_domain( arr )
  7388. arr.map {|i|
  7389. if /\A\[.*\]\z/ === i
  7390. i
  7391. else
  7392. quote_atom(i)
  7393. end
  7394. }.join('.')
  7395. end
  7396. ZONESTR_TABLE = {
  7397. 'jst' => 9 * 60,
  7398. 'eet' => 2 * 60,
  7399. 'bst' => 1 * 60,
  7400. 'met' => 1 * 60,
  7401. 'gmt' => 0,
  7402. 'utc' => 0,
  7403. 'ut' => 0,
  7404. 'nst' => -(3 * 60 + 30),
  7405. 'ast' => -4 * 60,
  7406. 'edt' => -4 * 60,
  7407. 'est' => -5 * 60,
  7408. 'cdt' => -5 * 60,
  7409. 'cst' => -6 * 60,
  7410. 'mdt' => -6 * 60,
  7411. 'mst' => -7 * 60,
  7412. 'pdt' => -7 * 60,
  7413. 'pst' => -8 * 60,
  7414. 'a' => -1 * 60,
  7415. 'b' => -2 * 60,
  7416. 'c' => -3 * 60,
  7417. 'd' => -4 * 60,
  7418. 'e' => -5 * 60,
  7419. 'f' => -6 * 60,
  7420. 'g' => -7 * 60,
  7421. 'h' => -8 * 60,
  7422. 'i' => -9 * 60,
  7423. # j not use
  7424. 'k' => -10 * 60,
  7425. 'l' => -11 * 60,
  7426. 'm' => -12 * 60,
  7427. 'n' => 1 * 60,
  7428. 'o' => 2 * 60,
  7429. 'p' => 3 * 60,
  7430. 'q' => 4 * 60,
  7431. 'r' => 5 * 60,
  7432. 's' => 6 * 60,
  7433. 't' => 7 * 60,
  7434. 'u' => 8 * 60,
  7435. 'v' => 9 * 60,
  7436. 'w' => 10 * 60,
  7437. 'x' => 11 * 60,
  7438. 'y' => 12 * 60,
  7439. 'z' => 0 * 60
  7440. }
  7441. def timezone_string_to_unixtime( str )
  7442. if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
  7443. sec = (m[2].to_i * 60 + m[3].to_i) * 60
  7444. m[1] == '-' ? -sec : sec
  7445. else
  7446. min = ZONESTR_TABLE[str.downcase] or
  7447. raise SyntaxError, "wrong timezone format '#{str}'"
  7448. min * 60
  7449. end
  7450. end
  7451. WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
  7452. MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
  7453. Jul Aug Sep Oct Nov Dec TMailBUG )
  7454. def time2str( tm )
  7455. # [ruby-list:7928]
  7456. gmt = Time.at(tm.to_i)
  7457. gmt.gmtime
  7458. offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
  7459. # DO NOT USE strftime: setlocale() breaks it
  7460. sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
  7461. WDAY[tm.wday], tm.mday, MONTH[tm.month],
  7462. tm.year, tm.hour, tm.min, tm.sec,
  7463. *(offset / 60).divmod(60)
  7464. end
  7465. MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
  7466. def message_id?( str )
  7467. MESSAGE_ID === str
  7468. end
  7469. MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
  7470. def mime_encoded?( str )
  7471. MIME_ENCODED === str
  7472. end
  7473. def decode_params( hash )
  7474. new = Hash.new
  7475. encoded = nil
  7476. hash.each do |key, value|
  7477. if m = /\*(?:(\d+)\*)?\z/.match(key)
  7478. ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
  7479. else
  7480. new[key] = to_kcode(value)
  7481. end
  7482. end
  7483. if encoded
  7484. encoded.each do |key, strings|
  7485. new[key] = decode_RFC2231(strings.join(''))
  7486. end
  7487. end
  7488. new
  7489. end
  7490. NKF_FLAGS = {
  7491. 'EUC' => '-e -m',
  7492. 'SJIS' => '-s -m'
  7493. }
  7494. def to_kcode( str )
  7495. flag = NKF_FLAGS[$KCODE] or return str
  7496. NKF.nkf(flag, str)
  7497. end
  7498. RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
  7499. def decode_RFC2231( str )
  7500. m = RFC2231_ENCODED.match(str) or return str
  7501. begin
  7502. NKF.nkf(NKF_FLAGS[$KCODE],
  7503. m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
  7504. rescue
  7505. m.post_match.gsub(/%[\da-f]{2}/in, "")
  7506. end
  7507. end
  7508. end
  7509. end
  7510. require 'tmail/info'
  7511. require 'tmail/mail'
  7512. require 'tmail/mailbox'
  7513. module ActionMailer
  7514. module VERSION #:nodoc:
  7515. MAJOR = 1
  7516. MINOR = 2
  7517. TINY = 5
  7518. STRING = [MAJOR, MINOR, TINY].join('.')
  7519. end
  7520. end
  7521. #--
  7522. # Copyright (c) 2004 David Heinemeier Hansson
  7523. #
  7524. # Permission is hereby granted, free of charge, to any person obtaining
  7525. # a copy of this software and associated documentation files (the
  7526. # "Software"), to deal in the Software without restriction, including
  7527. # without limitation the rights to use, copy, modify, merge, publish,
  7528. # distribute, sublicense, and/or sell copies of the Software, and to
  7529. # permit persons to whom the Software is furnished to do so, subject to
  7530. # the following conditions:
  7531. #
  7532. # The above copyright notice and this permission notice shall be
  7533. # included in all copies or substantial portions of the Software.
  7534. #
  7535. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7536. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  7537. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  7538. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  7539. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  7540. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  7541. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  7542. #++
  7543. begin
  7544. require 'action_controller'
  7545. rescue LoadError
  7546. begin
  7547. require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller'
  7548. rescue LoadError
  7549. require 'rubygems'
  7550. require_gem 'actionpack', '>= 1.9.1'
  7551. end
  7552. end
  7553. $:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")
  7554. require 'action_mailer/base'
  7555. require 'action_mailer/helpers'
  7556. require 'action_mailer/mail_helper'
  7557. require 'action_mailer/quoting'
  7558. require 'tmail'
  7559. require 'net/smtp'
  7560. ActionMailer::Base.class_eval do
  7561. include ActionMailer::Quoting
  7562. include ActionMailer::Helpers
  7563. helper MailHelper
  7564. end
  7565. silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }module TestHelper
  7566. def test_format(text)
  7567. "<em><strong><small>#{text}</small></strong></em>"
  7568. end
  7569. end
  7570. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  7571. $:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
  7572. require 'test/unit'
  7573. require 'action_mailer'
  7574. module MailerHelper
  7575. def person_name
  7576. "Mr. Joe Person"
  7577. end
  7578. end
  7579. class HelperMailer < ActionMailer::Base
  7580. helper MailerHelper
  7581. helper :test
  7582. def use_helper(recipient)
  7583. recipients recipient
  7584. subject "using helpers"
  7585. from "tester@example.com"
  7586. end
  7587. def use_test_helper(recipient)
  7588. recipients recipient
  7589. subject "using helpers"
  7590. from "tester@example.com"
  7591. self.body = { :text => "emphasize me!" }
  7592. end
  7593. def use_mail_helper(recipient)
  7594. recipients recipient
  7595. subject "using mailing helpers"
  7596. from "tester@example.com"
  7597. self.body = { :text =>
  7598. "But soft! What light through yonder window breaks? It is the east, " +
  7599. "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
  7600. "which is sick and pale with grief that thou, her maid, art far more " +
  7601. "fair than she. Be not her maid, for she is envious! Her vestal " +
  7602. "livery is but sick and green, and none but fools do wear it. Cast " +
  7603. "it off!"
  7604. }
  7605. end
  7606. def use_helper_method(recipient)
  7607. recipients recipient
  7608. subject "using helpers"
  7609. from "tester@example.com"
  7610. self.body = { :text => "emphasize me!" }
  7611. end
  7612. private
  7613. def name_of_the_mailer_class
  7614. self.class.name
  7615. end
  7616. helper_method :name_of_the_mailer_class
  7617. end
  7618. HelperMailer.template_root = File.dirname(__FILE__) + "/fixtures"
  7619. class MailerHelperTest < Test::Unit::TestCase
  7620. def new_mail( charset="utf-8" )
  7621. mail = TMail::Mail.new
  7622. mail.set_content_type "text", "plain", { "charset" => charset } if charset
  7623. mail
  7624. end
  7625. def setup
  7626. ActionMailer::Base.delivery_method = :test
  7627. ActionMailer::Base.perform_deliveries = true
  7628. ActionMailer::Base.deliveries = []
  7629. @recipient = 'test@localhost'
  7630. end
  7631. def test_use_helper
  7632. mail = HelperMailer.create_use_helper(@recipient)
  7633. assert_match %r{Mr. Joe Person}, mail.encoded
  7634. end
  7635. def test_use_test_helper
  7636. mail = HelperMailer.create_use_test_helper(@recipient)
  7637. assert_match %r{<em><strong><small>emphasize me!}, mail.encoded
  7638. end
  7639. def test_use_helper_method
  7640. mail = HelperMailer.create_use_helper_method(@recipient)
  7641. assert_match %r{HelperMailer}, mail.encoded
  7642. end
  7643. def test_use_mail_helper
  7644. mail = HelperMailer.create_use_mail_helper(@recipient)
  7645. assert_match %r{ But soft!}, mail.encoded
  7646. assert_match %r{east, and\n Juliet}, mail.encoded
  7647. end
  7648. end
  7649. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  7650. require 'test/unit'
  7651. require 'action_mailer'
  7652. class RenderMailer < ActionMailer::Base
  7653. def inline_template(recipient)
  7654. recipients recipient
  7655. subject "using helpers"
  7656. from "tester@example.com"
  7657. body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
  7658. end
  7659. def file_template(recipient)
  7660. recipients recipient
  7661. subject "using helpers"
  7662. from "tester@example.com"
  7663. body render(:file => "signed_up", :body => { :recipient => recipient })
  7664. end
  7665. def initialize_defaults(method_name)
  7666. super
  7667. mailer_name "test_mailer"
  7668. end
  7669. end
  7670. RenderMailer.template_root = File.dirname(__FILE__) + "/fixtures"
  7671. class RenderHelperTest < Test::Unit::TestCase
  7672. def setup
  7673. ActionMailer::Base.delivery_method = :test
  7674. ActionMailer::Base.perform_deliveries = true
  7675. ActionMailer::Base.deliveries = []
  7676. @recipient = 'test@localhost'
  7677. end
  7678. def test_inline_template
  7679. mail = RenderMailer.create_inline_template(@recipient)
  7680. assert_equal "Hello, Earth", mail.body.strip
  7681. end
  7682. def test_file_template
  7683. mail = RenderMailer.create_file_template(@recipient)
  7684. assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
  7685. end
  7686. end
  7687. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  7688. require 'test/unit'
  7689. require 'action_mailer'
  7690. class MockSMTP
  7691. def self.deliveries
  7692. @@deliveries
  7693. end
  7694. def initialize
  7695. @@deliveries = []
  7696. end
  7697. def sendmail(mail, from, to)
  7698. @@deliveries << [mail, from, to]
  7699. end
  7700. end
  7701. class Net::SMTP
  7702. def self.start(*args)
  7703. yield MockSMTP.new
  7704. end
  7705. end
  7706. class FunkyPathMailer < ActionMailer::Base
  7707. self.template_root = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  7708. def multipart_with_template_path_with_dots(recipient)
  7709. recipients recipient
  7710. subject "Have a lovely picture"
  7711. from "Chad Fowler <chad@chadfowler.com>"
  7712. attachment :content_type => "image/jpeg",
  7713. :body => "not really a jpeg, we're only testing, after all"
  7714. end
  7715. def template_path
  7716. "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  7717. end
  7718. end
  7719. class TestMailer < ActionMailer::Base
  7720. def signed_up(recipient)
  7721. @recipients = recipient
  7722. @subject = "[Signed up] Welcome #{recipient}"
  7723. @from = "system@loudthinking.com"
  7724. @sent_on = Time.local(2004, 12, 12)
  7725. @body["recipient"] = recipient
  7726. end
  7727. def cancelled_account(recipient)
  7728. self.recipients = recipient
  7729. self.subject = "[Cancelled] Goodbye #{recipient}"
  7730. self.from = "system@loudthinking.com"
  7731. self.sent_on = Time.local(2004, 12, 12)
  7732. self.body = "Goodbye, Mr. #{recipient}"
  7733. end
  7734. def cc_bcc(recipient)
  7735. recipients recipient
  7736. subject "testing bcc/cc"
  7737. from "system@loudthinking.com"
  7738. sent_on Time.local(2004, 12, 12)
  7739. cc "nobody@loudthinking.com"
  7740. bcc "root@loudthinking.com"
  7741. body "Nothing to see here."
  7742. end
  7743. def iso_charset(recipient)
  7744. @recipients = recipient
  7745. @subject = "testing isø charsets"
  7746. @from = "system@loudthinking.com"
  7747. @sent_on = Time.local 2004, 12, 12
  7748. @cc = "nobody@loudthinking.com"
  7749. @bcc = "root@loudthinking.com"
  7750. @body = "Nothing to see here."
  7751. @charset = "iso-8859-1"
  7752. end
  7753. def unencoded_subject(recipient)
  7754. @recipients = recipient
  7755. @subject = "testing unencoded subject"
  7756. @from = "system@loudthinking.com"
  7757. @sent_on = Time.local 2004, 12, 12
  7758. @cc = "nobody@loudthinking.com"
  7759. @bcc = "root@loudthinking.com"
  7760. @body = "Nothing to see here."
  7761. end
  7762. def extended_headers(recipient)
  7763. @recipients = recipient
  7764. @subject = "testing extended headers"
  7765. @from = "Grytøyr <stian1@example.net>"
  7766. @sent_on = Time.local 2004, 12, 12
  7767. @cc = "Grytøyr <stian2@example.net>"
  7768. @bcc = "Grytøyr <stian3@example.net>"
  7769. @body = "Nothing to see here."
  7770. @charset = "iso-8859-1"
  7771. end
  7772. def utf8_body(recipient)
  7773. @recipients = recipient
  7774. @subject = "testing utf-8 body"
  7775. @from = "Foo áëô îü <extended@example.net>"
  7776. @sent_on = Time.local 2004, 12, 12
  7777. @cc = "Foo áëô îü <extended@example.net>"
  7778. @bcc = "Foo áëô îü <extended@example.net>"
  7779. @body = "åœö blah"
  7780. @charset = "utf-8"
  7781. end
  7782. def multipart_with_mime_version(recipient)
  7783. recipients recipient
  7784. subject "multipart with mime_version"
  7785. from "test@example.com"
  7786. sent_on Time.local(2004, 12, 12)
  7787. mime_version "1.1"
  7788. content_type "multipart/alternative"
  7789. part "text/plain" do |p|
  7790. p.body = "blah"
  7791. end
  7792. part "text/html" do |p|
  7793. p.body = "<b>blah</b>"
  7794. end
  7795. end
  7796. def multipart_with_utf8_subject(recipient)
  7797. recipients recipient
  7798. subject "Foo áëô îü"
  7799. from "test@example.com"
  7800. charset "utf-8"
  7801. part "text/plain" do |p|
  7802. p.body = "blah"
  7803. end
  7804. part "text/html" do |p|
  7805. p.body = "<b>blah</b>"
  7806. end
  7807. end
  7808. def explicitly_multipart_example(recipient, ct=nil)
  7809. recipients recipient
  7810. subject "multipart example"
  7811. from "test@example.com"
  7812. sent_on Time.local(2004, 12, 12)
  7813. body "plain text default"
  7814. content_type ct if ct
  7815. part "text/html" do |p|
  7816. p.charset = "iso-8859-1"
  7817. p.body = "blah"
  7818. end
  7819. attachment :content_type => "image/jpeg", :filename => "foo.jpg",
  7820. :body => "123456789"
  7821. end
  7822. def implicitly_multipart_example(recipient, cs = nil, order = nil)
  7823. @recipients = recipient
  7824. @subject = "multipart example"
  7825. @from = "test@example.com"
  7826. @sent_on = Time.local 2004, 12, 12
  7827. @body = { "recipient" => recipient }
  7828. @charset = cs if cs
  7829. @implicit_parts_order = order if order
  7830. end
  7831. def implicitly_multipart_with_utf8
  7832. recipients "no.one@nowhere.test"
  7833. subject "Foo áëô îü"
  7834. from "some.one@somewhere.test"
  7835. template "implicitly_multipart_example"
  7836. body ({ "recipient" => "no.one@nowhere.test" })
  7837. end
  7838. def html_mail(recipient)
  7839. recipients recipient
  7840. subject "html mail"
  7841. from "test@example.com"
  7842. body "<em>Emphasize</em> <strong>this</strong>"
  7843. content_type "text/html"
  7844. end
  7845. def html_mail_with_underscores(recipient)
  7846. subject "html mail with underscores"
  7847. body %{<a href="http://google.com" target="_blank">_Google</a>}
  7848. end
  7849. def custom_template(recipient)
  7850. recipients recipient
  7851. subject "[Signed up] Welcome #{recipient}"
  7852. from "system@loudthinking.com"
  7853. sent_on Time.local(2004, 12, 12)
  7854. template "signed_up"
  7855. body["recipient"] = recipient
  7856. end
  7857. def various_newlines(recipient)
  7858. recipients recipient
  7859. subject "various newlines"
  7860. from "test@example.com"
  7861. body "line #1\nline #2\rline #3\r\nline #4\r\r" +
  7862. "line #5\n\nline#6\r\n\r\nline #7"
  7863. end
  7864. def various_newlines_multipart(recipient)
  7865. recipients recipient
  7866. subject "various newlines multipart"
  7867. from "test@example.com"
  7868. content_type "multipart/alternative"
  7869. part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r"
  7870. part :content_type => "text/html", :body => "<p>line #1</p>\n<p>line #2</p>\r<p>line #3</p>\r\n<p>line #4</p>\r\r"
  7871. end
  7872. def nested_multipart(recipient)
  7873. recipients recipient
  7874. subject "nested multipart"
  7875. from "test@example.com"
  7876. content_type "multipart/mixed"
  7877. part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
  7878. p.part :content_type => "text/plain", :body => "test text\nline #2"
  7879. p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
  7880. end
  7881. attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
  7882. end
  7883. def attachment_with_custom_header(recipient)
  7884. recipients recipient
  7885. subject "custom header in attachment"
  7886. from "test@example.com"
  7887. content_type "multipart/related"
  7888. part :content_type => "text/html", :body => 'yo'
  7889. attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '<test@test.com>' }
  7890. end
  7891. def unnamed_attachment(recipient)
  7892. recipients recipient
  7893. subject "nested multipart"
  7894. from "test@example.com"
  7895. content_type "multipart/mixed"
  7896. part :content_type => "text/plain", :body => "hullo"
  7897. attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
  7898. end
  7899. def headers_with_nonalpha_chars(recipient)
  7900. recipients recipient
  7901. subject "nonalpha chars"
  7902. from "One: Two <test@example.com>"
  7903. cc "Three: Four <test@example.com>"
  7904. bcc "Five: Six <test@example.com>"
  7905. body "testing"
  7906. end
  7907. def custom_content_type_attributes
  7908. recipients "no.one@nowhere.test"
  7909. subject "custom content types"
  7910. from "some.one@somewhere.test"
  7911. content_type "text/plain; format=flowed"
  7912. body "testing"
  7913. end
  7914. class <<self
  7915. attr_accessor :received_body
  7916. end
  7917. def receive(mail)
  7918. self.class.received_body = mail.body
  7919. end
  7920. end
  7921. TestMailer.template_root = File.dirname(__FILE__) + "/fixtures"
  7922. class ActionMailerTest < Test::Unit::TestCase
  7923. include ActionMailer::Quoting
  7924. def encode( text, charset="utf-8" )
  7925. quoted_printable( text, charset )
  7926. end
  7927. def new_mail( charset="utf-8" )
  7928. mail = TMail::Mail.new
  7929. if charset
  7930. mail.set_content_type "text", "plain", { "charset" => charset }
  7931. end
  7932. mail
  7933. end
  7934. def setup
  7935. ActionMailer::Base.delivery_method = :test
  7936. ActionMailer::Base.perform_deliveries = true
  7937. ActionMailer::Base.deliveries = []
  7938. @recipient = 'test@localhost'
  7939. end
  7940. def test_nested_parts
  7941. created = nil
  7942. assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
  7943. assert_equal 2,created.parts.size
  7944. assert_equal 2,created.parts.first.parts.size
  7945. assert_equal "multipart/mixed", created.content_type
  7946. assert_equal "multipart/alternative", created.parts.first.content_type
  7947. assert_equal "text/plain", created.parts.first.parts.first.content_type
  7948. assert_equal "text/html", created.parts.first.parts[1].content_type
  7949. assert_equal "application/octet-stream", created.parts[1].content_type
  7950. end
  7951. def test_attachment_with_custom_header
  7952. created = nil
  7953. assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)}
  7954. assert_equal "<test@test.com>", created.parts[1].header['content-id'].to_s
  7955. end
  7956. def test_signed_up
  7957. expected = new_mail
  7958. expected.to = @recipient
  7959. expected.subject = "[Signed up] Welcome #{@recipient}"
  7960. expected.body = "Hello there, \n\nMr. #{@recipient}"
  7961. expected.from = "system@loudthinking.com"
  7962. expected.date = Time.local(2004, 12, 12)
  7963. expected.mime_version = nil
  7964. created = nil
  7965. assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
  7966. assert_not_nil created
  7967. assert_equal expected.encoded, created.encoded
  7968. assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
  7969. assert_not_nil ActionMailer::Base.deliveries.first
  7970. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  7971. end
  7972. def test_custom_template
  7973. expected = new_mail
  7974. expected.to = @recipient
  7975. expected.subject = "[Signed up] Welcome #{@recipient}"
  7976. expected.body = "Hello there, \n\nMr. #{@recipient}"
  7977. expected.from = "system@loudthinking.com"
  7978. expected.date = Time.local(2004, 12, 12)
  7979. created = nil
  7980. assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
  7981. assert_not_nil created
  7982. assert_equal expected.encoded, created.encoded
  7983. end
  7984. def test_cancelled_account
  7985. expected = new_mail
  7986. expected.to = @recipient
  7987. expected.subject = "[Cancelled] Goodbye #{@recipient}"
  7988. expected.body = "Goodbye, Mr. #{@recipient}"
  7989. expected.from = "system@loudthinking.com"
  7990. expected.date = Time.local(2004, 12, 12)
  7991. created = nil
  7992. assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
  7993. assert_not_nil created
  7994. assert_equal expected.encoded, created.encoded
  7995. assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
  7996. assert_not_nil ActionMailer::Base.deliveries.first
  7997. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  7998. end
  7999. def test_cc_bcc
  8000. expected = new_mail
  8001. expected.to = @recipient
  8002. expected.subject = "testing bcc/cc"
  8003. expected.body = "Nothing to see here."
  8004. expected.from = "system@loudthinking.com"
  8005. expected.cc = "nobody@loudthinking.com"
  8006. expected.bcc = "root@loudthinking.com"
  8007. expected.date = Time.local 2004, 12, 12
  8008. created = nil
  8009. assert_nothing_raised do
  8010. created = TestMailer.create_cc_bcc @recipient
  8011. end
  8012. assert_not_nil created
  8013. assert_equal expected.encoded, created.encoded
  8014. assert_nothing_raised do
  8015. TestMailer.deliver_cc_bcc @recipient
  8016. end
  8017. assert_not_nil ActionMailer::Base.deliveries.first
  8018. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8019. end
  8020. def test_iso_charset
  8021. expected = new_mail( "iso-8859-1" )
  8022. expected.to = @recipient
  8023. expected.subject = encode "testing isø charsets", "iso-8859-1"
  8024. expected.body = "Nothing to see here."
  8025. expected.from = "system@loudthinking.com"
  8026. expected.cc = "nobody@loudthinking.com"
  8027. expected.bcc = "root@loudthinking.com"
  8028. expected.date = Time.local 2004, 12, 12
  8029. created = nil
  8030. assert_nothing_raised do
  8031. created = TestMailer.create_iso_charset @recipient
  8032. end
  8033. assert_not_nil created
  8034. assert_equal expected.encoded, created.encoded
  8035. assert_nothing_raised do
  8036. TestMailer.deliver_iso_charset @recipient
  8037. end
  8038. assert_not_nil ActionMailer::Base.deliveries.first
  8039. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8040. end
  8041. def test_unencoded_subject
  8042. expected = new_mail
  8043. expected.to = @recipient
  8044. expected.subject = "testing unencoded subject"
  8045. expected.body = "Nothing to see here."
  8046. expected.from = "system@loudthinking.com"
  8047. expected.cc = "nobody@loudthinking.com"
  8048. expected.bcc = "root@loudthinking.com"
  8049. expected.date = Time.local 2004, 12, 12
  8050. created = nil
  8051. assert_nothing_raised do
  8052. created = TestMailer.create_unencoded_subject @recipient
  8053. end
  8054. assert_not_nil created
  8055. assert_equal expected.encoded, created.encoded
  8056. assert_nothing_raised do
  8057. TestMailer.deliver_unencoded_subject @recipient
  8058. end
  8059. assert_not_nil ActionMailer::Base.deliveries.first
  8060. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8061. end
  8062. def test_instances_are_nil
  8063. assert_nil ActionMailer::Base.new
  8064. assert_nil TestMailer.new
  8065. end
  8066. def test_deliveries_array
  8067. assert_not_nil ActionMailer::Base.deliveries
  8068. assert_equal 0, ActionMailer::Base.deliveries.size
  8069. TestMailer.deliver_signed_up(@recipient)
  8070. assert_equal 1, ActionMailer::Base.deliveries.size
  8071. assert_not_nil ActionMailer::Base.deliveries.first
  8072. end
  8073. def test_perform_deliveries_flag
  8074. ActionMailer::Base.perform_deliveries = false
  8075. TestMailer.deliver_signed_up(@recipient)
  8076. assert_equal 0, ActionMailer::Base.deliveries.size
  8077. ActionMailer::Base.perform_deliveries = true
  8078. TestMailer.deliver_signed_up(@recipient)
  8079. assert_equal 1, ActionMailer::Base.deliveries.size
  8080. end
  8081. def test_unquote_quoted_printable_subject
  8082. msg = <<EOF
  8083. From: me@example.com
  8084. Subject: =?utf-8?Q?testing_testing_=D6=A4?=
  8085. Content-Type: text/plain; charset=iso-8859-1
  8086. The body
  8087. EOF
  8088. mail = TMail::Mail.parse(msg)
  8089. assert_equal "testing testing \326\244", mail.subject
  8090. assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
  8091. end
  8092. def test_unquote_7bit_subject
  8093. msg = <<EOF
  8094. From: me@example.com
  8095. Subject: this == working?
  8096. Content-Type: text/plain; charset=iso-8859-1
  8097. The body
  8098. EOF
  8099. mail = TMail::Mail.parse(msg)
  8100. assert_equal "this == working?", mail.subject
  8101. assert_equal "this == working?", mail.quoted_subject
  8102. end
  8103. def test_unquote_7bit_body
  8104. msg = <<EOF
  8105. From: me@example.com
  8106. Subject: subject
  8107. Content-Type: text/plain; charset=iso-8859-1
  8108. Content-Transfer-Encoding: 7bit
  8109. The=3Dbody
  8110. EOF
  8111. mail = TMail::Mail.parse(msg)
  8112. assert_equal "The=3Dbody", mail.body.strip
  8113. assert_equal "The=3Dbody", mail.quoted_body.strip
  8114. end
  8115. def test_unquote_quoted_printable_body
  8116. msg = <<EOF
  8117. From: me@example.com
  8118. Subject: subject
  8119. Content-Type: text/plain; charset=iso-8859-1
  8120. Content-Transfer-Encoding: quoted-printable
  8121. The=3Dbody
  8122. EOF
  8123. mail = TMail::Mail.parse(msg)
  8124. assert_equal "The=body", mail.body.strip
  8125. assert_equal "The=3Dbody", mail.quoted_body.strip
  8126. end
  8127. def test_unquote_base64_body
  8128. msg = <<EOF
  8129. From: me@example.com
  8130. Subject: subject
  8131. Content-Type: text/plain; charset=iso-8859-1
  8132. Content-Transfer-Encoding: base64
  8133. VGhlIGJvZHk=
  8134. EOF
  8135. mail = TMail::Mail.parse(msg)
  8136. assert_equal "The body", mail.body.strip
  8137. assert_equal "VGhlIGJvZHk=", mail.quoted_body.strip
  8138. end
  8139. def test_extended_headers
  8140. @recipient = "Grytøyr <test@localhost>"
  8141. expected = new_mail "iso-8859-1"
  8142. expected.to = quote_address_if_necessary @recipient, "iso-8859-1"
  8143. expected.subject = "testing extended headers"
  8144. expected.body = "Nothing to see here."
  8145. expected.from = quote_address_if_necessary "Grytøyr <stian1@example.net>", "iso-8859-1"
  8146. expected.cc = quote_address_if_necessary "Grytøyr <stian2@example.net>", "iso-8859-1"
  8147. expected.bcc = quote_address_if_necessary "Grytøyr <stian3@example.net>", "iso-8859-1"
  8148. expected.date = Time.local 2004, 12, 12
  8149. created = nil
  8150. assert_nothing_raised do
  8151. created = TestMailer.create_extended_headers @recipient
  8152. end
  8153. assert_not_nil created
  8154. assert_equal expected.encoded, created.encoded
  8155. assert_nothing_raised do
  8156. TestMailer.deliver_extended_headers @recipient
  8157. end
  8158. assert_not_nil ActionMailer::Base.deliveries.first
  8159. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8160. end
  8161. def test_utf8_body_is_not_quoted
  8162. @recipient = "Foo áëô îü <extended@example.net>"
  8163. expected = new_mail "utf-8"
  8164. expected.to = quote_address_if_necessary @recipient, "utf-8"
  8165. expected.subject = "testing utf-8 body"
  8166. expected.body = "åœö blah"
  8167. expected.from = quote_address_if_necessary @recipient, "utf-8"
  8168. expected.cc = quote_address_if_necessary @recipient, "utf-8"
  8169. expected.bcc = quote_address_if_necessary @recipient, "utf-8"
  8170. expected.date = Time.local 2004, 12, 12
  8171. created = TestMailer.create_utf8_body @recipient
  8172. assert_match(/åœö blah/, created.encoded)
  8173. end
  8174. def test_multiple_utf8_recipients
  8175. @recipient = ["\"Foo áëô îü\" <extended@example.net>", "\"Example Recipient\" <me@example.com>"]
  8176. expected = new_mail "utf-8"
  8177. expected.to = quote_address_if_necessary @recipient, "utf-8"
  8178. expected.subject = "testing utf-8 body"
  8179. expected.body = "åœö blah"
  8180. expected.from = quote_address_if_necessary @recipient.first, "utf-8"
  8181. expected.cc = quote_address_if_necessary @recipient, "utf-8"
  8182. expected.bcc = quote_address_if_necessary @recipient, "utf-8"
  8183. expected.date = Time.local 2004, 12, 12
  8184. created = TestMailer.create_utf8_body @recipient
  8185. assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>\r/, created.encoded)
  8186. assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, Example Recipient <me/, created.encoded)
  8187. end
  8188. def test_receive_decodes_base64_encoded_mail
  8189. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
  8190. TestMailer.receive(fixture)
  8191. assert_match(/Jamis/, TestMailer.received_body)
  8192. end
  8193. def test_receive_attachments
  8194. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email2")
  8195. mail = TMail::Mail.parse(fixture)
  8196. attachment = mail.attachments.last
  8197. assert_equal "smime.p7s", attachment.original_filename
  8198. assert_equal "application/pkcs7-signature", attachment.content_type
  8199. end
  8200. def test_decode_attachment_without_charset
  8201. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email3")
  8202. mail = TMail::Mail.parse(fixture)
  8203. attachment = mail.attachments.last
  8204. assert_equal 1026, attachment.read.length
  8205. end
  8206. def test_attachment_using_content_location
  8207. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email12")
  8208. mail = TMail::Mail.parse(fixture)
  8209. assert_equal 1, mail.attachments.length
  8210. assert_equal "Photo25.jpg", mail.attachments.first.original_filename
  8211. end
  8212. def test_attachment_with_text_type
  8213. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email13")
  8214. mail = TMail::Mail.parse(fixture)
  8215. assert mail.has_attachments?
  8216. assert_equal 1, mail.attachments.length
  8217. assert_equal "hello.rb", mail.attachments.first.original_filename
  8218. end
  8219. def test_decode_part_without_content_type
  8220. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email4")
  8221. mail = TMail::Mail.parse(fixture)
  8222. assert_nothing_raised { mail.body }
  8223. end
  8224. def test_decode_message_without_content_type
  8225. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email5")
  8226. mail = TMail::Mail.parse(fixture)
  8227. assert_nothing_raised { mail.body }
  8228. end
  8229. def test_decode_message_with_incorrect_charset
  8230. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email6")
  8231. mail = TMail::Mail.parse(fixture)
  8232. assert_nothing_raised { mail.body }
  8233. end
  8234. def test_multipart_with_mime_version
  8235. mail = TestMailer.create_multipart_with_mime_version(@recipient)
  8236. assert_equal "1.1", mail.mime_version
  8237. end
  8238. def test_multipart_with_utf8_subject
  8239. mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
  8240. assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
  8241. end
  8242. def test_implicitly_multipart_with_utf8
  8243. mail = TestMailer.create_implicitly_multipart_with_utf8
  8244. assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
  8245. end
  8246. def test_explicitly_multipart_messages
  8247. mail = TestMailer.create_explicitly_multipart_example(@recipient)
  8248. assert_equal 3, mail.parts.length
  8249. assert_nil mail.content_type
  8250. assert_equal "text/plain", mail.parts[0].content_type
  8251. assert_equal "text/html", mail.parts[1].content_type
  8252. assert_equal "iso-8859-1", mail.parts[1].sub_header("content-type", "charset")
  8253. assert_equal "inline", mail.parts[1].content_disposition
  8254. assert_equal "image/jpeg", mail.parts[2].content_type
  8255. assert_equal "attachment", mail.parts[2].content_disposition
  8256. assert_equal "foo.jpg", mail.parts[2].sub_header("content-disposition", "filename")
  8257. assert_equal "foo.jpg", mail.parts[2].sub_header("content-type", "name")
  8258. assert_nil mail.parts[2].sub_header("content-type", "charset")
  8259. end
  8260. def test_explicitly_multipart_with_content_type
  8261. mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
  8262. assert_equal 3, mail.parts.length
  8263. assert_equal "multipart/alternative", mail.content_type
  8264. end
  8265. def test_explicitly_multipart_with_invalid_content_type
  8266. mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
  8267. assert_equal 3, mail.parts.length
  8268. assert_nil mail.content_type
  8269. end
  8270. def test_implicitly_multipart_messages
  8271. mail = TestMailer.create_implicitly_multipart_example(@recipient)
  8272. assert_equal 3, mail.parts.length
  8273. assert_equal "1.0", mail.mime_version
  8274. assert_equal "multipart/alternative", mail.content_type
  8275. assert_equal "text/yaml", mail.parts[0].content_type
  8276. assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
  8277. assert_equal "text/plain", mail.parts[1].content_type
  8278. assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset")
  8279. assert_equal "text/html", mail.parts[2].content_type
  8280. assert_equal "utf-8", mail.parts[2].sub_header("content-type", "charset")
  8281. end
  8282. def test_implicitly_multipart_messages_with_custom_order
  8283. mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
  8284. assert_equal 3, mail.parts.length
  8285. assert_equal "text/html", mail.parts[0].content_type
  8286. assert_equal "text/plain", mail.parts[1].content_type
  8287. assert_equal "text/yaml", mail.parts[2].content_type
  8288. end
  8289. def test_implicitly_multipart_messages_with_charset
  8290. mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
  8291. assert_equal "multipart/alternative", mail.header['content-type'].body
  8292. assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
  8293. assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
  8294. assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
  8295. end
  8296. def test_html_mail
  8297. mail = TestMailer.create_html_mail(@recipient)
  8298. assert_equal "text/html", mail.content_type
  8299. end
  8300. def test_html_mail_with_underscores
  8301. mail = TestMailer.create_html_mail_with_underscores(@recipient)
  8302. assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body
  8303. end
  8304. def test_various_newlines
  8305. mail = TestMailer.create_various_newlines(@recipient)
  8306. assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
  8307. "line #5\n\nline#6\n\nline #7", mail.body)
  8308. end
  8309. def test_various_newlines_multipart
  8310. mail = TestMailer.create_various_newlines_multipart(@recipient)
  8311. assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
  8312. assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
  8313. end
  8314. def test_headers_removed_on_smtp_delivery
  8315. ActionMailer::Base.delivery_method = :smtp
  8316. TestMailer.deliver_cc_bcc(@recipient)
  8317. assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
  8318. assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
  8319. assert MockSMTP.deliveries[0][2].include?(@recipient)
  8320. assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
  8321. assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
  8322. assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
  8323. end
  8324. def test_recursive_multipart_processing
  8325. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
  8326. mail = TMail::Mail.parse(fixture)
  8327. assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
  8328. end
  8329. def test_decode_encoded_attachment_filename
  8330. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
  8331. mail = TMail::Mail.parse(fixture)
  8332. attachment = mail.attachments.last
  8333. assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename
  8334. end
  8335. def test_wrong_mail_header
  8336. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
  8337. assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
  8338. end
  8339. def test_decode_message_with_unknown_charset
  8340. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
  8341. mail = TMail::Mail.parse(fixture)
  8342. assert_nothing_raised { mail.body }
  8343. end
  8344. def test_decode_message_with_unquoted_atchar_in_header
  8345. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11")
  8346. mail = TMail::Mail.parse(fixture)
  8347. assert_not_nil mail.from
  8348. end
  8349. def test_empty_header_values_omitted
  8350. result = TestMailer.create_unnamed_attachment(@recipient).encoded
  8351. assert_match %r{Content-Type: application/octet-stream[^;]}, result
  8352. assert_match %r{Content-Disposition: attachment[^;]}, result
  8353. end
  8354. def test_headers_with_nonalpha_chars
  8355. mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
  8356. assert !mail.from_addrs.empty?
  8357. assert !mail.cc_addrs.empty?
  8358. assert !mail.bcc_addrs.empty?
  8359. assert_match(/:/, mail.from_addrs.to_s)
  8360. assert_match(/:/, mail.cc_addrs.to_s)
  8361. assert_match(/:/, mail.bcc_addrs.to_s)
  8362. end
  8363. def test_deliver_with_mail_object
  8364. mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
  8365. assert_nothing_raised { TestMailer.deliver(mail) }
  8366. assert_equal 1, TestMailer.deliveries.length
  8367. end
  8368. def test_multipart_with_template_path_with_dots
  8369. mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
  8370. assert_equal 2, mail.parts.length
  8371. end
  8372. def test_custom_content_type_attributes
  8373. mail = TestMailer.create_custom_content_type_attributes
  8374. assert_match %r{format=flowed}, mail['content-type'].to_s
  8375. assert_match %r{charset=utf-8}, mail['content-type'].to_s
  8376. end
  8377. end
  8378. class InheritableTemplateRootTest < Test::Unit::TestCase
  8379. def test_attr
  8380. expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  8381. assert_equal expected, FunkyPathMailer.template_root
  8382. sub = Class.new(FunkyPathMailer)
  8383. sub.template_root = 'test/path'
  8384. assert_equal 'test/path', sub.template_root
  8385. assert_equal expected, FunkyPathMailer.template_root
  8386. end
  8387. end
  8388. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  8389. $:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor")
  8390. require 'test/unit'
  8391. require 'tmail'
  8392. require 'tempfile'
  8393. class QuotingTest < Test::Unit::TestCase
  8394. def test_quote_multibyte_chars
  8395. original = "\303\246 \303\270 and \303\245"
  8396. result = execute_in_sandbox(<<-CODE)
  8397. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  8398. $KCODE = 'u'
  8399. require 'jcode'
  8400. require 'action_mailer/quoting'
  8401. include ActionMailer::Quoting
  8402. quoted_printable(#{original.inspect}, "UTF-8")
  8403. CODE
  8404. unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
  8405. assert_equal unquoted, original
  8406. end
  8407. private
  8408. # This whole thing *could* be much simpler, but I don't think Tempfile,
  8409. # popen and others exist on all platforms (like Windows).
  8410. def execute_in_sandbox(code)
  8411. test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
  8412. res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
  8413. File.open(test_name, "w+") do |file|
  8414. file.write(<<-CODE)
  8415. block = Proc.new do
  8416. #{code}
  8417. end
  8418. puts block.call
  8419. CODE
  8420. end
  8421. system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
  8422. File.read(res_name)
  8423. ensure
  8424. File.delete(test_name) rescue nil
  8425. File.delete(res_name) rescue nil
  8426. end
  8427. end
  8428. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  8429. $:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
  8430. require 'test/unit'
  8431. require 'action_mailer'
  8432. class TMailMailTest < Test::Unit::TestCase
  8433. def test_body
  8434. m = TMail::Mail.new
  8435. expected = 'something_with_underscores'
  8436. m.encoding = 'quoted-printable'
  8437. quoted_body = [expected].pack('*M')
  8438. m.body = quoted_body
  8439. assert_equal "something_with_underscores=\n", m.quoted_body
  8440. assert_equal expected, m.body
  8441. end
  8442. end
  8443. $:.unshift(File.dirname(__FILE__) + "/../lib")
  8444. require "action_controller"
  8445. require "action_controller/test_process"
  8446. Person = Struct.new("Person", :id, :name, :email_address, :phone_number)
  8447. class AddressBookService
  8448. attr_reader :people
  8449. def initialize() @people = [] end
  8450. def create_person(data) people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end
  8451. def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end
  8452. def next_person_id() people.first.id + 1 end
  8453. end
  8454. class AddressBookController < ActionController::Base
  8455. layout "address_book/layout"
  8456. before_filter :initialize_session_storage
  8457. # Could also have used a proc
  8458. # before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) }
  8459. def index
  8460. @title = "Address Book"
  8461. @people = @address_book.people
  8462. end
  8463. def person
  8464. @person = @address_book.find_person(@params["id"])
  8465. end
  8466. def create_person
  8467. @address_book.create_person(@params["person"])
  8468. redirect_to :action => "index"
  8469. end
  8470. private
  8471. def initialize_session_storage
  8472. @address_book = @session["address_book"] ||= AddressBookService.new
  8473. end
  8474. end
  8475. ActionController::Base.template_root = File.dirname(__FILE__)
  8476. # ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
  8477. begin
  8478. AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
  8479. rescue => e
  8480. CGI.new.out { "#{e.class}: #{e.message}" }
  8481. end$:.unshift(File.dirname(__FILE__) + "/../lib")
  8482. require "action_controller"
  8483. require 'action_controller/test_process'
  8484. Person = Struct.new("Person", :name, :address, :age)
  8485. class BenchmarkController < ActionController::Base
  8486. def message
  8487. render_text "hello world"
  8488. end
  8489. def list
  8490. @people = [ Person.new("David"), Person.new("Mary") ]
  8491. render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>"
  8492. end
  8493. def form_helper
  8494. @person = Person.new "david", "hyacintvej", 24
  8495. render_template(
  8496. "<% person = Person.new 'Mary', 'hyacintvej', 22 %> " +
  8497. "change the name <%= text_field 'person', 'name' %> and <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>"
  8498. )
  8499. end
  8500. end
  8501. #ActionController::Base.template_root = File.dirname(__FILE__)
  8502. require "benchmark"
  8503. RUNS = ARGV[0] ? ARGV[0].to_i : 50
  8504. require "profile" if ARGV[1]
  8505. runtime = Benchmark.measure {
  8506. RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) }
  8507. }
  8508. puts "List: #{RUNS / runtime.real}"
  8509. runtime = Benchmark.measure {
  8510. RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) }
  8511. }
  8512. puts "Message: #{RUNS / runtime.real}"
  8513. runtime = Benchmark.measure {
  8514. RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) }
  8515. }
  8516. puts "Form helper: #{RUNS / runtime.real}"
  8517. require 'rbconfig'
  8518. require 'find'
  8519. require 'ftools'
  8520. include Config
  8521. # this was adapted from rdoc's install.rb by ways of Log4r
  8522. $sitedir = CONFIG["sitelibdir"]
  8523. unless $sitedir
  8524. version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  8525. $libdir = File.join(CONFIG["libdir"], "ruby", version)
  8526. $sitedir = $:.find {|x| x =~ /site_ruby/ }
  8527. if !$sitedir
  8528. $sitedir = File.join($libdir, "site_ruby")
  8529. elsif $sitedir !~ Regexp.quote(version)
  8530. $sitedir = File.join($sitedir, version)
  8531. end
  8532. end
  8533. # the acual gruntwork
  8534. Dir.chdir("lib")
  8535. Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f|
  8536. if f[-3..-1] == ".rb"
  8537. File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  8538. else
  8539. File::makedirs(File.join($sitedir, *f.split(/\//)))
  8540. end
  8541. }require 'test/unit'
  8542. require 'test/unit/assertions'
  8543. require 'rexml/document'
  8544. require File.dirname(__FILE__) + "/vendor/html-scanner/html/document"
  8545. module Test #:nodoc:
  8546. module Unit #:nodoc:
  8547. # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
  8548. # can be used against. These collections are:
  8549. #
  8550. # * assigns: Instance variables assigned in the action that are available for the view.
  8551. # * session: Objects being saved in the session.
  8552. # * flash: The flash objects currently in the session.
  8553. # * cookies: Cookies being sent to the user on this request.
  8554. #
  8555. # These collections can be used just like any other hash:
  8556. #
  8557. # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
  8558. # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
  8559. # assert flash.empty? # makes sure that there's nothing in the flash
  8560. #
  8561. # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
  8562. # appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
  8563. # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
  8564. #
  8565. # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
  8566. #
  8567. # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
  8568. # action call which can then be asserted against.
  8569. #
  8570. # == Manipulating the request collections
  8571. #
  8572. # The collections described above link to the response, so you can test if what the actions were expected to do happened. But
  8573. # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
  8574. # and cookies, though. For sessions, you just do:
  8575. #
  8576. # @request.session[:key] = "value"
  8577. #
  8578. # For cookies, you need to manually create the cookie, like this:
  8579. #
  8580. # @request.cookies["key"] = CGI::Cookie.new("key", "value")
  8581. #
  8582. # == Testing named routes
  8583. #
  8584. # If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
  8585. # Example:
  8586. #
  8587. # assert_redirected_to page_url(:title => 'foo')
  8588. module Assertions
  8589. # Asserts that the response is one of the following types:
  8590. #
  8591. # * <tt>:success</tt>: Status code was 200
  8592. # * <tt>:redirect</tt>: Status code was in the 300-399 range
  8593. # * <tt>:missing</tt>: Status code was 404
  8594. # * <tt>:error</tt>: Status code was in the 500-599 range
  8595. #
  8596. # You can also pass an explicit status code number as the type, like assert_response(501)
  8597. def assert_response(type, message = nil)
  8598. clean_backtrace do
  8599. if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
  8600. assert_block("") { true } # to count the assertion
  8601. elsif type.is_a?(Fixnum) && @response.response_code == type
  8602. assert_block("") { true } # to count the assertion
  8603. else
  8604. assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
  8605. end
  8606. end
  8607. end
  8608. # Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
  8609. # such that assert_redirected_to(:controller => "weblog") will also match the redirection of
  8610. # redirect_to(:controller => "weblog", :action => "show") and so on.
  8611. def assert_redirected_to(options = {}, message=nil)
  8612. clean_backtrace do
  8613. assert_response(:redirect, message)
  8614. if options.is_a?(String)
  8615. msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
  8616. url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
  8617. eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
  8618. u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
  8619. [u, (p[0..0] == '/') ? p : '/' + p]
  8620. end.flatten
  8621. assert_equal(eurl, url, msg) if eurl && url
  8622. assert_equal(epath, path, msg) if epath && path
  8623. else
  8624. @response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
  8625. msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)#{', difference: <?>' if @response_diff}",
  8626. @response.redirected_to || @response.redirect_url, @response_diff)
  8627. assert_block(msg) do
  8628. if options.is_a?(Symbol)
  8629. @response.redirected_to == options
  8630. else
  8631. options.keys.all? do |k|
  8632. if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path)
  8633. else options[k] == (@response.redirected_to[k].respond_to?(:to_param) ? @response.redirected_to[k].to_param : @response.redirected_to[k] unless @response.redirected_to[k].nil?)
  8634. end
  8635. end
  8636. end
  8637. end
  8638. end
  8639. end
  8640. end
  8641. # Asserts that the request was rendered with the appropriate template file.
  8642. def assert_template(expected = nil, message=nil)
  8643. clean_backtrace do
  8644. rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
  8645. msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
  8646. assert_block(msg) do
  8647. if expected.nil?
  8648. !@response.rendered_with_file?
  8649. else
  8650. expected == rendered
  8651. end
  8652. end
  8653. end
  8654. end
  8655. # Asserts that the routing of the given path was handled correctly and that the parsed options match.
  8656. def assert_recognizes(expected_options, path, extras={}, message=nil)
  8657. clean_backtrace do
  8658. path = "/#{path}" unless path[0..0] == '/'
  8659. # Load routes.rb if it hasn't been loaded.
  8660. ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
  8661. # Assume given controller
  8662. request = ActionController::TestRequest.new({}, {}, nil)
  8663. request.path = path
  8664. ActionController::Routing::Routes.recognize!(request)
  8665. expected_options = expected_options.clone
  8666. extras.each_key { |key| expected_options.delete key } unless extras.nil?
  8667. expected_options.stringify_keys!
  8668. msg = build_message(message, "The recognized options <?> did not match <?>",
  8669. request.path_parameters, expected_options)
  8670. assert_block(msg) { request.path_parameters == expected_options }
  8671. end
  8672. end
  8673. # Asserts that the provided options can be used to generate the provided path.
  8674. def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
  8675. clean_backtrace do
  8676. expected_path = "/#{expected_path}" unless expected_path[0] == ?/
  8677. # Load routes.rb if it hasn't been loaded.
  8678. ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
  8679. generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
  8680. found_extras = options.reject {|k, v| ! extra_keys.include? k}
  8681. msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
  8682. assert_block(msg) { found_extras == extras }
  8683. msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
  8684. expected_path)
  8685. assert_block(msg) { expected_path == generated_path }
  8686. end
  8687. end
  8688. # Asserts that path and options match both ways; in other words, the URL generated from
  8689. # options is the same as path, and also that the options recognized from path are the same as options
  8690. def assert_routing(path, options, defaults={}, extras={}, message=nil)
  8691. assert_recognizes(options, path, extras, message)
  8692. controller, default_controller = options[:controller], defaults[:controller]
  8693. if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
  8694. options[:controller] = "/#{controller}"
  8695. end
  8696. assert_generates(path, options, defaults, extras, message)
  8697. end
  8698. # Asserts that there is a tag/node/element in the body of the response
  8699. # that meets all of the given conditions. The +conditions+ parameter must
  8700. # be a hash of any of the following keys (all are optional):
  8701. #
  8702. # * <tt>:tag</tt>: the node type must match the corresponding value
  8703. # * <tt>:attributes</tt>: a hash. The node's attributes must match the
  8704. # corresponding values in the hash.
  8705. # * <tt>:parent</tt>: a hash. The node's parent must match the
  8706. # corresponding hash.
  8707. # * <tt>:child</tt>: a hash. At least one of the node's immediate children
  8708. # must meet the criteria described by the hash.
  8709. # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
  8710. # meet the criteria described by the hash.
  8711. # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
  8712. # must meet the criteria described by the hash.
  8713. # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
  8714. # meet the criteria described by the hash.
  8715. # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
  8716. # the criteria described by the hash, and at least one sibling must match.
  8717. # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
  8718. # the criteria described by the hash, and at least one sibling must match.
  8719. # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
  8720. # the keys:
  8721. # * <tt>:count</tt>: either a number or a range which must equal (or
  8722. # include) the number of children that match.
  8723. # * <tt>:less_than</tt>: the number of matching children must be less
  8724. # than this number.
  8725. # * <tt>:greater_than</tt>: the number of matching children must be
  8726. # greater than this number.
  8727. # * <tt>:only</tt>: another hash consisting of the keys to use
  8728. # to match on the children, and only matching children will be
  8729. # counted.
  8730. # * <tt>:content</tt>: the textual content of the node must match the
  8731. # given value. This will not match HTML tags in the body of a
  8732. # tag--only text.
  8733. #
  8734. # Conditions are matched using the following algorithm:
  8735. #
  8736. # * if the condition is a string, it must be a substring of the value.
  8737. # * if the condition is a regexp, it must match the value.
  8738. # * if the condition is a number, the value must match number.to_s.
  8739. # * if the condition is +true+, the value must not be +nil+.
  8740. # * if the condition is +false+ or +nil+, the value must be +nil+.
  8741. #
  8742. # Usage:
  8743. #
  8744. # # assert that there is a "span" tag
  8745. # assert_tag :tag => "span"
  8746. #
  8747. # # assert that there is a "span" tag with id="x"
  8748. # assert_tag :tag => "span", :attributes => { :id => "x" }
  8749. #
  8750. # # assert that there is a "span" tag using the short-hand
  8751. # assert_tag :span
  8752. #
  8753. # # assert that there is a "span" tag with id="x" using the short-hand
  8754. # assert_tag :span, :attributes => { :id => "x" }
  8755. #
  8756. # # assert that there is a "span" inside of a "div"
  8757. # assert_tag :tag => "span", :parent => { :tag => "div" }
  8758. #
  8759. # # assert that there is a "span" somewhere inside a table
  8760. # assert_tag :tag => "span", :ancestor => { :tag => "table" }
  8761. #
  8762. # # assert that there is a "span" with at least one "em" child
  8763. # assert_tag :tag => "span", :child => { :tag => "em" }
  8764. #
  8765. # # assert that there is a "span" containing a (possibly nested)
  8766. # # "strong" tag.
  8767. # assert_tag :tag => "span", :descendant => { :tag => "strong" }
  8768. #
  8769. # # assert that there is a "span" containing between 2 and 4 "em" tags
  8770. # # as immediate children
  8771. # assert_tag :tag => "span",
  8772. # :children => { :count => 2..4, :only => { :tag => "em" } }
  8773. #
  8774. # # get funky: assert that there is a "div", with an "ul" ancestor
  8775. # # and an "li" parent (with "class" = "enum"), and containing a
  8776. # # "span" descendant that contains text matching /hello world/
  8777. # assert_tag :tag => "div",
  8778. # :ancestor => { :tag => "ul" },
  8779. # :parent => { :tag => "li",
  8780. # :attributes => { :class => "enum" } },
  8781. # :descendant => { :tag => "span",
  8782. # :child => /hello world/ }
  8783. #
  8784. # <strong>Please note</strong: #assert_tag and #assert_no_tag only work
  8785. # with well-formed XHTML. They recognize a few tags as implicitly self-closing
  8786. # (like br and hr and such) but will not work correctly with tags
  8787. # that allow optional closing tags (p, li, td). <em>You must explicitly
  8788. # close all of your tags to use these assertions.</em>
  8789. def assert_tag(*opts)
  8790. clean_backtrace do
  8791. opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
  8792. tag = find_tag(opts)
  8793. assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
  8794. end
  8795. end
  8796. # Identical to #assert_tag, but asserts that a matching tag does _not_
  8797. # exist. (See #assert_tag for a full discussion of the syntax.)
  8798. def assert_no_tag(*opts)
  8799. clean_backtrace do
  8800. opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
  8801. tag = find_tag(opts)
  8802. assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
  8803. end
  8804. end
  8805. # test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
  8806. def assert_dom_equal(expected, actual, message="")
  8807. clean_backtrace do
  8808. expected_dom = HTML::Document.new(expected).root
  8809. actual_dom = HTML::Document.new(actual).root
  8810. full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
  8811. assert_block(full_message) { expected_dom == actual_dom }
  8812. end
  8813. end
  8814. # negated form of +assert_dom_equivalent+
  8815. def assert_dom_not_equal(expected, actual, message="")
  8816. clean_backtrace do
  8817. expected_dom = HTML::Document.new(expected).root
  8818. actual_dom = HTML::Document.new(actual).root
  8819. full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
  8820. assert_block(full_message) { expected_dom != actual_dom }
  8821. end
  8822. end
  8823. # ensures that the passed record is valid by active record standards. returns the error messages if not
  8824. def assert_valid(record)
  8825. clean_backtrace do
  8826. assert record.valid?, record.errors.full_messages.join("\n")
  8827. end
  8828. end
  8829. def clean_backtrace(&block)
  8830. yield
  8831. rescue AssertionFailedError => e
  8832. path = File.expand_path(__FILE__)
  8833. raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
  8834. end
  8835. end
  8836. end
  8837. end
  8838. require 'action_controller/mime_type'
  8839. require 'action_controller/request'
  8840. require 'action_controller/response'
  8841. require 'action_controller/routing'
  8842. require 'action_controller/code_generation'
  8843. require 'action_controller/url_rewriter'
  8844. require 'drb'
  8845. require 'set'
  8846. module ActionController #:nodoc:
  8847. class ActionControllerError < StandardError #:nodoc:
  8848. end
  8849. class SessionRestoreError < ActionControllerError #:nodoc:
  8850. end
  8851. class MissingTemplate < ActionControllerError #:nodoc:
  8852. end
  8853. class RoutingError < ActionControllerError #:nodoc:
  8854. attr_reader :failures
  8855. def initialize(message, failures=[])
  8856. super(message)
  8857. @failures = failures
  8858. end
  8859. end
  8860. class UnknownController < ActionControllerError #:nodoc:
  8861. end
  8862. class UnknownAction < ActionControllerError #:nodoc:
  8863. end
  8864. class MissingFile < ActionControllerError #:nodoc:
  8865. end
  8866. class SessionOverflowError < ActionControllerError #:nodoc:
  8867. DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
  8868. def initialize(message = nil)
  8869. super(message || DEFAULT_MESSAGE)
  8870. end
  8871. end
  8872. class DoubleRenderError < ActionControllerError #:nodoc:
  8873. DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...) and return false\"."
  8874. def initialize(message = nil)
  8875. super(message || DEFAULT_MESSAGE)
  8876. end
  8877. end
  8878. class RedirectBackError < ActionControllerError #:nodoc:
  8879. DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify @request.env["HTTP_REFERER"].'
  8880. def initialize(message = nil)
  8881. super(message || DEFAULT_MESSAGE)
  8882. end
  8883. end
  8884. # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed
  8885. # on request and then either render a template or redirect to another action. An action is defined as a public method
  8886. # on the controller, which will automatically be made accessible to the web-server through Rails Routes.
  8887. #
  8888. # A sample controller could look like this:
  8889. #
  8890. # class GuestBookController < ActionController::Base
  8891. # def index
  8892. # @entries = Entry.find(:all)
  8893. # end
  8894. #
  8895. # def sign
  8896. # Entry.create(params[:entry])
  8897. # redirect_to :action => "index"
  8898. # end
  8899. # end
  8900. #
  8901. # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
  8902. # after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the
  8903. # template <tt>app/views/guestbook/index.rhtml</tt> by default after populating the <tt>@entries</tt> instance variable.
  8904. #
  8905. # Unlike index, the sign action will not render a template. After performing its main purpose (creating a
  8906. # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external
  8907. # "302 Moved" HTTP response that takes the user to the index action.
  8908. #
  8909. # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
  8910. # Most actions are variations of these themes.
  8911. #
  8912. # == Requests
  8913. #
  8914. # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
  8915. # This value should hold the name of the action to be performed. Once the action has been identified, the remaining
  8916. # request parameters, the session (if one is available), and the full request with all the http headers are made available to
  8917. # the action through instance variables. Then the action is performed.
  8918. #
  8919. # The full request object is available with the request accessor and is primarily used to query for http headers. These queries
  8920. # are made by accessing the environment hash, like this:
  8921. #
  8922. # def server_ip
  8923. # location = request.env["SERVER_ADDR"]
  8924. # render :text => "This server hosted at #{location}"
  8925. # end
  8926. #
  8927. # == Parameters
  8928. #
  8929. # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
  8930. # which returns a hash. For example, an action that was performed through <tt>/weblog/list?category=All&limit=5</tt> will include
  8931. # <tt>{ "category" => "All", "limit" => 5 }</tt> in params.
  8932. #
  8933. # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
  8934. #
  8935. # <input type="text" name="post[name]" value="david">
  8936. # <input type="text" name="post[address]" value="hyacintvej">
  8937. #
  8938. # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
  8939. # If the address input had been named "post[address][street]", the params would have included
  8940. # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
  8941. #
  8942. # == Sessions
  8943. #
  8944. # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
  8945. # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
  8946. # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
  8947. # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
  8948. #
  8949. # You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
  8950. #
  8951. # session[:person] = Person.authenticate(user_name, password)
  8952. #
  8953. # And retrieved again through the same hash:
  8954. #
  8955. # Hello #{session[:person]}
  8956. #
  8957. # For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
  8958. # remove the entire session with reset_session.
  8959. #
  8960. # By default, sessions are stored on the file system in <tt>RAILS_ROOT/tmp/sessions</tt>. Any object can be placed in the session
  8961. # (as long as it can be Marshalled). But remember that 1000 active sessions each storing a 50kb object could lead to a 50MB store on the filesystem.
  8962. # In other words, think carefully about size and caching before resorting to the use of the session on the filesystem.
  8963. #
  8964. # An alternative to storing sessions on disk is to use ActiveRecordStore to store sessions in your database, which can solve problems
  8965. # caused by storing sessions in the file system and may speed up your application. To use ActiveRecordStore, uncomment the line:
  8966. #
  8967. # config.action_controller.session_store = :active_record_store
  8968. #
  8969. # in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
  8970. #
  8971. # == Responses
  8972. #
  8973. # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
  8974. # object is generated automatically through the use of renders and redirects and requires no user intervention.
  8975. #
  8976. # == Renders
  8977. #
  8978. # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
  8979. # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
  8980. # The controller passes objects to the view by assigning instance variables:
  8981. #
  8982. # def show
  8983. # @post = Post.find(params[:id])
  8984. # end
  8985. #
  8986. # Which are then automatically available to the view:
  8987. #
  8988. # Title: <%= @post.title %>
  8989. #
  8990. # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
  8991. # the manual rendering methods:
  8992. #
  8993. # def search
  8994. # @results = Search.find(params[:query])
  8995. # case @results
  8996. # when 0 then render :action => "no_results"
  8997. # when 1 then render :action => "show"
  8998. # when 2..10 then render :action => "show_many"
  8999. # end
  9000. # end
  9001. #
  9002. # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
  9003. #
  9004. # == Redirects
  9005. #
  9006. # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database,
  9007. # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to)
  9008. # a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
  9009. #
  9010. # def create
  9011. # @entry = Entry.new(params[:entry])
  9012. # if @entry.save
  9013. # # The entry was saved correctly, redirect to show
  9014. # redirect_to :action => 'show', :id => @entry.id
  9015. # else
  9016. # # things didn't go so well, do something else
  9017. # end
  9018. # end
  9019. #
  9020. # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed.
  9021. #
  9022. # == Calling multiple redirects or renders
  9023. #
  9024. # An action should conclude with a single render or redirect. Attempting to try to do either again will result in a DoubleRenderError:
  9025. #
  9026. # def do_something
  9027. # redirect_to :action => "elsewhere"
  9028. # render :action => "overthere" # raises DoubleRenderError
  9029. # end
  9030. #
  9031. # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
  9032. #
  9033. # def do_something
  9034. # redirect_to(:action => "elsewhere") and return if monkeys.nil?
  9035. # render :action => "overthere" # won't be called unless monkeys is nil
  9036. # end
  9037. #
  9038. class Base
  9039. DEFAULT_RENDER_STATUS_CODE = "200 OK"
  9040. include Reloadable::Subclasses
  9041. # Determines whether the view has access to controller internals @request, @response, @session, and @template.
  9042. # By default, it does.
  9043. @@view_controller_internals = true
  9044. cattr_accessor :view_controller_internals
  9045. # Protected instance variable cache
  9046. @@protected_variables_cache = nil
  9047. cattr_accessor :protected_variables_cache
  9048. # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
  9049. # and images to a dedicated asset server away from the main web server. Example:
  9050. # ActionController::Base.asset_host = "http://assets.example.com"
  9051. @@asset_host = ""
  9052. cattr_accessor :asset_host
  9053. # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
  9054. # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt>
  9055. # should instead be implemented in the controller to determine when debugging screens should be shown.
  9056. @@consider_all_requests_local = true
  9057. cattr_accessor :consider_all_requests_local
  9058. # Enable or disable the collection of failure information for RoutingErrors.
  9059. # This information can be extremely useful when tweaking custom routes, but is
  9060. # pointless once routes have been tested and verified.
  9061. @@debug_routes = true
  9062. cattr_accessor :debug_routes
  9063. # Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex
  9064. # around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications
  9065. # may not be. Turned off by default.
  9066. @@allow_concurrency = false
  9067. cattr_accessor :allow_concurrency
  9068. # Modern REST web services often need to submit complex data to the web application.
  9069. # The param_parsers hash lets you register handlers wich will process the http body and add parameters to the
  9070. # <tt>params</tt> hash. These handlers are invoked for post and put requests.
  9071. #
  9072. # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instanciated
  9073. # in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one
  9074. # action serve both regular forms and web service requests.
  9075. #
  9076. # Example of doing your own parser for a custom content type:
  9077. #
  9078. # ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data|
  9079. # node = REXML::Document.new(post)
  9080. # { node.root.name => node.root }
  9081. # end
  9082. #
  9083. # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the
  9084. # root node for such requests. The new default is to keep the root, such that "<r><name>David</name></r>" results
  9085. # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can
  9086. # re-register XmlSimple as application/xml handler ike this:
  9087. #
  9088. # ActionController::Base.param_parsers[Mime::XML] =
  9089. # Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
  9090. #
  9091. # A YAML parser is also available and can be turned on with:
  9092. #
  9093. # ActionController::Base.param_parsers[Mime::YAML] = :yaml
  9094. @@param_parsers = { Mime::XML => :xml_simple }
  9095. cattr_accessor :param_parsers
  9096. # Template root determines the base from which template references will be made. So a call to render("test/template")
  9097. # will be converted to "#{template_root}/test/template.rhtml".
  9098. class_inheritable_accessor :template_root
  9099. # The logger is used for generating information on the action run-time (including benchmarking) if available.
  9100. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
  9101. cattr_accessor :logger
  9102. # Determines which template class should be used by ActionController.
  9103. cattr_accessor :template_class
  9104. # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
  9105. cattr_accessor :ignore_missing_templates
  9106. # Holds the request object that's primarily used to get environment variables through access like
  9107. # <tt>request.env["REQUEST_URI"]</tt>.
  9108. attr_accessor :request
  9109. # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>params["post_id"]</tt>
  9110. # to get the post_id. No type casts are made, so all values are returned as strings.
  9111. attr_accessor :params
  9112. # Holds the response object that's primarily used to set additional HTTP headers through access like
  9113. # <tt>response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template
  9114. # has been rendered through response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output,
  9115. # such as a OutputCompressionFilter.
  9116. attr_accessor :response
  9117. # Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person"
  9118. # key. The session will hold any type of object as values, but the key should be a string or symbol.
  9119. attr_accessor :session
  9120. # Holds a hash of header names and values. Accessed like <tt>headers["Cache-Control"]</tt> to get the value of the Cache-Control
  9121. # directive. Values should always be specified as strings.
  9122. attr_accessor :headers
  9123. # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
  9124. # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
  9125. attr_accessor :assigns
  9126. # Returns the name of the action this controller is processing.
  9127. attr_accessor :action_name
  9128. class << self
  9129. # Factory for the standard create, process loop where the controller is discarded after processing.
  9130. def process(request, response) #:nodoc:
  9131. new.process(request, response)
  9132. end
  9133. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
  9134. def controller_class_name
  9135. @controller_class_name ||= name.demodulize
  9136. end
  9137. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
  9138. def controller_name
  9139. @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
  9140. end
  9141. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
  9142. def controller_path
  9143. @controller_path ||= name.gsub(/Controller$/, '').underscore
  9144. end
  9145. # Return an array containing the names of public methods that have been marked hidden from the action processor.
  9146. # By default, all methods defined in ActionController::Base and included modules are hidden.
  9147. # More methods can be hidden using <tt>hide_actions</tt>.
  9148. def hidden_actions
  9149. write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods) unless read_inheritable_attribute(:hidden_actions)
  9150. read_inheritable_attribute(:hidden_actions)
  9151. end
  9152. # Hide each of the given methods from being callable as actions.
  9153. def hide_action(*names)
  9154. write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect { |n| n.to_s })
  9155. end
  9156. # Replace sensitive paramater data from the request log.
  9157. # Filters paramaters that have any of the arguments as a substring.
  9158. # Looks in all subhashes of the param hash for keys to filter.
  9159. # If a block is given, each key and value of the paramater hash and all
  9160. # subhashes is passed to it, the value or key
  9161. # can be replaced using String#replace or similar method.
  9162. #
  9163. # Examples:
  9164. # filter_parameter_logging
  9165. # => Does nothing, just slows the logging process down
  9166. #
  9167. # filter_parameter_logging :password
  9168. # => replaces the value to all keys matching /password/i with "[FILTERED]"
  9169. #
  9170. # filter_parameter_logging :foo, "bar"
  9171. # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
  9172. #
  9173. # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
  9174. # => reverses the value to all keys matching /secret/i
  9175. #
  9176. # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
  9177. # => reverses the value to all keys matching /secret/i, and
  9178. # replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
  9179. def filter_parameter_logging(*filter_words, &block)
  9180. parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
  9181. define_method(:filter_parameters) do |unfiltered_parameters|
  9182. filtered_parameters = {}
  9183. unfiltered_parameters.each do |key, value|
  9184. if key =~ parameter_filter
  9185. filtered_parameters[key] = '[FILTERED]'
  9186. elsif value.is_a?(Hash)
  9187. filtered_parameters[key] = filter_parameters(value)
  9188. elsif block_given?
  9189. key, value = key.dup, value.dup
  9190. yield key, value
  9191. filtered_parameters[key] = value
  9192. else
  9193. filtered_parameters[key] = value
  9194. end
  9195. end
  9196. filtered_parameters
  9197. end
  9198. end
  9199. end
  9200. public
  9201. # Extracts the action_name from the request parameters and performs that action.
  9202. def process(request, response, method = :perform_action, *arguments) #:nodoc:
  9203. initialize_template_class(response)
  9204. assign_shortcuts(request, response)
  9205. initialize_current_url
  9206. assign_names
  9207. forget_variables_added_to_assigns
  9208. log_processing
  9209. send(method, *arguments)
  9210. response
  9211. ensure
  9212. process_cleanup
  9213. end
  9214. # Returns a URL that has been rewritten according to the options hash and the defined Routes.
  9215. # (For doing a complete redirect, use redirect_to).
  9216. #  
  9217. # <tt>url_for</tt> is used to:
  9218. #  
  9219. # All keys given to url_for are forwarded to the Route module, save for the following:
  9220. # * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example,
  9221. # <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
  9222. # will produce "/posts/show/10#comments".
  9223. # * <tt>:only_path</tt> -- if true, returns the absolute URL (omitting the protocol, host name, and port)
  9224. # * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
  9225. # is currently not recommended since it breaks caching.
  9226. # * <tt>:host</tt> -- overrides the default (current) host if provided
  9227. # * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
  9228. #
  9229. # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
  9230. # Routes composes a query string as the key/value pairs not included in the <base>.
  9231. #
  9232. # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
  9233. # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
  9234. #  
  9235. # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
  9236. # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
  9237. # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
  9238. #
  9239. # When generating a new URL, missing values may be filled in from the current request's parameters. For example,
  9240. # <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
  9241. # other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's
  9242. # path.
  9243. #  
  9244. # The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for
  9245. # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
  9246. # taken from the defaults. There are a few simple rules on how this is performed:
  9247. #
  9248. # * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt>
  9249. # * If the controller changes, the action will default to index unless provided
  9250. #
  9251. # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
  9252. # route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>.
  9253. #
  9254. # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated
  9255. # from this page.
  9256. #
  9257. # * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and
  9258. # last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
  9259. # * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note
  9260. # that this URL leaves out the assumed action of 'bio'.
  9261. #
  9262. # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
  9263. # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
  9264. # value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
  9265. # defaults. On it's own, this rule can account for much of the typical Rails URL behavior.
  9266. #  
  9267. # Although a convienence, defaults can occasionaly get in your way. In some cases a default persists longer than desired.
  9268. # The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
  9269. # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
  9270. # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
  9271. # displayed on:
  9272. #
  9273. # url_for :controller => 'posts', :action => nil
  9274. #
  9275. # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
  9276. # :overwrite_params options. Say for your posts you have different views for showing and printing them.
  9277. # Then, in the show view, you get the URL for the print view like this
  9278. #
  9279. # url_for :overwrite_params => { :action => 'print' }
  9280. #
  9281. # This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
  9282. # would have slashed-off the path components after the changed action.
  9283. def url_for(options = {}, *parameters_for_method_reference) #:doc:
  9284. case options
  9285. when String then options
  9286. when Symbol then send(options, *parameters_for_method_reference)
  9287. when Hash then @url.rewrite(rewrite_options(options))
  9288. end
  9289. end
  9290. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
  9291. def controller_class_name
  9292. self.class.controller_class_name
  9293. end
  9294. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
  9295. def controller_name
  9296. self.class.controller_name
  9297. end
  9298. def session_enabled?
  9299. request.session_options[:disabled] != false
  9300. end
  9301. protected
  9302. # Renders the content that will be returned to the browser as the response body.
  9303. #
  9304. # === Rendering an action
  9305. #
  9306. # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is
  9307. # specified. By default, actions are rendered within the current layout (if one exists).
  9308. #
  9309. # # Renders the template for the action "goal" within the current controller
  9310. # render :action => "goal"
  9311. #
  9312. # # Renders the template for the action "short_goal" within the current controller,
  9313. # # but without the current active layout
  9314. # render :action => "short_goal", :layout => false
  9315. #
  9316. # # Renders the template for the action "long_goal" within the current controller,
  9317. # # but with a custom layout
  9318. # render :action => "long_goal", :layout => "spectacular"
  9319. #
  9320. # _Deprecation_ _notice_: This used to have the signatures <tt>render_action("action", status = 200)</tt>,
  9321. # <tt>render_without_layout("controller/action", status = 200)</tt>, and
  9322. # <tt>render_with_layout("controller/action", status = 200, layout)</tt>.
  9323. #
  9324. # === Rendering partials
  9325. #
  9326. # Partial rendering is most commonly used together with Ajax calls that only update one or a few elements on a page
  9327. # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in
  9328. # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the
  9329. # controller action responding to Ajax calls). By default, the current layout is not used.
  9330. #
  9331. # # Renders the partial located at app/views/controller/_win.r(html|xml)
  9332. # render :partial => "win"
  9333. #
  9334. # # Renders the partial with a status code of 500 (internal error)
  9335. # render :partial => "broken", :status => 500
  9336. #
  9337. # # Renders the same partial but also makes a local variable available to it
  9338. # render :partial => "win", :locals => { :name => "david" }
  9339. #
  9340. # # Renders a collection of the same partial by making each element of @wins available through
  9341. # # the local variable "win" as it builds the complete response
  9342. # render :partial => "win", :collection => @wins
  9343. #
  9344. # # Renders the same collection of partials, but also renders the win_divider partial in between
  9345. # # each win partial.
  9346. # render :partial => "win", :collection => @wins, :spacer_template => "win_divider"
  9347. #
  9348. # _Deprecation_ _notice_: This used to have the signatures
  9349. # <tt>render_partial(partial_path = default_template_name, object = nil, local_assigns = {})</tt> and
  9350. # <tt>render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})</tt>.
  9351. #
  9352. # === Rendering a template
  9353. #
  9354. # Template rendering works just like action rendering except that it takes a path relative to the template root.
  9355. # The current layout is automatically applied.
  9356. #
  9357. # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.rhtml)
  9358. # render :template => "weblog/show"
  9359. #
  9360. # === Rendering a file
  9361. #
  9362. # File rendering works just like action rendering except that it takes a filesystem path. By default, the path
  9363. # is assumed to be absolute, and the current layout is not applied.
  9364. #
  9365. # # Renders the template located at the absolute filesystem path
  9366. # render :file => "/path/to/some/template.rhtml"
  9367. # render :file => "c:/path/to/some/template.rhtml"
  9368. #
  9369. # # Renders a template within the current layout, and with a 404 status code
  9370. # render :file => "/path/to/some/template.rhtml", :layout => true, :status => 404
  9371. # render :file => "c:/path/to/some/template.rhtml", :layout => true, :status => 404
  9372. #
  9373. # # Renders a template relative to the template root and chooses the proper file extension
  9374. # render :file => "some/template", :use_full_path => true
  9375. #
  9376. # _Deprecation_ _notice_: This used to have the signature <tt>render_file(path, status = 200)</tt>
  9377. #
  9378. # === Rendering text
  9379. #
  9380. # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
  9381. # rendering is not done within the active layout.
  9382. #
  9383. # # Renders the clear text "hello world" with status code 200
  9384. # render :text => "hello world!"
  9385. #
  9386. # # Renders the clear text "Explosion!" with status code 500
  9387. # render :text => "Explosion!", :status => 500
  9388. #
  9389. # # Renders the clear text "Hi there!" within the current active layout (if one exists)
  9390. # render :text => "Explosion!", :layout => true
  9391. #
  9392. # # Renders the clear text "Hi there!" within the layout
  9393. # # placed in "app/views/layouts/special.r(html|xml)"
  9394. # render :text => "Explosion!", :layout => "special"
  9395. #
  9396. # _Deprecation_ _notice_: This used to have the signature <tt>render_text("text", status = 200)</tt>
  9397. #
  9398. # === Rendering an inline template
  9399. #
  9400. # Rendering of an inline template works as a cross between text and action rendering where the source for the template
  9401. # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering
  9402. # and the current layout is not used.
  9403. #
  9404. # # Renders "hello, hello, hello, again"
  9405. # render :inline => "<%= 'hello, ' * 3 + 'again' %>"
  9406. #
  9407. # # Renders "<p>Good seeing you!</p>" using Builder
  9408. # render :inline => "xml.p { 'Good seeing you!' }", :type => :rxml
  9409. #
  9410. # # Renders "hello david"
  9411. # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }
  9412. #
  9413. # _Deprecation_ _notice_: This used to have the signature <tt>render_template(template, status = 200, type = :rhtml)</tt>
  9414. #
  9415. # === Rendering inline JavaScriptGenerator page updates
  9416. #
  9417. # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details),
  9418. # you can also pass the <tt>:update</tt> parameter to +render+, along with a block, to render page updates inline.
  9419. #
  9420. # render :update do |page|
  9421. # page.replace_html 'user_list', :partial => 'user', :collection => @users
  9422. # page.visual_effect :highlight, 'user_list'
  9423. # end
  9424. #
  9425. # === Rendering nothing
  9426. #
  9427. # Rendering nothing is often convenient in combination with Ajax calls that perform their effect client-side or
  9428. # when you just want to communicate a status code. Due to a bug in Safari, nothing actually means a single space.
  9429. #
  9430. # # Renders an empty response with status code 200
  9431. # render :nothing => true
  9432. #
  9433. # # Renders an empty response with status code 401 (access denied)
  9434. # render :nothing => true, :status => 401
  9435. def render(options = nil, deprecated_status = nil, &block) #:doc:
  9436. raise DoubleRenderError, "Can only render or redirect once per action" if performed?
  9437. # Backwards compatibility
  9438. unless options.is_a?(Hash)
  9439. if options == :update
  9440. options = {:update => true}
  9441. else
  9442. return render_file(options || default_template_name, deprecated_status, true)
  9443. end
  9444. end
  9445. if content_type = options[:content_type]
  9446. headers["Content-Type"] = content_type
  9447. end
  9448. if text = options[:text]
  9449. render_text(text, options[:status])
  9450. else
  9451. if file = options[:file]
  9452. render_file(file, options[:status], options[:use_full_path], options[:locals] || {})
  9453. elsif template = options[:template]
  9454. render_file(template, options[:status], true)
  9455. elsif inline = options[:inline]
  9456. render_template(inline, options[:status], options[:type], options[:locals] || {})
  9457. elsif action_name = options[:action]
  9458. render_action(action_name, options[:status], options[:layout])
  9459. elsif xml = options[:xml]
  9460. render_xml(xml, options[:status])
  9461. elsif partial = options[:partial]
  9462. partial = default_template_name if partial == true
  9463. if collection = options[:collection]
  9464. render_partial_collection(partial, collection, options[:spacer_template], options[:locals], options[:status])
  9465. else
  9466. render_partial(partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals], options[:status])
  9467. end
  9468. elsif options[:update]
  9469. add_variables_to_assigns
  9470. @template.send :evaluate_assigns
  9471. generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
  9472. render_javascript(generator.to_s)
  9473. elsif options[:nothing]
  9474. # Safari doesn't pass the headers of the return if the response is zero length
  9475. render_text(" ", options[:status])
  9476. else
  9477. render_file(default_template_name, options[:status], true)
  9478. end
  9479. end
  9480. end
  9481. # Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
  9482. # of sending it as the response body to the browser.
  9483. def render_to_string(options = nil, &block) #:doc:
  9484. result = render(options, &block)
  9485. erase_render_results
  9486. forget_variables_added_to_assigns
  9487. reset_variables_added_to_assigns
  9488. result
  9489. end
  9490. def render_action(action_name, status = nil, with_layout = true) #:nodoc:
  9491. template = default_template_name(action_name.to_s)
  9492. if with_layout && !template_exempt_from_layout?(template)
  9493. render_with_layout(template, status)
  9494. else
  9495. render_without_layout(template, status)
  9496. end
  9497. end
  9498. def render_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
  9499. add_variables_to_assigns
  9500. assert_existence_of_template_file(template_path) if use_full_path
  9501. logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
  9502. render_text(@template.render_file(template_path, use_full_path, locals), status)
  9503. end
  9504. def render_template(template, status = nil, type = :rhtml, local_assigns = {}) #:nodoc:
  9505. add_variables_to_assigns
  9506. render_text(@template.render_template(type, template, nil, local_assigns), status)
  9507. end
  9508. def render_text(text = nil, status = nil) #:nodoc:
  9509. @performed_render = true
  9510. @response.headers['Status'] = (status || DEFAULT_RENDER_STATUS_CODE).to_s
  9511. @response.body = text
  9512. end
  9513. def render_javascript(javascript, status = nil) #:nodoc:
  9514. @response.headers['Content-Type'] = 'text/javascript; charset=UTF-8'
  9515. render_text(javascript, status)
  9516. end
  9517. def render_xml(xml, status = nil) #:nodoc:
  9518. @response.headers['Content-Type'] = 'application/xml'
  9519. render_text(xml, status)
  9520. end
  9521. def render_nothing(status = nil) #:nodoc:
  9522. render_text(' ', status)
  9523. end
  9524. def render_partial(partial_path = default_template_name, object = nil, local_assigns = nil, status = nil) #:nodoc:
  9525. add_variables_to_assigns
  9526. render_text(@template.render_partial(partial_path, object, local_assigns), status)
  9527. end
  9528. def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil, status = nil) #:nodoc:
  9529. add_variables_to_assigns
  9530. render_text(@template.render_partial_collection(partial_name, collection, partial_spacer_template, local_assigns), status)
  9531. end
  9532. def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
  9533. render_with_a_layout(template_name, status, layout)
  9534. end
  9535. def render_without_layout(template_name = default_template_name, status = nil) #:nodoc:
  9536. render_with_no_layout(template_name, status)
  9537. end
  9538. # Clears the rendered results, allowing for another render to be performed.
  9539. def erase_render_results #:nodoc:
  9540. @response.body = nil
  9541. @performed_render = false
  9542. end
  9543. # Clears the redirected results from the headers, resets the status to 200 and returns
  9544. # the URL that was used to redirect or nil if there was no redirected URL
  9545. # Note that +redirect_to+ will change the body of the response to indicate a redirection.
  9546. # The response body is not reset here, see +erase_render_results+
  9547. def erase_redirect_results #:nodoc:
  9548. @performed_redirect = false
  9549. response.redirected_to = nil
  9550. response.redirected_to_method_params = nil
  9551. response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
  9552. response.headers.delete('location')
  9553. end
  9554. # Erase both render and redirect results
  9555. def erase_results #:nodoc:
  9556. erase_render_results
  9557. erase_redirect_results
  9558. end
  9559. def rewrite_options(options) #:nodoc:
  9560. if defaults = default_url_options(options)
  9561. defaults.merge(options)
  9562. else
  9563. options
  9564. end
  9565. end
  9566. # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
  9567. # the form of a hash, just like the one you would use for url_for directly. Example:
  9568. #
  9569. # def default_url_options(options)
  9570. # { :project => @project.active? ? @project.url_name : "unknown" }
  9571. # end
  9572. #
  9573. # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
  9574. # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
  9575. # by this method.
  9576. def default_url_options(options) #:doc:
  9577. end
  9578. # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
  9579. #
  9580. # * <tt>Hash</tt>: The URL will be generated by calling url_for with the +options+.
  9581. # * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through as the target for redirection.
  9582. # * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
  9583. # * <tt>:back</tt>: Back to the page that issued the request. Useful for forms that are triggered from multiple places.
  9584. # Short-hand for redirect_to(request.env["HTTP_REFERER"])
  9585. #
  9586. # Examples:
  9587. # redirect_to :action => "show", :id => 5
  9588. # redirect_to "http://www.rubyonrails.org"
  9589. # redirect_to "/images/screenshot.jpg"
  9590. # redirect_to :back
  9591. #
  9592. # The redirection happens as a "302 Moved" header.
  9593. #
  9594. # When using <tt>redirect_to :back</tt>, if there is no referrer,
  9595. # RedirectBackError will be raised. You may specify some fallback
  9596. # behavior for this case by rescueing RedirectBackError.
  9597. def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
  9598. case options
  9599. when %r{^\w+://.*}
  9600. raise DoubleRenderError if performed?
  9601. logger.info("Redirected to #{options}") if logger
  9602. response.redirect(options)
  9603. response.redirected_to = options
  9604. @performed_redirect = true
  9605. when String
  9606. redirect_to(request.protocol + request.host_with_port + options)
  9607. when :back
  9608. request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"]) : raise(RedirectBackError)
  9609. else
  9610. if parameters_for_method_reference.empty?
  9611. redirect_to(url_for(options))
  9612. response.redirected_to = options
  9613. else
  9614. redirect_to(url_for(options, *parameters_for_method_reference))
  9615. response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference
  9616. end
  9617. end
  9618. end
  9619. # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
  9620. # intermediate caches shouldn't cache the response.
  9621. #
  9622. # Examples:
  9623. # expires_in 20.minutes
  9624. # expires_in 3.hours, :private => false
  9625. # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
  9626. #
  9627. # This method will overwrite an existing Cache-Control header.
  9628. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
  9629. def expires_in(seconds, options = {}) #:doc:
  9630. cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
  9631. cache_options.delete_if { |k,v| v.nil? or v == false }
  9632. cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
  9633. @response.headers["Cache-Control"] = cache_control.join(', ')
  9634. end
  9635. # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
  9636. # intermediate caches (like caching proxy servers).
  9637. def expires_now #:doc:
  9638. @response.headers["Cache-Control"] = "no-cache"
  9639. end
  9640. # Resets the session by clearing out all the objects stored within and initializing a new session object.
  9641. def reset_session #:doc:
  9642. @request.reset_session
  9643. @session = @request.session
  9644. @response.session = @session
  9645. end
  9646. private
  9647. def self.view_class
  9648. @view_class ||=
  9649. # create a new class based on the default template class and include helper methods
  9650. returning Class.new(ActionView::Base) do |view_class|
  9651. view_class.send(:include, master_helper_module)
  9652. end
  9653. end
  9654. def self.view_root
  9655. @view_root ||= template_root
  9656. end
  9657. def initialize_template_class(response)
  9658. raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class
  9659. response.template = self.class.view_class.new(self.class.view_root, {}, self)
  9660. response.redirected_to = nil
  9661. @performed_render = @performed_redirect = false
  9662. end
  9663. def assign_shortcuts(request, response)
  9664. @request, @params, @cookies = request, request.parameters, request.cookies
  9665. @response = response
  9666. @response.session = request.session
  9667. @session = @response.session
  9668. @template = @response.template
  9669. @assigns = @response.template.assigns
  9670. @headers = @response.headers
  9671. end
  9672. def initialize_current_url
  9673. @url = UrlRewriter.new(@request, @params.clone())
  9674. end
  9675. def log_processing
  9676. if logger
  9677. logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
  9678. logger.info " Session ID: #{@session.session_id}" if @session and @session.respond_to?(:session_id)
  9679. logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(@params).inspect : @params.inspect}"
  9680. end
  9681. end
  9682. def perform_action
  9683. if self.class.action_methods.include?(action_name) || self.class.action_methods.include?('method_missing')
  9684. send(action_name)
  9685. render unless performed?
  9686. elsif template_exists? && template_public?
  9687. render
  9688. else
  9689. raise UnknownAction, "No action responded to #{action_name}", caller
  9690. end
  9691. end
  9692. def performed?
  9693. @performed_render || @performed_redirect
  9694. end
  9695. def assign_names
  9696. @action_name = (params['action'] || 'index')
  9697. end
  9698. def action_methods
  9699. self.class.action_methods
  9700. end
  9701. def self.action_methods
  9702. @action_methods ||= Set.new(public_instance_methods - hidden_actions)
  9703. end
  9704. def add_variables_to_assigns
  9705. unless @variables_added
  9706. add_instance_variables_to_assigns
  9707. add_class_variables_to_assigns if view_controller_internals
  9708. @variables_added = true
  9709. end
  9710. end
  9711. def forget_variables_added_to_assigns
  9712. @variables_added = nil
  9713. end
  9714. def reset_variables_added_to_assigns
  9715. @template.instance_variable_set("@assigns_added", nil)
  9716. end
  9717. def add_instance_variables_to_assigns
  9718. @@protected_variables_cache ||= protected_instance_variables.inject({}) { |h, k| h[k] = true; h }
  9719. instance_variables.each do |var|
  9720. next if @@protected_variables_cache.include?(var)
  9721. @assigns[var[1..-1]] = instance_variable_get(var)
  9722. end
  9723. end
  9724. def add_class_variables_to_assigns
  9725. %w( template_root logger template_class ignore_missing_templates ).each do |cvar|
  9726. @assigns[cvar] = self.send(cvar)
  9727. end
  9728. end
  9729. def protected_instance_variables
  9730. if view_controller_internals
  9731. [ "@assigns", "@performed_redirect", "@performed_render" ]
  9732. else
  9733. [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@cookies", "@template", "@request_origin", "@parent_controller" ]
  9734. end
  9735. end
  9736. def request_origin
  9737. # this *needs* to be cached!
  9738. # otherwise you'd get different results if calling it more than once
  9739. @request_origin ||= "#{@request.remote_ip} at #{Time.now.to_s(:db)}"
  9740. end
  9741. def complete_request_uri
  9742. "#{@request.protocol}#{@request.host}#{@request.request_uri}"
  9743. end
  9744. def close_session
  9745. @session.close unless @session.nil? || Hash === @session
  9746. end
  9747. def template_exists?(template_name = default_template_name)
  9748. @template.file_exists?(template_name)
  9749. end
  9750. def template_public?(template_name = default_template_name)
  9751. @template.file_public?(template_name)
  9752. end
  9753. def template_exempt_from_layout?(template_name = default_template_name)
  9754. template_name =~ /\.rjs$/ || (@template.pick_template_extension(template_name) == :rjs rescue false)
  9755. end
  9756. def assert_existence_of_template_file(template_name)
  9757. unless template_exists?(template_name) || ignore_missing_templates
  9758. full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
  9759. template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
  9760. raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
  9761. end
  9762. end
  9763. def default_template_name(action_name = self.action_name)
  9764. if action_name
  9765. action_name = action_name.to_s
  9766. if action_name.include?('/') && template_path_includes_controller?(action_name)
  9767. action_name = strip_out_controller(action_name)
  9768. end
  9769. end
  9770. "#{self.class.controller_path}/#{action_name}"
  9771. end
  9772. def strip_out_controller(path)
  9773. path.split('/', 2).last
  9774. end
  9775. def template_path_includes_controller?(path)
  9776. self.class.controller_path.split('/')[-1] == path.split('/')[0]
  9777. end
  9778. def process_cleanup
  9779. close_session
  9780. end
  9781. end
  9782. end
  9783. require 'benchmark'
  9784. module ActionController #:nodoc:
  9785. # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
  9786. # package has been included, a separate timing section for database calls will be added as well.
  9787. module Benchmarking #:nodoc:
  9788. def self.included(base)
  9789. base.extend(ClassMethods)
  9790. base.class_eval do
  9791. alias_method :perform_action_without_benchmark, :perform_action
  9792. alias_method :perform_action, :perform_action_with_benchmark
  9793. alias_method :render_without_benchmark, :render
  9794. alias_method :render, :render_with_benchmark
  9795. end
  9796. end
  9797. module ClassMethods
  9798. # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
  9799. # (unless <tt>use_silence</tt> is set to false).
  9800. #
  9801. # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
  9802. # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
  9803. # will only be conducted if the log level is low enough.
  9804. def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
  9805. if logger && logger.level == log_level
  9806. result = nil
  9807. seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
  9808. logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
  9809. result
  9810. else
  9811. yield
  9812. end
  9813. end
  9814. # Silences the logger for the duration of the block.
  9815. def silence
  9816. old_logger_level, logger.level = logger.level, Logger::ERROR if logger
  9817. yield
  9818. ensure
  9819. logger.level = old_logger_level if logger
  9820. end
  9821. end
  9822. def render_with_benchmark(options = nil, deprecated_status = nil, &block)
  9823. unless logger
  9824. render_without_benchmark(options, deprecated_status, &block)
  9825. else
  9826. db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
  9827. render_output = nil
  9828. @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, deprecated_status, &block) }.real
  9829. if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
  9830. @db_rt_before_render = db_runtime
  9831. @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
  9832. @rendering_runtime -= @db_rt_after_render
  9833. end
  9834. render_output
  9835. end
  9836. end
  9837. def perform_action_with_benchmark
  9838. unless logger
  9839. perform_action_without_benchmark
  9840. else
  9841. runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
  9842. log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
  9843. log_message << rendering_runtime(runtime) if @rendering_runtime
  9844. log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
  9845. log_message << " | #{headers["Status"]}"
  9846. log_message << " [#{complete_request_uri rescue "unknown"}]"
  9847. logger.info(log_message)
  9848. end
  9849. end
  9850. private
  9851. def rendering_runtime(runtime)
  9852. " | Rendering: #{sprintf("%.5f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime * 100) / runtime)}%)"
  9853. end
  9854. def active_record_runtime(runtime)
  9855. db_runtime = ActiveRecord::Base.connection.reset_runtime
  9856. db_runtime += @db_rt_before_render if @db_rt_before_render
  9857. db_runtime += @db_rt_after_render if @db_rt_after_render
  9858. db_percentage = (db_runtime * 100) / runtime
  9859. " | DB: #{sprintf("%.5f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
  9860. end
  9861. end
  9862. end
  9863. require 'fileutils'
  9864. module ActionController #:nodoc:
  9865. # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
  9866. # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
  9867. #
  9868. # You can read more about each approach and the sweeping assistance by clicking the modules below.
  9869. #
  9870. # Note: To turn off all caching and sweeping, set Base.perform_caching = false.
  9871. module Caching
  9872. def self.included(base) #:nodoc:
  9873. base.send(:include, Pages, Actions, Fragments, Sweeping)
  9874. base.class_eval do
  9875. @@perform_caching = true
  9876. cattr_accessor :perform_caching
  9877. end
  9878. end
  9879. # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
  9880. # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
  9881. # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
  9882. # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
  9883. # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
  9884. #
  9885. # Specifying which actions to cache is done through the <tt>caches</tt> class method:
  9886. #
  9887. # class WeblogController < ActionController::Base
  9888. # caches_page :show, :new
  9889. # end
  9890. #
  9891. # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
  9892. # generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
  9893. # the Action Pack to generate it.
  9894. #
  9895. # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
  9896. # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
  9897. #
  9898. # class WeblogController < ActionController::Base
  9899. # def update
  9900. # List.update(params[:list][:id], params[:list])
  9901. # expire_page :action => "show", :id => params[:list][:id]
  9902. # redirect_to :action => "show", :id => params[:list][:id]
  9903. # end
  9904. # end
  9905. #
  9906. # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
  9907. # expired.
  9908. #
  9909. # == Setting the cache directory
  9910. #
  9911. # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
  9912. # For Rails, this directory has already been set to RAILS_ROOT + "/public".
  9913. #
  9914. # == Setting the cache extension
  9915. #
  9916. # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
  9917. # something else, like .php or .shtml, just set Base.page_cache_extension.
  9918. module Pages
  9919. def self.included(base) #:nodoc:
  9920. base.extend(ClassMethods)
  9921. base.class_eval do
  9922. @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
  9923. cattr_accessor :page_cache_directory
  9924. @@page_cache_extension = '.html'
  9925. cattr_accessor :page_cache_extension
  9926. end
  9927. end
  9928. module ClassMethods
  9929. # Expires the page that was cached with the +path+ as a key. Example:
  9930. # expire_page "/lists/show"
  9931. def expire_page(path)
  9932. return unless perform_caching
  9933. benchmark "Expired page: #{page_cache_file(path)}" do
  9934. File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
  9935. end
  9936. end
  9937. # Manually cache the +content+ in the key determined by +path+. Example:
  9938. # cache_page "I'm the cached content", "/lists/show"
  9939. def cache_page(content, path)
  9940. return unless perform_caching
  9941. benchmark "Cached page: #{page_cache_file(path)}" do
  9942. FileUtils.makedirs(File.dirname(page_cache_path(path)))
  9943. File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
  9944. end
  9945. end
  9946. # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
  9947. # matches the triggering url.
  9948. def caches_page(*actions)
  9949. return unless perform_caching
  9950. actions.each do |action|
  9951. class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
  9952. end
  9953. end
  9954. private
  9955. def page_cache_file(path)
  9956. name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
  9957. name << page_cache_extension unless (name.split('/').last || name).include? '.'
  9958. return name
  9959. end
  9960. def page_cache_path(path)
  9961. page_cache_directory + page_cache_file(path)
  9962. end
  9963. end
  9964. # Expires the page that was cached with the +options+ as a key. Example:
  9965. # expire_page :controller => "lists", :action => "show"
  9966. def expire_page(options = {})
  9967. return unless perform_caching
  9968. if options[:action].is_a?(Array)
  9969. options[:action].dup.each do |action|
  9970. self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true, :action => action })))
  9971. end
  9972. else
  9973. self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
  9974. end
  9975. end
  9976. # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used
  9977. # If no options are provided, the current +options+ for this action is used. Example:
  9978. # cache_page "I'm the cached content", :controller => "lists", :action => "show"
  9979. def cache_page(content = nil, options = {})
  9980. return unless perform_caching && caching_allowed
  9981. self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
  9982. end
  9983. private
  9984. def caching_allowed
  9985. !@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400
  9986. end
  9987. end
  9988. # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
  9989. # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
  9990. # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
  9991. #
  9992. # class ListsController < ApplicationController
  9993. # before_filter :authenticate, :except => :public
  9994. # caches_page :public
  9995. # caches_action :show, :feed
  9996. # end
  9997. #
  9998. # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
  9999. # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
  10000. #
  10001. # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
  10002. # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
  10003. # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
  10004. # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
  10005. module Actions
  10006. def self.append_features(base) #:nodoc:
  10007. super
  10008. base.extend(ClassMethods)
  10009. base.send(:attr_accessor, :rendered_action_cache)
  10010. end
  10011. module ClassMethods #:nodoc:
  10012. def caches_action(*actions)
  10013. return unless perform_caching
  10014. around_filter(ActionCacheFilter.new(*actions))
  10015. end
  10016. end
  10017. def expire_action(options = {})
  10018. return unless perform_caching
  10019. if options[:action].is_a?(Array)
  10020. options[:action].dup.each do |action|
  10021. expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
  10022. end
  10023. else
  10024. expire_fragment(url_for(options).split("://").last)
  10025. end
  10026. end
  10027. class ActionCacheFilter #:nodoc:
  10028. def initialize(*actions)
  10029. @actions = actions
  10030. end
  10031. def before(controller)
  10032. return unless @actions.include?(controller.action_name.intern)
  10033. if cache = controller.read_fragment(controller.url_for.split("://").last)
  10034. controller.rendered_action_cache = true
  10035. controller.send(:render_text, cache)
  10036. false
  10037. end
  10038. end
  10039. def after(controller)
  10040. return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
  10041. controller.write_fragment(controller.url_for.split("://").last, controller.response.body)
  10042. end
  10043. end
  10044. end
  10045. # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
  10046. # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
  10047. # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
  10048. #
  10049. # <b>Hello <%= @name %></b>
  10050. # <% cache do %>
  10051. # All the topics in the system:
  10052. # <%= render_collection_of_partials "topic", Topic.find_all %>
  10053. # <% end %>
  10054. #
  10055. # This cache will bind to the name of action that called it. So you would be able to invalidate it using
  10056. # <tt>expire_fragment(:controller => "topics", :action => "list")</tt> -- if that was the controller/action used. This is not too helpful
  10057. # if you need to cache multiple fragments per action or if the action itself is cached using <tt>caches_action</tt>. So instead we should
  10058. # qualify the name of the action used with something like:
  10059. #
  10060. # <% cache(:action => "list", :action_suffix => "all_topics") do %>
  10061. #
  10062. # That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another
  10063. # fragment using a different suffix. Note that the URL doesn't have to really exist or be callable. We're just using the url_for system
  10064. # to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be
  10065. # <tt>expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")</tt>.
  10066. #
  10067. # == Fragment stores
  10068. #
  10069. # In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store
  10070. # of which there are four different kinds:
  10071. #
  10072. # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and shares the fragments for
  10073. # all the web server processes running off the same application directory.
  10074. # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
  10075. # own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
  10076. # up a lot of memory since each process keeps all the caches in memory.
  10077. # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
  10078. # around for all processes, but requires that you run and manage a separate DRb process.
  10079. # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
  10080. # Requires the ruby-memcache library: gem install ruby-memcache.
  10081. #
  10082. # Configuration examples (MemoryStore is the default):
  10083. #
  10084. # ActionController::Base.fragment_cache_store = :memory_store
  10085. # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
  10086. # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
  10087. # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
  10088. # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
  10089. module Fragments
  10090. def self.append_features(base) #:nodoc:
  10091. super
  10092. base.class_eval do
  10093. @@fragment_cache_store = MemoryStore.new
  10094. cattr_reader :fragment_cache_store
  10095. def self.fragment_cache_store=(store_option)
  10096. store, *parameters = *([ store_option ].flatten)
  10097. @@fragment_cache_store = if store.is_a?(Symbol)
  10098. store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
  10099. store_class = ActionController::Caching::Fragments.const_get(store_class_name)
  10100. store_class.new(*parameters)
  10101. else
  10102. store
  10103. end
  10104. end
  10105. end
  10106. end
  10107. def fragment_cache_key(name)
  10108. name.is_a?(Hash) ? url_for(name).split("://").last : name
  10109. end
  10110. # Called by CacheHelper#cache
  10111. def cache_erb_fragment(block, name = {}, options = nil)
  10112. unless perform_caching then block.call; return end
  10113. buffer = eval("_erbout", block.binding)
  10114. if cache = read_fragment(name, options)
  10115. buffer.concat(cache)
  10116. else
  10117. pos = buffer.length
  10118. block.call
  10119. write_fragment(name, buffer[pos..-1], options)
  10120. end
  10121. end
  10122. def write_fragment(name, content, options = nil)
  10123. return unless perform_caching
  10124. key = fragment_cache_key(name)
  10125. self.class.benchmark "Cached fragment: #{key}" do
  10126. fragment_cache_store.write(key, content, options)
  10127. end
  10128. content
  10129. end
  10130. def read_fragment(name, options = nil)
  10131. return unless perform_caching
  10132. key = fragment_cache_key(name)
  10133. self.class.benchmark "Fragment read: #{key}" do
  10134. fragment_cache_store.read(key, options)
  10135. end
  10136. end
  10137. # Name can take one of three forms:
  10138. # * String: This would normally take the form of a path like "pages/45/notes"
  10139. # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
  10140. # * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache
  10141. def expire_fragment(name, options = nil)
  10142. return unless perform_caching
  10143. key = fragment_cache_key(name)
  10144. if key.is_a?(Regexp)
  10145. self.class.benchmark "Expired fragments matching: #{key.source}" do
  10146. fragment_cache_store.delete_matched(key, options)
  10147. end
  10148. else
  10149. self.class.benchmark "Expired fragment: #{key}" do
  10150. fragment_cache_store.delete(key, options)
  10151. end
  10152. end
  10153. end
  10154. # Deprecated -- just call expire_fragment with a regular expression
  10155. def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
  10156. expire_fragment(matcher, options)
  10157. end
  10158. class UnthreadedMemoryStore #:nodoc:
  10159. def initialize #:nodoc:
  10160. @data = {}
  10161. end
  10162. def read(name, options=nil) #:nodoc:
  10163. @data[name]
  10164. end
  10165. def write(name, value, options=nil) #:nodoc:
  10166. @data[name] = value
  10167. end
  10168. def delete(name, options=nil) #:nodoc:
  10169. @data.delete(name)
  10170. end
  10171. def delete_matched(matcher, options=nil) #:nodoc:
  10172. @data.delete_if { |k,v| k =~ matcher }
  10173. end
  10174. end
  10175. module ThreadSafety #:nodoc:
  10176. def read(name, options=nil) #:nodoc:
  10177. @mutex.synchronize { super }
  10178. end
  10179. def write(name, value, options=nil) #:nodoc:
  10180. @mutex.synchronize { super }
  10181. end
  10182. def delete(name, options=nil) #:nodoc:
  10183. @mutex.synchronize { super }
  10184. end
  10185. def delete_matched(matcher, options=nil) #:nodoc:
  10186. @mutex.synchronize { super }
  10187. end
  10188. end
  10189. class MemoryStore < UnthreadedMemoryStore #:nodoc:
  10190. def initialize #:nodoc:
  10191. super
  10192. if ActionController::Base.allow_concurrency
  10193. @mutex = Mutex.new
  10194. MemoryStore.send(:include, ThreadSafety)
  10195. end
  10196. end
  10197. end
  10198. class DRbStore < MemoryStore #:nodoc:
  10199. attr_reader :address
  10200. def initialize(address = 'druby://localhost:9192')
  10201. super()
  10202. @address = address
  10203. @data = DRbObject.new(nil, address)
  10204. end
  10205. end
  10206. class MemCacheStore < MemoryStore #:nodoc:
  10207. attr_reader :addresses
  10208. def initialize(*addresses)
  10209. super()
  10210. addresses = addresses.flatten
  10211. addresses = ["localhost"] if addresses.empty?
  10212. @addresses = addresses
  10213. @data = MemCache.new(*addresses)
  10214. end
  10215. end
  10216. class UnthreadedFileStore #:nodoc:
  10217. attr_reader :cache_path
  10218. def initialize(cache_path)
  10219. @cache_path = cache_path
  10220. end
  10221. def write(name, value, options = nil) #:nodoc:
  10222. ensure_cache_path(File.dirname(real_file_path(name)))
  10223. File.open(real_file_path(name), "wb+") { |f| f.write(value) }
  10224. rescue => e
  10225. Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
  10226. end
  10227. def read(name, options = nil) #:nodoc:
  10228. File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
  10229. end
  10230. def delete(name, options) #:nodoc:
  10231. File.delete(real_file_path(name))
  10232. rescue SystemCallError => e
  10233. # If there's no cache, then there's nothing to complain about
  10234. end
  10235. def delete_matched(matcher, options) #:nodoc:
  10236. search_dir(@cache_path) do |f|
  10237. if f =~ matcher
  10238. begin
  10239. File.delete(f)
  10240. rescue Object => e
  10241. # If there's no cache, then there's nothing to complain about
  10242. end
  10243. end
  10244. end
  10245. end
  10246. private
  10247. def real_file_path(name)
  10248. '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
  10249. end
  10250. def ensure_cache_path(path)
  10251. FileUtils.makedirs(path) unless File.exists?(path)
  10252. end
  10253. def search_dir(dir, &callback)
  10254. Dir.foreach(dir) do |d|
  10255. next if d == "." || d == ".."
  10256. name = File.join(dir, d)
  10257. if File.directory?(name)
  10258. search_dir(name, &callback)
  10259. else
  10260. callback.call name
  10261. end
  10262. end
  10263. end
  10264. end
  10265. class FileStore < UnthreadedFileStore #:nodoc:
  10266. def initialize(cache_path)
  10267. super(cache_path)
  10268. if ActionController::Base.allow_concurrency
  10269. @mutex = Mutex.new
  10270. FileStore.send(:include, ThreadSafety)
  10271. end
  10272. end
  10273. end
  10274. end
  10275. # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
  10276. # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
  10277. #
  10278. # class ListSweeper < ActionController::Caching::Sweeper
  10279. # observe List, Item
  10280. #
  10281. # def after_save(record)
  10282. # list = record.is_a?(List) ? record : record.list
  10283. # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
  10284. # expire_action(:controller => "lists", :action => "all")
  10285. # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
  10286. # end
  10287. # end
  10288. #
  10289. # The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
  10290. #
  10291. # class ListsController < ApplicationController
  10292. # caches_action :index, :show, :public, :feed
  10293. # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
  10294. # end
  10295. #
  10296. # In the example above, four actions are cached and three actions are responsible for expiring those caches.
  10297. module Sweeping
  10298. def self.append_features(base) #:nodoc:
  10299. super
  10300. base.extend(ClassMethods)
  10301. end
  10302. module ClassMethods #:nodoc:
  10303. def cache_sweeper(*sweepers)
  10304. return unless perform_caching
  10305. configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
  10306. sweepers.each do |sweeper|
  10307. observer(sweeper)
  10308. sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
  10309. if sweeper_instance.is_a?(Sweeper)
  10310. around_filter(sweeper_instance, :only => configuration[:only])
  10311. else
  10312. after_filter(sweeper_instance, :only => configuration[:only])
  10313. end
  10314. end
  10315. end
  10316. end
  10317. end
  10318. if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
  10319. class Sweeper < ActiveRecord::Observer #:nodoc:
  10320. attr_accessor :controller
  10321. # ActiveRecord::Observer will mark this class as reloadable even though it should not be.
  10322. # However, subclasses of ActionController::Caching::Sweeper should be Reloadable
  10323. include Reloadable::Subclasses
  10324. def before(controller)
  10325. self.controller = controller
  10326. callback(:before)
  10327. end
  10328. def after(controller)
  10329. callback(:after)
  10330. # Clean up, so that the controller can be collected after this request
  10331. self.controller = nil
  10332. end
  10333. private
  10334. def callback(timing)
  10335. controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
  10336. action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
  10337. send(controller_callback_method_name) if respond_to?(controller_callback_method_name)
  10338. send(action_callback_method_name) if respond_to?(action_callback_method_name)
  10339. end
  10340. def method_missing(method, *arguments)
  10341. return if @controller.nil?
  10342. @controller.send(method, *arguments)
  10343. end
  10344. end
  10345. end
  10346. end
  10347. end
  10348. require 'cgi'
  10349. require 'cgi/session'
  10350. require 'cgi/session/pstore'
  10351. require 'action_controller/cgi_ext/cgi_methods'
  10352. # Wrapper around the CGIMethods that have been secluded to allow testing without
  10353. # an instantiated CGI object
  10354. class CGI #:nodoc:
  10355. class << self
  10356. alias :escapeHTML_fail_on_nil :escapeHTML
  10357. def escapeHTML(string)
  10358. escapeHTML_fail_on_nil(string) unless string.nil?
  10359. end
  10360. end
  10361. # Returns a parameter hash including values from both the request (POST/GET)
  10362. # and the query string with the latter taking precedence.
  10363. def parameters
  10364. request_parameters.update(query_parameters)
  10365. end
  10366. def query_parameters
  10367. CGIMethods.parse_query_parameters(query_string)
  10368. end
  10369. def request_parameters
  10370. CGIMethods.parse_request_parameters(params, env_table)
  10371. end
  10372. def redirect(where)
  10373. header({
  10374. "Status" => "302 Moved",
  10375. "location" => "#{where}"
  10376. })
  10377. end
  10378. def session(parameters = nil)
  10379. parameters = {} if parameters.nil?
  10380. parameters['database_manager'] = CGI::Session::PStore
  10381. CGI::Session.new(self, parameters)
  10382. end
  10383. end
  10384. require 'cgi'
  10385. require 'action_controller/vendor/xml_simple'
  10386. require 'action_controller/vendor/xml_node'
  10387. # Static methods for parsing the query and request parameters that can be used in
  10388. # a CGI extension class or testing in isolation.
  10389. class CGIMethods #:nodoc:
  10390. public
  10391. # Returns a hash with the pairs from the query string. The implicit hash construction that is done in
  10392. # parse_request_params is not done here.
  10393. def CGIMethods.parse_query_parameters(query_string)
  10394. parsed_params = {}
  10395. query_string.split(/[&;]/).each { |p|
  10396. # Ignore repeated delimiters.
  10397. next if p.empty?
  10398. k, v = p.split('=',2)
  10399. v = nil if (v && v.empty?)
  10400. k = CGI.unescape(k) if k
  10401. v = CGI.unescape(v) if v
  10402. unless k.include?(?[)
  10403. parsed_params[k] = v
  10404. else
  10405. keys = split_key(k)
  10406. last_key = keys.pop
  10407. last_key = keys.pop if (use_array = last_key.empty?)
  10408. parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
  10409. if use_array then (parent[last_key] ||= []) << v
  10410. else parent[last_key] = v
  10411. end
  10412. end
  10413. }
  10414. parsed_params
  10415. end
  10416. # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
  10417. # "Somewhere cool!" are translated into a full hash hierarchy, like
  10418. # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
  10419. def CGIMethods.parse_request_parameters(params)
  10420. parsed_params = {}
  10421. for key, value in params
  10422. value = [value] if key =~ /.*\[\]$/
  10423. unless key.include?('[')
  10424. # much faster to test for the most common case first (GET)
  10425. # and avoid the call to build_deep_hash
  10426. parsed_params[key] = get_typed_value(value[0])
  10427. else
  10428. build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
  10429. end
  10430. end
  10431. parsed_params
  10432. end
  10433. def self.parse_formatted_request_parameters(mime_type, raw_post_data)
  10434. params = case strategy = ActionController::Base.param_parsers[mime_type]
  10435. when Proc
  10436. strategy.call(raw_post_data)
  10437. when :xml_simple
  10438. raw_post_data.blank? ? nil :
  10439. typecast_xml_value(XmlSimple.xml_in(raw_post_data,
  10440. 'forcearray' => false,
  10441. 'forcecontent' => true,
  10442. 'keeproot' => true,
  10443. 'contentkey' => '__content__'))
  10444. when :yaml
  10445. YAML.load(raw_post_data)
  10446. when :xml_node
  10447. node = XmlNode.from_xml(raw_post_data)
  10448. { node.node_name => node }
  10449. end
  10450. dasherize_keys(params || {})
  10451. rescue Object => e
  10452. { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
  10453. "raw_post_data" => raw_post_data, "format" => mime_type }
  10454. end
  10455. def self.typecast_xml_value(value)
  10456. case value
  10457. when Hash
  10458. if value.has_key?("__content__")
  10459. content = translate_xml_entities(value["__content__"])
  10460. case value["type"]
  10461. when "integer" then content.to_i
  10462. when "boolean" then content == "true"
  10463. when "datetime" then Time.parse(content)
  10464. when "date" then Date.parse(content)
  10465. else content
  10466. end
  10467. else
  10468. value.empty? ? nil : value.inject({}) do |h,(k,v)|
  10469. h[k] = typecast_xml_value(v)
  10470. h
  10471. end
  10472. end
  10473. when Array
  10474. value.map! { |i| typecast_xml_value(i) }
  10475. case value.length
  10476. when 0 then nil
  10477. when 1 then value.first
  10478. else value
  10479. end
  10480. else
  10481. raise "can't typecast #{value.inspect}"
  10482. end
  10483. end
  10484. private
  10485. def self.translate_xml_entities(value)
  10486. value.gsub(/&lt;/, "<").
  10487. gsub(/&gt;/, ">").
  10488. gsub(/&quot;/, '"').
  10489. gsub(/&apos;/, "'").
  10490. gsub(/&amp;/, "&")
  10491. end
  10492. def self.dasherize_keys(params)
  10493. case params.class.to_s
  10494. when "Hash"
  10495. params.inject({}) do |h,(k,v)|
  10496. h[k.to_s.tr("-", "_")] = dasherize_keys(v)
  10497. h
  10498. end
  10499. when "Array"
  10500. params.map { |v| dasherize_keys(v) }
  10501. else
  10502. params
  10503. end
  10504. end
  10505. # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
  10506. # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
  10507. # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
  10508. def CGIMethods.split_key(key)
  10509. if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
  10510. keys = [$1]
  10511. keys.concat($2[1..-2].split(']['))
  10512. keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
  10513. keys
  10514. else
  10515. [key]
  10516. end
  10517. end
  10518. def CGIMethods.get_typed_value(value)
  10519. # test most frequent case first
  10520. if value.is_a?(String)
  10521. value
  10522. elsif value.respond_to?(:content_type) && ! value.content_type.blank?
  10523. # Uploaded file
  10524. unless value.respond_to?(:full_original_filename)
  10525. class << value
  10526. alias_method :full_original_filename, :original_filename
  10527. # Take the basename of the upload's original filename.
  10528. # This handles the full Windows paths given by Internet Explorer
  10529. # (and perhaps other broken user agents) without affecting
  10530. # those which give the lone filename.
  10531. # The Windows regexp is adapted from Perl's File::Basename.
  10532. def original_filename
  10533. if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
  10534. md.captures.first
  10535. else
  10536. File.basename full_original_filename
  10537. end
  10538. end
  10539. end
  10540. end
  10541. # Return the same value after overriding original_filename.
  10542. value
  10543. elsif value.respond_to?(:read)
  10544. # Value as part of a multipart request
  10545. value.read
  10546. elsif value.class == Array
  10547. value.collect { |v| CGIMethods.get_typed_value(v) }
  10548. else
  10549. # other value (neither string nor a multipart request)
  10550. value.to_s
  10551. end
  10552. end
  10553. PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
  10554. def CGIMethods.get_levels(key)
  10555. all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
  10556. if main.nil?
  10557. []
  10558. elsif trailing
  10559. [key]
  10560. elsif bracketed
  10561. [main] + bracketed.slice(1...-1).split('][')
  10562. else
  10563. [main]
  10564. end
  10565. end
  10566. def CGIMethods.build_deep_hash(value, hash, levels)
  10567. if levels.length == 0
  10568. value
  10569. elsif hash.nil?
  10570. { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
  10571. else
  10572. hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
  10573. end
  10574. end
  10575. end
  10576. CGI.module_eval { remove_const "Cookie" }
  10577. class CGI #:nodoc:
  10578. # This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below.
  10579. # It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on
  10580. # http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
  10581. class Cookie < DelegateClass(Array)
  10582. # Create a new CGI::Cookie object.
  10583. #
  10584. # The contents of the cookie can be specified as a +name+ and one
  10585. # or more +value+ arguments. Alternatively, the contents can
  10586. # be specified as a single hash argument. The possible keywords of
  10587. # this hash are as follows:
  10588. #
  10589. # name:: the name of the cookie. Required.
  10590. # value:: the cookie's value or list of values.
  10591. # path:: the path for which this cookie applies. Defaults to the
  10592. # base directory of the CGI script.
  10593. # domain:: the domain for which this cookie applies.
  10594. # expires:: the time at which this cookie expires, as a +Time+ object.
  10595. # secure:: whether this cookie is a secure cookie or not (default to
  10596. # false). Secure cookies are only transmitted to HTTPS
  10597. # servers.
  10598. #
  10599. # These keywords correspond to attributes of the cookie object.
  10600. def initialize(name = '', *value)
  10601. if name.kind_of?(String)
  10602. @name = name
  10603. @value = Array(value)
  10604. @domain = nil
  10605. @expires = nil
  10606. @secure = false
  10607. @path = nil
  10608. else
  10609. @name = name['name']
  10610. @value = Array(name['value'])
  10611. @domain = name['domain']
  10612. @expires = name['expires']
  10613. @secure = name['secure'] || false
  10614. @path = name['path']
  10615. end
  10616. unless @name
  10617. raise ArgumentError, "`name' required"
  10618. end
  10619. # simple support for IE
  10620. unless @path
  10621. %r|^(.*/)|.match(ENV['SCRIPT_NAME'])
  10622. @path = ($1 or '')
  10623. end
  10624. super(@value)
  10625. end
  10626. def __setobj__(obj)
  10627. @_dc_obj = obj
  10628. end
  10629. attr_accessor("name", "value", "path", "domain", "expires")
  10630. attr_reader("secure")
  10631. # Set whether the Cookie is a secure cookie or not.
  10632. #
  10633. # +val+ must be a boolean.
  10634. def secure=(val)
  10635. @secure = val if val == true or val == false
  10636. @secure
  10637. end
  10638. # Convert the Cookie to its string representation.
  10639. def to_s
  10640. buf = ""
  10641. buf << @name << '='
  10642. if @value.kind_of?(String)
  10643. buf << CGI::escape(@value)
  10644. else
  10645. buf << @value.collect{|v| CGI::escape(v) }.join("&")
  10646. end
  10647. if @domain
  10648. buf << '; domain=' << @domain
  10649. end
  10650. if @path
  10651. buf << '; path=' << @path
  10652. end
  10653. if @expires
  10654. buf << '; expires=' << CGI::rfc1123_date(@expires)
  10655. end
  10656. if @secure == true
  10657. buf << '; secure'
  10658. end
  10659. buf
  10660. end
  10661. # Parse a raw cookie string into a hash of cookie-name=>Cookie
  10662. # pairs.
  10663. #
  10664. # cookies = CGI::Cookie::parse("raw_cookie_string")
  10665. # # { "name1" => cookie1, "name2" => cookie2, ... }
  10666. #
  10667. def self.parse(raw_cookie)
  10668. cookies = Hash.new([])
  10669. if raw_cookie
  10670. raw_cookie.split(/; ?/).each do |pairs|
  10671. name, values = pairs.split('=',2)
  10672. next unless name and values
  10673. name = CGI::unescape(name)
  10674. values = values.split('&').collect!{|v| CGI::unescape(v) }
  10675. unless cookies.has_key?(name)
  10676. cookies[name] = new(name, *values)
  10677. end
  10678. end
  10679. end
  10680. cookies
  10681. end
  10682. end # class Cookie
  10683. end
  10684. class CGI #:nodoc:
  10685. # Add @request.env['RAW_POST_DATA'] for the vegans.
  10686. module QueryExtension
  10687. # Initialize the data from the query.
  10688. #
  10689. # Handles multipart forms (in particular, forms that involve file uploads).
  10690. # Reads query parameters in the @params field, and cookies into @cookies.
  10691. def initialize_query()
  10692. @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
  10693. #fix some strange request environments
  10694. if method = env_table['REQUEST_METHOD']
  10695. method = method.to_s.downcase.intern
  10696. else
  10697. method = :get
  10698. end
  10699. if method == :post && (boundary = multipart_form_boundary)
  10700. @multipart = true
  10701. @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
  10702. else
  10703. @multipart = false
  10704. @params = CGI::parse(read_query_params(method) || "")
  10705. end
  10706. end
  10707. private
  10708. unless defined?(MULTIPART_FORM_BOUNDARY_RE)
  10709. MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
  10710. end
  10711. def multipart_form_boundary
  10712. MULTIPART_FORM_BOUNDARY_RE.match(env_table['CONTENT_TYPE']).to_a.pop
  10713. end
  10714. if defined? MOD_RUBY
  10715. def read_params_from_query
  10716. Apache::request.args || ''
  10717. end
  10718. else
  10719. def read_params_from_query
  10720. # fixes CGI querystring parsing for lighttpd
  10721. env_qs = env_table['QUERY_STRING']
  10722. if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
  10723. uri.split('?', 2)[1] || ''
  10724. else
  10725. env_qs
  10726. end
  10727. end
  10728. end
  10729. def read_params_from_post
  10730. stdinput.binmode if stdinput.respond_to?(:binmode)
  10731. content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || ''
  10732. # fix for Safari Ajax postings that always append \000
  10733. content.chop! if content[-1] == 0
  10734. content.gsub! /&_=$/, ''
  10735. env_table['RAW_POST_DATA'] = content.freeze
  10736. end
  10737. def read_query_params(method)
  10738. case method
  10739. when :get
  10740. read_params_from_query
  10741. when :post, :put
  10742. read_params_from_post
  10743. when :cmd
  10744. read_from_cmdline
  10745. else # when :head, :delete, :options
  10746. read_params_from_query
  10747. end
  10748. end
  10749. end # module QueryExtension
  10750. end
  10751. require 'action_controller/cgi_ext/cgi_ext'
  10752. require 'action_controller/cgi_ext/cookie_performance_fix'
  10753. require 'action_controller/cgi_ext/raw_post_data_fix'
  10754. module ActionController #:nodoc:
  10755. class Base
  10756. # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
  10757. # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
  10758. #
  10759. # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
  10760. # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
  10761. # lib/action_controller/session.
  10762. # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
  10763. # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
  10764. # of the request, or automatically generated for a new session.
  10765. # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
  10766. # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
  10767. # an ArgumentError is raised.
  10768. # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
  10769. # indefinitely.
  10770. # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
  10771. # server.
  10772. # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
  10773. # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
  10774. def self.process_cgi(cgi = CGI.new, session_options = {})
  10775. new.process_cgi(cgi, session_options)
  10776. end
  10777. def process_cgi(cgi, session_options = {}) #:nodoc:
  10778. process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
  10779. end
  10780. end
  10781. class CgiRequest < AbstractRequest #:nodoc:
  10782. attr_accessor :cgi, :session_options
  10783. DEFAULT_SESSION_OPTIONS = {
  10784. :database_manager => CGI::Session::PStore,
  10785. :prefix => "ruby_sess.",
  10786. :session_path => "/"
  10787. } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
  10788. def initialize(cgi, session_options = {})
  10789. @cgi = cgi
  10790. @session_options = session_options
  10791. @env = @cgi.send(:env_table)
  10792. super()
  10793. end
  10794. def query_string
  10795. if (qs = @cgi.query_string) && !qs.empty?
  10796. qs
  10797. elsif uri = @env['REQUEST_URI']
  10798. parts = uri.split('?')
  10799. parts.shift
  10800. parts.join('?')
  10801. else
  10802. @env['QUERY_STRING'] || ''
  10803. end
  10804. end
  10805. def query_parameters
  10806. (qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
  10807. end
  10808. def request_parameters
  10809. @request_parameters ||=
  10810. if ActionController::Base.param_parsers.has_key?(content_type)
  10811. CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA'])
  10812. else
  10813. CGIMethods.parse_request_parameters(@cgi.params)
  10814. end
  10815. end
  10816. def cookies
  10817. @cgi.cookies.freeze
  10818. end
  10819. def host_with_port
  10820. if forwarded = env["HTTP_X_FORWARDED_HOST"]
  10821. forwarded.split(/,\s?/).last
  10822. elsif http_host = env['HTTP_HOST']
  10823. http_host
  10824. elsif server_name = env['SERVER_NAME']
  10825. server_name
  10826. else
  10827. "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
  10828. end
  10829. end
  10830. def host
  10831. host_with_port[/^[^:]+/]
  10832. end
  10833. def port
  10834. if host_with_port =~ /:(\d+)$/
  10835. $1.to_i
  10836. else
  10837. standard_port
  10838. end
  10839. end
  10840. def session
  10841. unless @session
  10842. if @session_options == false
  10843. @session = Hash.new
  10844. else
  10845. stale_session_check! do
  10846. if session_options_with_string_keys['new_session'] == true
  10847. @session = new_session
  10848. else
  10849. @session = CGI::Session.new(@cgi, session_options_with_string_keys)
  10850. end
  10851. @session['__valid_session']
  10852. end
  10853. end
  10854. end
  10855. @session
  10856. end
  10857. def reset_session
  10858. @session.delete if CGI::Session === @session
  10859. @session = new_session
  10860. end
  10861. def method_missing(method_id, *arguments)
  10862. @cgi.send(method_id, *arguments) rescue super
  10863. end
  10864. private
  10865. # Delete an old session if it exists then create a new one.
  10866. def new_session
  10867. if @session_options == false
  10868. Hash.new
  10869. else
  10870. CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
  10871. CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
  10872. end
  10873. end
  10874. def stale_session_check!
  10875. yield
  10876. rescue ArgumentError => argument_error
  10877. if argument_error.message =~ %r{undefined class/module (\w+)}
  10878. begin
  10879. Module.const_missing($1)
  10880. rescue LoadError, NameError => const_error
  10881. raise ActionController::SessionRestoreError, <<end_msg
  10882. Session contains objects whose class definition isn\'t available.
  10883. Remember to require the classes for all objects kept in the session.
  10884. (Original exception: #{const_error.message} [#{const_error.class}])
  10885. end_msg
  10886. end
  10887. retry
  10888. else
  10889. raise
  10890. end
  10891. end
  10892. def session_options_with_string_keys
  10893. @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
  10894. end
  10895. end
  10896. class CgiResponse < AbstractResponse #:nodoc:
  10897. def initialize(cgi)
  10898. @cgi = cgi
  10899. super()
  10900. end
  10901. def out(output = $stdout)
  10902. convert_content_type!(@headers)
  10903. output.binmode if output.respond_to?(:binmode)
  10904. output.sync = false if output.respond_to?(:sync=)
  10905. begin
  10906. output.write(@cgi.header(@headers))
  10907. if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
  10908. return
  10909. elsif @body.respond_to?(:call)
  10910. @body.call(self, output)
  10911. else
  10912. output.write(@body)
  10913. end
  10914. output.flush if output.respond_to?(:flush)
  10915. rescue Errno::EPIPE => e
  10916. # lost connection to the FCGI process -- ignore the output, then
  10917. end
  10918. end
  10919. private
  10920. def convert_content_type!(headers)
  10921. if header = headers.delete("Content-Type")
  10922. headers["type"] = header
  10923. end
  10924. if header = headers.delete("Content-type")
  10925. headers["type"] = header
  10926. end
  10927. if header = headers.delete("content-type")
  10928. headers["type"] = header
  10929. end
  10930. end
  10931. end
  10932. end
  10933. module ActionController
  10934. module CodeGeneration #:nodoc:
  10935. class GenerationError < StandardError #:nodoc:
  10936. end
  10937. class Source #:nodoc:
  10938. attr_reader :lines, :indentation_level
  10939. IndentationString = ' '
  10940. def initialize
  10941. @lines, @indentation_level = [], 0
  10942. end
  10943. def line(line)
  10944. @lines << (IndentationString * @indentation_level + line)
  10945. end
  10946. alias :<< :line
  10947. def indent
  10948. @indentation_level += 1
  10949. yield
  10950. ensure
  10951. @indentation_level -= 1
  10952. end
  10953. def to_s() lines.join("\n") end
  10954. end
  10955. class CodeGenerator #:nodoc:
  10956. attr_accessor :source, :locals
  10957. def initialize(source = nil)
  10958. @locals = []
  10959. @source = source || Source.new
  10960. end
  10961. BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym}
  10962. ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym}
  10963. Keywords = BeginKeywords + ResumeKeywords
  10964. def method_missing(keyword, *text)
  10965. if Keywords.include? keyword
  10966. if ResumeKeywords.include? keyword
  10967. raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/
  10968. source.lines.pop # Remove the 'end'
  10969. end
  10970. line "#{keyword} #{text.join ' '}"
  10971. begin source.indent { yield(self.dup) }
  10972. ensure line 'end'
  10973. end
  10974. else
  10975. super(keyword, *text)
  10976. end
  10977. end
  10978. def line(*args) self.source.line(*args) end
  10979. alias :<< :line
  10980. def indent(*args, &block) source(*args, &block) end
  10981. def to_s() source.to_s end
  10982. def share_locals_with(other)
  10983. other.locals = self.locals = (other.locals | locals)
  10984. end
  10985. FieldsToDuplicate = [:locals]
  10986. def dup
  10987. copy = self.class.new(source)
  10988. self.class::FieldsToDuplicate.each do |sym|
  10989. value = self.send(sym)
  10990. value = value.dup unless value.nil? || value.is_a?(Numeric)
  10991. copy.send("#{sym}=", value)
  10992. end
  10993. return copy
  10994. end
  10995. end
  10996. class RecognitionGenerator < CodeGenerator #:nodoc:
  10997. Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement]
  10998. attr_accessor(*Attributes)
  10999. FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
  11000. def initialize(*args)
  11001. super(*args)
  11002. @after, @before = [], []
  11003. @current = nil
  11004. @results, @constants = {}, {}
  11005. @depth = 0
  11006. @move_ahead = nil
  11007. @finish_statement = Proc.new {|hash_expr| hash_expr}
  11008. end
  11009. def if_next_matches(string, &block)
  11010. test = Routing.test_condition(next_segment(true), string)
  11011. self.if(test, &block)
  11012. end
  11013. def move_forward(places = 1)
  11014. dup = self.dup
  11015. dup.depth += 1
  11016. dup.move_ahead = places
  11017. yield dup
  11018. end
  11019. def next_segment(assign_inline = false, default = nil)
  11020. if locals.include?(segment_name)
  11021. code = segment_name
  11022. else
  11023. code = "#{segment_name} = #{path_name}[#{index_name}]"
  11024. if assign_inline
  11025. code = "(#{code})"
  11026. else
  11027. line(code)
  11028. code = segment_name
  11029. end
  11030. locals << segment_name
  11031. end
  11032. code = "(#{code} || #{default.inspect})" if default
  11033. return code.to_s
  11034. end
  11035. def segment_name() "segment#{depth}".to_sym end
  11036. def path_name() :path end
  11037. def index_name
  11038. move_ahead, @move_ahead = @move_ahead, nil
  11039. move_ahead ? "index += #{move_ahead}" : 'index'
  11040. end
  11041. def continue
  11042. dup = self.dup
  11043. dup.before << dup.current
  11044. dup.current = dup.after.shift
  11045. dup.go
  11046. end
  11047. def go
  11048. if current then current.write_recognition(self)
  11049. else self.finish
  11050. end
  11051. end
  11052. def result(key, expression, delay = false)
  11053. unless delay
  11054. line "#{key}_value = #{expression}"
  11055. expression = "#{key}_value"
  11056. end
  11057. results[key] = expression
  11058. end
  11059. def constant_result(key, object)
  11060. constants[key] = object
  11061. end
  11062. def finish(ensure_traversal_finished = true)
  11063. pairs = []
  11064. (results.keys + constants.keys).uniq.each do |key|
  11065. pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}"
  11066. end
  11067. hash_expr = "{#{pairs.join(', ')}}"
  11068. statement = finish_statement.call(hash_expr)
  11069. if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement}
  11070. else self << statement
  11071. end
  11072. end
  11073. end
  11074. class GenerationGenerator < CodeGenerator #:nodoc:
  11075. Attributes = [:after, :before, :current, :segments]
  11076. attr_accessor(*Attributes)
  11077. FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
  11078. def initialize(*args)
  11079. super(*args)
  11080. @after, @before = [], []
  11081. @current = nil
  11082. @segments = []
  11083. end
  11084. def hash_name() 'hash' end
  11085. def local_name(key) "#{key}_value" end
  11086. def hash_value(key, assign = true, default = nil)
  11087. if locals.include?(local_name(key)) then code = local_name(key)
  11088. else
  11089. code = "hash[#{key.to_sym.inspect}]"
  11090. if assign
  11091. code = "(#{local_name(key)} = #{code})"
  11092. locals << local_name(key)
  11093. end
  11094. end
  11095. code = "(#{code} || (#{default.inspect}))" if default
  11096. return code
  11097. end
  11098. def expire_for_keys(*keys)
  11099. return if keys.empty?
  11100. conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"}
  11101. line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}"
  11102. end
  11103. def add_segment(*segments)
  11104. d = dup
  11105. d.segments.concat segments
  11106. yield d
  11107. end
  11108. def go
  11109. if current then current.write_generation(self)
  11110. else self.finish
  11111. end
  11112. end
  11113. def continue
  11114. d = dup
  11115. d.before << d.current
  11116. d.current = d.after.shift
  11117. d.go
  11118. end
  11119. def finish
  11120. line %("/#{segments.join('/')}")
  11121. end
  11122. def check_conditions(conditions)
  11123. tests = []
  11124. generator = nil
  11125. conditions.each do |key, condition|
  11126. tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp
  11127. tests << Routing.test_condition((generator || self).hash_value(key, false), condition)
  11128. generator = self.dup unless generator
  11129. end
  11130. return tests.join(' && ')
  11131. end
  11132. end
  11133. end
  11134. end
  11135. module ActionController #:nodoc:
  11136. # Components allow you to call other actions for their rendered response while executing another action. You can either delegate
  11137. # the entire response rendering or you can mix a partial response in with your other content.
  11138. #
  11139. # class WeblogController < ActionController::Base
  11140. # # Performs a method and then lets hello_world output its render
  11141. # def delegate_action
  11142. # do_other_stuff_before_hello_world
  11143. # render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" }
  11144. # end
  11145. # end
  11146. #
  11147. # class GreeterController < ActionController::Base
  11148. # def hello_world
  11149. # render :text => "#{params[:person]} says, Hello World!"
  11150. # end
  11151. # end
  11152. #
  11153. # The same can be done in a view to do a partial rendering:
  11154. #
  11155. # Let's see a greeting:
  11156. # <%= render_component :controller => "greeter", :action => "hello_world" %>
  11157. #
  11158. # It is also possible to specify the controller as a class constant, bypassing the inflector
  11159. # code to compute the controller class at runtime:
  11160. #
  11161. # <%= render_component :controller => GreeterController, :action => "hello_world" %>
  11162. #
  11163. # == When to use components
  11164. #
  11165. # Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
  11166. # conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
  11167. # reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
  11168. # across many applications at once.
  11169. #
  11170. # So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
  11171. module Components
  11172. def self.included(base) #:nodoc:
  11173. base.send :include, InstanceMethods
  11174. base.extend(ClassMethods)
  11175. base.helper do
  11176. def render_component(options)
  11177. @controller.send(:render_component_as_string, options)
  11178. end
  11179. end
  11180. # If this controller was instantiated to process a component request,
  11181. # +parent_controller+ points to the instantiator of this controller.
  11182. base.send :attr_accessor, :parent_controller
  11183. base.class_eval do
  11184. alias_method :process_cleanup_without_components, :process_cleanup
  11185. alias_method :process_cleanup, :process_cleanup_with_components
  11186. alias_method :set_session_options_without_components, :set_session_options
  11187. alias_method :set_session_options, :set_session_options_with_components
  11188. alias_method :flash_without_components, :flash
  11189. alias_method :flash, :flash_with_components
  11190. alias_method :component_request?, :parent_controller
  11191. end
  11192. end
  11193. module ClassMethods
  11194. # Track parent controller to identify component requests
  11195. def process_with_components(request, response, parent_controller = nil) #:nodoc:
  11196. controller = new
  11197. controller.parent_controller = parent_controller
  11198. controller.process(request, response)
  11199. end
  11200. # Set the template root to be one directory behind the root dir of the controller. Examples:
  11201. # /code/weblog/components/admin/users_controller.rb with Admin::UsersController
  11202. # will use /code/weblog/components as template root
  11203. # and find templates in /code/weblog/components/admin/users/
  11204. #
  11205. # /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController
  11206. # will also use /code/weblog/components as template root
  11207. # and find templates in /code/weblog/components/admin/parties/users/
  11208. def uses_component_template_root
  11209. path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first)
  11210. path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)
  11211. self.template_root = path_of_controller_root
  11212. end
  11213. end
  11214. module InstanceMethods
  11215. # Extracts the action_name from the request parameters and performs that action.
  11216. def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
  11217. flash.discard if component_request?
  11218. process_without_components(request, response, method, *arguments)
  11219. end
  11220. protected
  11221. # Renders the component specified as the response for the current method
  11222. def render_component(options) #:doc:
  11223. component_logging(options) do
  11224. render_text(component_response(options, true).body, response.headers["Status"])
  11225. end
  11226. end
  11227. # Returns the component response as a string
  11228. def render_component_as_string(options) #:doc:
  11229. component_logging(options) do
  11230. response = component_response(options, false)
  11231. if redirected = response.redirected_to
  11232. render_component_as_string(redirected)
  11233. else
  11234. response.body
  11235. end
  11236. end
  11237. end
  11238. def flash_with_components(refresh = false) #:nodoc:
  11239. if @flash.nil? || refresh
  11240. @flash =
  11241. if @parent_controller
  11242. @parent_controller.flash
  11243. else
  11244. flash_without_components
  11245. end
  11246. end
  11247. @flash
  11248. end
  11249. private
  11250. def component_response(options, reuse_response)
  11251. klass = component_class(options)
  11252. request = request_for_component(klass.controller_name, options)
  11253. response = reuse_response ? @response : @response.dup
  11254. klass.process_with_components(request, response, self)
  11255. end
  11256. # determine the controller class for the component request
  11257. def component_class(options)
  11258. if controller = options[:controller]
  11259. controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
  11260. else
  11261. self.class
  11262. end
  11263. end
  11264. # Create a new request object based on the current request.
  11265. # The new request inherits the session from the current request,
  11266. # bypassing any session options set for the component controller's class
  11267. def request_for_component(controller_name, options)
  11268. request = @request.dup
  11269. request.session = @request.session
  11270. request.instance_variable_set(
  11271. :@parameters,
  11272. (options[:params] || {}).with_indifferent_access.update(
  11273. "controller" => controller_name, "action" => options[:action], "id" => options[:id]
  11274. )
  11275. )
  11276. request
  11277. end
  11278. def component_logging(options)
  11279. if logger
  11280. logger.info "Start rendering component (#{options.inspect}): "
  11281. result = yield
  11282. logger.info "\n\nEnd of component rendering"
  11283. result
  11284. else
  11285. yield
  11286. end
  11287. end
  11288. def set_session_options_with_components(request)
  11289. set_session_options_without_components(request) unless component_request?
  11290. end
  11291. def process_cleanup_with_components
  11292. process_cleanup_without_components unless component_request?
  11293. end
  11294. end
  11295. end
  11296. end
  11297. module ActionController #:nodoc:
  11298. # Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request,
  11299. # the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object
  11300. # itself back -- just the value it holds). Examples for writing:
  11301. #
  11302. # cookies[:user_name] = "david" # => Will set a simple session cookie
  11303. # cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
  11304. #
  11305. # Examples for reading:
  11306. #
  11307. # cookies[:user_name] # => "david"
  11308. # cookies.size # => 2
  11309. #
  11310. # Example for deleting:
  11311. #
  11312. # cookies.delete :user_name
  11313. #
  11314. # All the option symbols for setting cookies are:
  11315. #
  11316. # * <tt>value</tt> - the cookie's value or list of values (as an array).
  11317. # * <tt>path</tt> - the path for which this cookie applies. Defaults to the root of the application.
  11318. # * <tt>domain</tt> - the domain for which this cookie applies.
  11319. # * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
  11320. # * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
  11321. # Secure cookies are only transmitted to HTTPS servers.
  11322. module Cookies
  11323. protected
  11324. # Returns the cookie container, which operates as described above.
  11325. def cookies
  11326. CookieJar.new(self)
  11327. end
  11328. # Deprecated cookie writer method
  11329. def cookie(*options)
  11330. @response.headers["cookie"] << CGI::Cookie.new(*options)
  11331. end
  11332. end
  11333. class CookieJar < Hash #:nodoc:
  11334. def initialize(controller)
  11335. @controller, @cookies = controller, controller.instance_variable_get("@cookies")
  11336. super()
  11337. update(@cookies)
  11338. end
  11339. # Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using either the cookie method
  11340. # or cookies[]= (for simple name/value cookies without options).
  11341. def [](name)
  11342. @cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
  11343. end
  11344. def []=(name, options)
  11345. if options.is_a?(Hash)
  11346. options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
  11347. options["name"] = name.to_s
  11348. else
  11349. options = { "name" => name.to_s, "value" => options }
  11350. end
  11351. set_cookie(options)
  11352. end
  11353. # Removes the cookie on the client machine by setting the value to an empty string
  11354. # and setting its expiration date into the past
  11355. def delete(name)
  11356. set_cookie("name" => name.to_s, "value" => "", "expires" => Time.at(0))
  11357. end
  11358. private
  11359. def set_cookie(options) #:doc:
  11360. options["path"] = "/" unless options["path"]
  11361. cookie = CGI::Cookie.new(options)
  11362. @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
  11363. @controller.response.headers["cookie"] << cookie
  11364. end
  11365. end
  11366. end
  11367. module ActionController #:nodoc:
  11368. module Dependencies #:nodoc:
  11369. def self.append_features(base)
  11370. super
  11371. base.extend(ClassMethods)
  11372. end
  11373. # Dependencies control what classes are needed for the controller to run its course. This is an alternative to doing explicit
  11374. # +require+ statements that bring a number of benefits. It's more succinct, communicates what type of dependency we're talking about,
  11375. # can trigger special behavior (as in the case of +observer+), and enables Rails to be clever about reloading in cached environments
  11376. # like FCGI. Example:
  11377. #
  11378. # class ApplicationController < ActionController::Base
  11379. # model :account, :company, :person, :project, :category
  11380. # helper :access_control
  11381. # service :notifications, :billings
  11382. # observer :project_change_observer
  11383. # end
  11384. #
  11385. # Please note that a controller like ApplicationController will automatically attempt to require_dependency on a model of its
  11386. # singuralized name and a helper of its name. If nothing is found, no error is raised. This is especially useful for concrete
  11387. # controllers like PostController:
  11388. #
  11389. # class PostController < ApplicationController
  11390. # # model :post (already required)
  11391. # # helper :post (already required)
  11392. # end
  11393. #
  11394. # Also note, that if the models follow the pattern of just 1 class per file in the form of MyClass => my_class.rb, then these
  11395. # classes don't have to be required as Active Support will auto-require them.
  11396. module ClassMethods #:nodoc:
  11397. # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
  11398. # backend for modelling entity classes.
  11399. def model(*models)
  11400. require_dependencies(:model, models)
  11401. depend_on(:model, models)
  11402. end
  11403. # Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like
  11404. # Action Mailer service or a Payment Gateway service.
  11405. def service(*services)
  11406. require_dependencies(:service, services)
  11407. depend_on(:service, services)
  11408. end
  11409. # Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will
  11410. # automatically have .instance called on them to make them active on assignment.
  11411. def observer(*observers)
  11412. require_dependencies(:observer, observers)
  11413. depend_on(:observer, observers)
  11414. instantiate_observers(observers)
  11415. end
  11416. # Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling
  11417. # <tt>ApplicationController.dependencies_on(:model)</tt> would return <tt>[:account, :company, :person, :project, :category]</tt>
  11418. def dependencies_on(layer)
  11419. read_inheritable_attribute("#{layer}_dependencies")
  11420. end
  11421. def depend_on(layer, dependencies) #:nodoc:
  11422. write_inheritable_array("#{layer}_dependencies", dependencies)
  11423. end
  11424. private
  11425. def instantiate_observers(observers)
  11426. observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
  11427. end
  11428. def require_dependencies(layer, dependencies)
  11429. dependencies.flatten.each do |dependency|
  11430. begin
  11431. require_dependency(dependency.to_s)
  11432. rescue LoadError => e
  11433. raise LoadError.new("Missing #{layer} #{dependency}.rb").copy_blame!(e)
  11434. rescue Object => exception
  11435. exception.blame_file! "=> #{layer} #{dependency}.rb"
  11436. raise
  11437. end
  11438. end
  11439. end
  11440. end
  11441. end
  11442. end
  11443. require 'test/unit'
  11444. require 'test/unit/assertions'
  11445. require 'rexml/document'
  11446. module Test #:nodoc:
  11447. module Unit #:nodoc:
  11448. module Assertions
  11449. def assert_success(message=nil) #:nodoc:
  11450. assert_response(:success, message)
  11451. end
  11452. def assert_redirect(message=nil) #:nodoc:
  11453. assert_response(:redirect, message)
  11454. end
  11455. def assert_rendered_file(expected=nil, message=nil) #:nodoc:
  11456. assert_template(expected, message)
  11457. end
  11458. # ensure that the session has an object with the specified name
  11459. def assert_session_has(key=nil, message=nil) #:nodoc:
  11460. msg = build_message(message, "<?> is not in the session <?>", key, @response.session)
  11461. assert_block(msg) { @response.has_session_object?(key) }
  11462. end
  11463. # ensure that the session has no object with the specified name
  11464. def assert_session_has_no(key=nil, message=nil) #:nodoc:
  11465. msg = build_message(message, "<?> is in the session <?>", key, @response.session)
  11466. assert_block(msg) { !@response.has_session_object?(key) }
  11467. end
  11468. def assert_session_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11469. msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, @response.session[key])
  11470. assert_block(msg) { expected == @response.session[key] }
  11471. end
  11472. # -- cookie assertions ---------------------------------------------------
  11473. def assert_no_cookie(key = nil, message = nil) #:nodoc:
  11474. actual = @response.cookies[key]
  11475. msg = build_message(message, "<?> not expected in cookies['?']", actual, key)
  11476. assert_block(msg) { actual.nil? or actual.empty? }
  11477. end
  11478. def assert_cookie_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11479. actual = @response.cookies[key]
  11480. actual = actual.first if actual
  11481. msg = build_message(message, "<?> expected in cookies['?'] but was <?>", expected, key, actual)
  11482. assert_block(msg) { expected == actual }
  11483. end
  11484. # -- flash assertions ---------------------------------------------------
  11485. # ensure that the flash has an object with the specified name
  11486. def assert_flash_has(key=nil, message=nil) #:nodoc:
  11487. msg = build_message(message, "<?> is not in the flash <?>", key, @response.flash)
  11488. assert_block(msg) { @response.has_flash_object?(key) }
  11489. end
  11490. # ensure that the flash has no object with the specified name
  11491. def assert_flash_has_no(key=nil, message=nil) #:nodoc:
  11492. msg = build_message(message, "<?> is in the flash <?>", key, @response.flash)
  11493. assert_block(msg) { !@response.has_flash_object?(key) }
  11494. end
  11495. # ensure the flash exists
  11496. def assert_flash_exists(message=nil) #:nodoc:
  11497. msg = build_message(message, "the flash does not exist <?>", @response.session['flash'] )
  11498. assert_block(msg) { @response.has_flash? }
  11499. end
  11500. # ensure the flash does not exist
  11501. def assert_flash_not_exists(message=nil) #:nodoc:
  11502. msg = build_message(message, "the flash exists <?>", @response.flash)
  11503. assert_block(msg) { !@response.has_flash? }
  11504. end
  11505. # ensure the flash is empty but existent
  11506. def assert_flash_empty(message=nil) #:nodoc:
  11507. msg = build_message(message, "the flash is not empty <?>", @response.flash)
  11508. assert_block(msg) { !@response.has_flash_with_contents? }
  11509. end
  11510. # ensure the flash is not empty
  11511. def assert_flash_not_empty(message=nil) #:nodoc:
  11512. msg = build_message(message, "the flash is empty")
  11513. assert_block(msg) { @response.has_flash_with_contents? }
  11514. end
  11515. def assert_flash_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11516. msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, @response.flash[key])
  11517. assert_block(msg) { expected == @response.flash[key] }
  11518. end
  11519. # ensure our redirection url is an exact match
  11520. def assert_redirect_url(url=nil, message=nil) #:nodoc:
  11521. assert_redirect(message)
  11522. msg = build_message(message, "<?> is not the redirected location <?>", url, @response.redirect_url)
  11523. assert_block(msg) { @response.redirect_url == url }
  11524. end
  11525. # ensure our redirection url matches a pattern
  11526. def assert_redirect_url_match(pattern=nil, message=nil) #:nodoc:
  11527. assert_redirect(message)
  11528. msg = build_message(message, "<?> was not found in the location: <?>", pattern, @response.redirect_url)
  11529. assert_block(msg) { @response.redirect_url_match?(pattern) }
  11530. end
  11531. # -- template assertions ------------------------------------------------
  11532. # ensure that a template object with the given name exists
  11533. def assert_template_has(key=nil, message=nil) #:nodoc:
  11534. msg = build_message(message, "<?> is not a template object", key )
  11535. assert_block(msg) { @response.has_template_object?(key) }
  11536. end
  11537. # ensure that a template object with the given name does not exist
  11538. def assert_template_has_no(key=nil,message=nil) #:nodoc:
  11539. msg = build_message(message, "<?> is a template object <?>", key, @response.template_objects[key])
  11540. assert_block(msg) { !@response.has_template_object?(key) }
  11541. end
  11542. # ensures that the object assigned to the template on +key+ is equal to +expected+ object.
  11543. def assert_template_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11544. msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, @response.template.assigns[key.to_s])
  11545. assert_block(msg) { expected == @response.template.assigns[key.to_s] }
  11546. end
  11547. alias_method :assert_assigned_equal, :assert_template_equal
  11548. # Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
  11549. # This will only work if the template rendered a valid XML document.
  11550. def assert_template_xpath_match(expression=nil, expected=nil, message=nil) #:nodoc:
  11551. xml, matches = REXML::Document.new(@response.body), []
  11552. xml.elements.each(expression) { |e| matches << e.text }
  11553. if matches.empty? then
  11554. msg = build_message(message, "<?> not found in document", expression)
  11555. flunk(msg)
  11556. return
  11557. elsif matches.length < 2 then
  11558. matches = matches.first
  11559. end
  11560. msg = build_message(message, "<?> found <?>, not <?>", expression, matches, expected)
  11561. assert_block(msg) { matches == expected }
  11562. end
  11563. # Assert the template object with the given name is an Active Record descendant and is valid.
  11564. def assert_valid_record(key = nil, message = nil) #:nodoc:
  11565. record = find_record_in_template(key)
  11566. msg = build_message(message, "Active Record is invalid <?>)", record.errors.full_messages)
  11567. assert_block(msg) { record.valid? }
  11568. end
  11569. # Assert the template object with the given name is an Active Record descendant and is invalid.
  11570. def assert_invalid_record(key = nil, message = nil) #:nodoc:
  11571. record = find_record_in_template(key)
  11572. msg = build_message(message, "Active Record is valid)")
  11573. assert_block(msg) { !record.valid? }
  11574. end
  11575. # Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid.
  11576. def assert_valid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
  11577. record = find_record_in_template(key)
  11578. record.send(:validate)
  11579. cols = glue_columns(columns)
  11580. cols.delete_if { |col| !record.errors.invalid?(col) }
  11581. msg = build_message(message, "Active Record has invalid columns <?>)", cols.join(",") )
  11582. assert_block(msg) { cols.empty? }
  11583. end
  11584. # Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid.
  11585. def assert_invalid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
  11586. record = find_record_in_template(key)
  11587. record.send(:validate)
  11588. cols = glue_columns(columns)
  11589. cols.delete_if { |col| record.errors.invalid?(col) }
  11590. msg = build_message(message, "Active Record has valid columns <?>)", cols.join(",") )
  11591. assert_block(msg) { cols.empty? }
  11592. end
  11593. private
  11594. def glue_columns(columns)
  11595. cols = []
  11596. cols << columns if columns.class == String
  11597. cols += columns if columns.class == Array
  11598. cols
  11599. end
  11600. def find_record_in_template(key = nil)
  11601. assert_template_has(key)
  11602. record = @response.template_objects[key]
  11603. assert_not_nil(record)
  11604. assert_kind_of ActiveRecord::Base, record
  11605. return record
  11606. end
  11607. end
  11608. end
  11609. endmodule ActionController
  11610. class Base
  11611. protected
  11612. # Deprecated in favor of calling redirect_to directly with the path.
  11613. def redirect_to_path(path) #:nodoc:
  11614. redirect_to(path)
  11615. end
  11616. # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass
  11617. # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through
  11618. # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.
  11619. def redirect_to_url(url, permanently = false) #:nodoc:
  11620. headers["Status"] = "301 Moved Permanently" if permanently
  11621. redirect_to(url)
  11622. end
  11623. end
  11624. end
  11625. module ActionController
  11626. class AbstractRequest
  11627. # Determine whether the body of a HTTP call is URL-encoded (default)
  11628. # or matches one of the registered param_parsers.
  11629. #
  11630. # For backward compatibility, the post format is extracted from the
  11631. # X-Post-Data-Format HTTP header if present.
  11632. def post_format
  11633. case content_type.to_s
  11634. when 'application/xml'
  11635. :xml
  11636. when 'application/x-yaml'
  11637. :yaml
  11638. else
  11639. :url_encoded
  11640. end
  11641. end
  11642. # Is this a POST request formatted as XML or YAML?
  11643. def formatted_post?
  11644. post? && (post_format == :yaml || post_format == :xml)
  11645. end
  11646. # Is this a POST request formatted as XML?
  11647. def xml_post?
  11648. post? && post_format == :xml
  11649. end
  11650. # Is this a POST request formatted as YAML?
  11651. def yaml_post?
  11652. post? && post_format == :yaml
  11653. end
  11654. end
  11655. end
  11656. module ActionController #:nodoc:
  11657. module Filters #:nodoc:
  11658. def self.included(base)
  11659. base.extend(ClassMethods)
  11660. base.send(:include, ActionController::Filters::InstanceMethods)
  11661. end
  11662. # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
  11663. # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
  11664. # compression after the action has been performed.
  11665. #
  11666. # Filters have access to the request, response, and all the instance variables set by other filters in the chain
  11667. # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
  11668. # to halt the processing before the intended action is processed by returning false or performing a redirect or render.
  11669. # This is especially useful for filters like authentication where you're not interested in allowing the action to be
  11670. # performed if the proper credentials are not in order.
  11671. #
  11672. # == Filter inheritance
  11673. #
  11674. # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
  11675. # affecting the superclass. For example:
  11676. #
  11677. # class BankController < ActionController::Base
  11678. # before_filter :audit
  11679. #
  11680. # private
  11681. # def audit
  11682. # # record the action and parameters in an audit log
  11683. # end
  11684. # end
  11685. #
  11686. # class VaultController < BankController
  11687. # before_filter :verify_credentials
  11688. #
  11689. # private
  11690. # def verify_credentials
  11691. # # make sure the user is allowed into the vault
  11692. # end
  11693. # end
  11694. #
  11695. # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
  11696. # first the audit method is called, then the verify_credentials method. If the audit method returns false, then
  11697. # verify_credentials and the intended action are never called.
  11698. #
  11699. # == Filter types
  11700. #
  11701. # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
  11702. # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
  11703. # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
  11704. #
  11705. # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
  11706. # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
  11707. #
  11708. # class OutputCompressionFilter
  11709. # def self.filter(controller)
  11710. # controller.response.body = compress(controller.response.body)
  11711. # end
  11712. # end
  11713. #
  11714. # class NewspaperController < ActionController::Base
  11715. # after_filter OutputCompressionFilter
  11716. # end
  11717. #
  11718. # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
  11719. # manipulate them as it sees fit.
  11720. #
  11721. # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
  11722. # Or just as a quick test. It works like this:
  11723. #
  11724. # class WeblogController < ActionController::Base
  11725. # before_filter { |controller| false if controller.params["stop_action"] }
  11726. # end
  11727. #
  11728. # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
  11729. # This means that the block has access to both the request and response objects complete with convenience methods for params,
  11730. # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
  11731. # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
  11732. #
  11733. # == Filter chain ordering
  11734. #
  11735. # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
  11736. # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
  11737. # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
  11738. # beginning of their respective chain and executed before the rest. For example:
  11739. #
  11740. # class ShoppingController
  11741. # before_filter :verify_open_shop
  11742. #
  11743. # class CheckoutController
  11744. # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
  11745. #
  11746. # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
  11747. # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
  11748. # is open or not.
  11749. #
  11750. # You may pass multiple filter arguments of each type as well as a filter block.
  11751. # If a block is given, it is treated as the last argument.
  11752. #
  11753. # == Around filters
  11754. #
  11755. # In addition to the individual before and after filters, it's also possible to specify that a single object should handle
  11756. # both the before and after call. That's especially useful when you need to keep state active between the before and after,
  11757. # such as the example of a benchmark filter below:
  11758. #
  11759. # class WeblogController < ActionController::Base
  11760. # around_filter BenchmarkingFilter.new
  11761. #
  11762. # # Before this action is performed, BenchmarkingFilter#before(controller) is executed
  11763. # def index
  11764. # end
  11765. # # After this action has been performed, BenchmarkingFilter#after(controller) is executed
  11766. # end
  11767. #
  11768. # class BenchmarkingFilter
  11769. # def initialize
  11770. # @runtime
  11771. # end
  11772. #
  11773. # def before
  11774. # start_timer
  11775. # end
  11776. #
  11777. # def after
  11778. # stop_timer
  11779. # report_result
  11780. # end
  11781. # end
  11782. #
  11783. # == Filter chain skipping
  11784. #
  11785. # Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
  11786. # subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
  11787. # they would like to be relieved of. Examples
  11788. #
  11789. # class ApplicationController < ActionController::Base
  11790. # before_filter :authenticate
  11791. # end
  11792. #
  11793. # class WeblogController < ApplicationController
  11794. # # will run the :authenticate filter
  11795. # end
  11796. #
  11797. # class SignupController < ApplicationController
  11798. # # will not run the :authenticate filter
  11799. # skip_before_filter :authenticate
  11800. # end
  11801. #
  11802. # == Filter conditions
  11803. #
  11804. # Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
  11805. # exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both
  11806. # of which accept an arbitrary number of method references. For example:
  11807. #
  11808. # class Journal < ActionController::Base
  11809. # # only require authentication if the current action is edit or delete
  11810. # before_filter :authorize, :only => [ :edit, :delete ]
  11811. #
  11812. # private
  11813. # def authorize
  11814. # # redirect to login unless authenticated
  11815. # end
  11816. # end
  11817. #
  11818. # When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
  11819. #
  11820. # class UserPreferences < ActionController::Base
  11821. # before_filter(:except => :new) { # some proc ... }
  11822. # # ...
  11823. # end
  11824. #
  11825. module ClassMethods
  11826. # The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
  11827. # on this controller are performed.
  11828. def append_before_filter(*filters, &block)
  11829. conditions = extract_conditions!(filters)
  11830. filters << block if block_given?
  11831. add_action_conditions(filters, conditions)
  11832. append_filter_to_chain('before', filters)
  11833. end
  11834. # The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
  11835. # on this controller are performed.
  11836. def prepend_before_filter(*filters, &block)
  11837. conditions = extract_conditions!(filters)
  11838. filters << block if block_given?
  11839. add_action_conditions(filters, conditions)
  11840. prepend_filter_to_chain('before', filters)
  11841. end
  11842. # Short-hand for append_before_filter since that's the most common of the two.
  11843. alias :before_filter :append_before_filter
  11844. # The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
  11845. # on this controller are performed.
  11846. def append_after_filter(*filters, &block)
  11847. conditions = extract_conditions!(filters)
  11848. filters << block if block_given?
  11849. add_action_conditions(filters, conditions)
  11850. append_filter_to_chain('after', filters)
  11851. end
  11852. # The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
  11853. # on this controller are performed.
  11854. def prepend_after_filter(*filters, &block)
  11855. conditions = extract_conditions!(filters)
  11856. filters << block if block_given?
  11857. add_action_conditions(filters, conditions)
  11858. prepend_filter_to_chain("after", filters)
  11859. end
  11860. # Short-hand for append_after_filter since that's the most common of the two.
  11861. alias :after_filter :append_after_filter
  11862. # The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
  11863. # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
  11864. # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
  11865. #
  11866. # B#before
  11867. # A#before
  11868. # A#after
  11869. # B#after
  11870. def append_around_filter(*filters)
  11871. conditions = extract_conditions!(filters)
  11872. for filter in filters.flatten
  11873. ensure_filter_responds_to_before_and_after(filter)
  11874. append_before_filter(conditions || {}) { |c| filter.before(c) }
  11875. prepend_after_filter(conditions || {}) { |c| filter.after(c) }
  11876. end
  11877. end
  11878. # The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
  11879. # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
  11880. # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
  11881. #
  11882. # A#before
  11883. # B#before
  11884. # B#after
  11885. # A#after
  11886. def prepend_around_filter(*filters)
  11887. for filter in filters.flatten
  11888. ensure_filter_responds_to_before_and_after(filter)
  11889. prepend_before_filter { |c| filter.before(c) }
  11890. append_after_filter { |c| filter.after(c) }
  11891. end
  11892. end
  11893. # Short-hand for append_around_filter since that's the most common of the two.
  11894. alias :around_filter :append_around_filter
  11895. # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
  11896. # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
  11897. # of many sub-controllers need a different hierarchy.
  11898. #
  11899. # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
  11900. # just like when you apply the filters.
  11901. def skip_before_filter(*filters)
  11902. if conditions = extract_conditions!(filters)
  11903. remove_contradicting_conditions!(filters, conditions)
  11904. conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
  11905. add_action_conditions(filters, conditions)
  11906. else
  11907. for filter in filters.flatten
  11908. write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
  11909. end
  11910. end
  11911. end
  11912. # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
  11913. # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
  11914. # of many sub-controllers need a different hierarchy.
  11915. #
  11916. # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
  11917. # just like when you apply the filters.
  11918. def skip_after_filter(*filters)
  11919. if conditions = extract_conditions!(filters)
  11920. remove_contradicting_conditions!(filters, conditions)
  11921. conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
  11922. add_action_conditions(filters, conditions)
  11923. else
  11924. for filter in filters.flatten
  11925. write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
  11926. end
  11927. end
  11928. end
  11929. # Returns all the before filters for this class and all its ancestors.
  11930. def before_filters #:nodoc:
  11931. @before_filters ||= read_inheritable_attribute("before_filters") || []
  11932. end
  11933. # Returns all the after filters for this class and all its ancestors.
  11934. def after_filters #:nodoc:
  11935. @after_filters ||= read_inheritable_attribute("after_filters") || []
  11936. end
  11937. # Returns a mapping between filters and the actions that may run them.
  11938. def included_actions #:nodoc:
  11939. @included_actions ||= read_inheritable_attribute("included_actions") || {}
  11940. end
  11941. # Returns a mapping between filters and actions that may not run them.
  11942. def excluded_actions #:nodoc:
  11943. @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
  11944. end
  11945. private
  11946. def append_filter_to_chain(condition, filters)
  11947. write_inheritable_array("#{condition}_filters", filters)
  11948. end
  11949. def prepend_filter_to_chain(condition, filters)
  11950. old_filters = read_inheritable_attribute("#{condition}_filters") || []
  11951. write_inheritable_attribute("#{condition}_filters", filters + old_filters)
  11952. end
  11953. def ensure_filter_responds_to_before_and_after(filter)
  11954. unless filter.respond_to?(:before) && filter.respond_to?(:after)
  11955. raise ActionControllerError, "Filter object must respond to both before and after"
  11956. end
  11957. end
  11958. def extract_conditions!(filters)
  11959. return nil unless filters.last.is_a? Hash
  11960. filters.pop
  11961. end
  11962. def add_action_conditions(filters, conditions)
  11963. return unless conditions
  11964. included, excluded = conditions[:only], conditions[:except]
  11965. write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
  11966. write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
  11967. end
  11968. def condition_hash(filters, *actions)
  11969. filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
  11970. end
  11971. def remove_contradicting_conditions!(filters, conditions)
  11972. return unless conditions[:only]
  11973. filters.each do |filter|
  11974. next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
  11975. [*conditions[:only]].each do |conditional_action|
  11976. conditional_action = conditional_action.to_s
  11977. included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
  11978. end
  11979. end
  11980. end
  11981. end
  11982. module InstanceMethods # :nodoc:
  11983. def self.included(base)
  11984. base.class_eval do
  11985. alias_method :perform_action_without_filters, :perform_action
  11986. alias_method :perform_action, :perform_action_with_filters
  11987. alias_method :process_without_filters, :process
  11988. alias_method :process, :process_with_filters
  11989. alias_method :process_cleanup_without_filters, :process_cleanup
  11990. alias_method :process_cleanup, :process_cleanup_with_filters
  11991. end
  11992. end
  11993. def perform_action_with_filters
  11994. before_action_result = before_action
  11995. unless before_action_result == false || performed?
  11996. perform_action_without_filters
  11997. after_action
  11998. end
  11999. @before_filter_chain_aborted = (before_action_result == false)
  12000. end
  12001. def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
  12002. @before_filter_chain_aborted = false
  12003. process_without_filters(request, response, method, *arguments)
  12004. end
  12005. # Calls all the defined before-filter filters, which are added by using "before_filter :method".
  12006. # If any of the filters return false, no more filters will be executed and the action is aborted.
  12007. def before_action #:doc:
  12008. call_filters(self.class.before_filters)
  12009. end
  12010. # Calls all the defined after-filter filters, which are added by using "after_filter :method".
  12011. # If any of the filters return false, no more filters will be executed.
  12012. def after_action #:doc:
  12013. call_filters(self.class.after_filters)
  12014. end
  12015. private
  12016. def call_filters(filters)
  12017. filters.each do |filter|
  12018. next if action_exempted?(filter)
  12019. filter_result = case
  12020. when filter.is_a?(Symbol)
  12021. self.send(filter)
  12022. when filter_block?(filter)
  12023. filter.call(self)
  12024. when filter_class?(filter)
  12025. filter.filter(self)
  12026. else
  12027. raise(
  12028. ActionControllerError,
  12029. 'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
  12030. )
  12031. end
  12032. if filter_result == false
  12033. logger.info "Filter chain halted as [#{filter}] returned false" if logger
  12034. return false
  12035. end
  12036. end
  12037. end
  12038. def filter_block?(filter)
  12039. filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
  12040. end
  12041. def filter_class?(filter)
  12042. filter.respond_to?('filter')
  12043. end
  12044. def action_exempted?(filter)
  12045. case
  12046. when ia = self.class.included_actions[filter]
  12047. !ia.include?(action_name)
  12048. when ea = self.class.excluded_actions[filter]
  12049. ea.include?(action_name)
  12050. end
  12051. end
  12052. def process_cleanup_with_filters
  12053. if @before_filter_chain_aborted
  12054. close_session
  12055. else
  12056. process_cleanup_without_filters
  12057. end
  12058. end
  12059. end
  12060. end
  12061. end
  12062. module ActionController #:nodoc:
  12063. # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
  12064. # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
  12065. # that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can then expose
  12066. # the flash to its template. Actually, that exposure is automatically done. Example:
  12067. #
  12068. # class WeblogController < ActionController::Base
  12069. # def create
  12070. # # save post
  12071. # flash[:notice] = "Successfully created post"
  12072. # redirect_to :action => "display", :params => { :id => post.id }
  12073. # end
  12074. #
  12075. # def display
  12076. # # doesn't need to assign the flash notice to the template, that's done automatically
  12077. # end
  12078. # end
  12079. #
  12080. # display.rhtml
  12081. # <% if @flash[:notice] %><div class="notice"><%= @flash[:notice] %></div><% end %>
  12082. #
  12083. # This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
  12084. # as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
  12085. #
  12086. # See docs on the FlashHash class for more details about the flash.
  12087. module Flash
  12088. def self.included(base)
  12089. base.send :include, InstanceMethods
  12090. base.class_eval do
  12091. alias_method :assign_shortcuts_without_flash, :assign_shortcuts
  12092. alias_method :assign_shortcuts, :assign_shortcuts_with_flash
  12093. alias_method :process_cleanup_without_flash, :process_cleanup
  12094. alias_method :process_cleanup, :process_cleanup_with_flash
  12095. end
  12096. end
  12097. class FlashNow #:nodoc:
  12098. def initialize(flash)
  12099. @flash = flash
  12100. end
  12101. def []=(k, v)
  12102. @flash[k] = v
  12103. @flash.discard(k)
  12104. v
  12105. end
  12106. def [](k)
  12107. @flash[k]
  12108. end
  12109. end
  12110. class FlashHash < Hash
  12111. def initialize #:nodoc:
  12112. super
  12113. @used = {}
  12114. end
  12115. def []=(k, v) #:nodoc:
  12116. keep(k)
  12117. super
  12118. end
  12119. def update(h) #:nodoc:
  12120. h.keys.each{ |k| discard(k) }
  12121. super
  12122. end
  12123. alias :merge! :update
  12124. def replace(h) #:nodoc:
  12125. @used = {}
  12126. super
  12127. end
  12128. # Sets a flash that will not be available to the next action, only to the current.
  12129. #
  12130. # flash.now[:message] = "Hello current action"
  12131. #
  12132. # This method enables you to use the flash as a central messaging system in your app.
  12133. # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
  12134. # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
  12135. # vanish when the current action is done.
  12136. #
  12137. # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
  12138. def now
  12139. FlashNow.new self
  12140. end
  12141. # Keeps either the entire current flash or a specific flash entry available for the next action:
  12142. #
  12143. # flash.keep # keeps the entire flash
  12144. # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
  12145. def keep(k=nil)
  12146. use(k, false)
  12147. end
  12148. # Marks the entire flash or a single flash entry to be discarded by the end of the current action
  12149. #
  12150. # flash.keep # keep entire flash available for the next action
  12151. # flash.discard(:warning) # discard the "warning" entry (it'll still be available for the current action)
  12152. def discard(k=nil)
  12153. use(k)
  12154. end
  12155. # Mark for removal entries that were kept, and delete unkept ones.
  12156. #
  12157. # This method is called automatically by filters, so you generally don't need to care about it.
  12158. def sweep #:nodoc:
  12159. keys.each do |k|
  12160. unless @used[k]
  12161. use(k)
  12162. else
  12163. delete(k)
  12164. @used.delete(k)
  12165. end
  12166. end
  12167. (@used.keys - keys).each{|k| @used.delete k } # clean up after keys that could have been left over by calling reject! or shift on the flash
  12168. end
  12169. private
  12170. # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
  12171. # use() # marks the entire flash as used
  12172. # use('msg') # marks the "msg" entry as used
  12173. # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
  12174. # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
  12175. def use(k=nil, v=true)
  12176. unless k.nil?
  12177. @used[k] = v
  12178. else
  12179. keys.each{|key| use key, v }
  12180. end
  12181. end
  12182. end
  12183. module InstanceMethods #:nodoc:
  12184. def assign_shortcuts_with_flash(request, response) #:nodoc:
  12185. assign_shortcuts_without_flash(request, response)
  12186. flash(:refresh)
  12187. end
  12188. def process_cleanup_with_flash
  12189. flash.sweep if @session
  12190. process_cleanup_without_flash
  12191. end
  12192. protected
  12193. # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
  12194. # <tt>flash["notice"] = "hello"</tt> to put a new one.
  12195. # Note that if sessions are disabled only flash.now will work.
  12196. def flash(refresh = false) #:doc:
  12197. if @flash.nil? || refresh
  12198. @flash =
  12199. if @session.is_a?(Hash)
  12200. # @session is a Hash, if sessions are disabled
  12201. # we don't put the flash in the session in this case
  12202. FlashHash.new
  12203. else
  12204. # otherwise, @session is a CGI::Session or a TestSession
  12205. # so make sure it gets retrieved from/saved to session storage after request processing
  12206. @session["flash"] ||= FlashHash.new
  12207. end
  12208. end
  12209. @flash
  12210. end
  12211. # deprecated. use <tt>flash.keep</tt> instead
  12212. def keep_flash #:doc:
  12213. warn 'keep_flash is deprecated; use flash.keep instead.'
  12214. flash.keep
  12215. end
  12216. end
  12217. end
  12218. endmodule ActionController #:nodoc:
  12219. module Helpers #:nodoc:
  12220. def self.append_features(base)
  12221. super
  12222. # Initialize the base module to aggregate its helpers.
  12223. base.class_inheritable_accessor :master_helper_module
  12224. base.master_helper_module = Module.new
  12225. # Extend base with class methods to declare helpers.
  12226. base.extend(ClassMethods)
  12227. base.class_eval do
  12228. # Wrap inherited to create a new master helper module for subclasses.
  12229. class << self
  12230. alias_method :inherited_without_helper, :inherited
  12231. alias_method :inherited, :inherited_with_helper
  12232. end
  12233. end
  12234. end
  12235. # The template helpers serve to relieve the templates from including the same inline code again and again. It's a
  12236. # set of standardized methods for working with forms (FormHelper), dates (DateHelper), texts (TextHelper), and
  12237. # Active Records (ActiveRecordHelper) that's available to all templates by default.
  12238. #
  12239. # It's also really easy to make your own helpers and it's much encouraged to keep the template files free
  12240. # from complicated logic. It's even encouraged to bundle common compositions of methods from other helpers
  12241. # (often the common helpers) as they're used by the specific application.
  12242. #
  12243. # module MyHelper
  12244. # def hello_world() "hello world" end
  12245. # end
  12246. #
  12247. # MyHelper can now be included in a controller, like this:
  12248. #
  12249. # class MyController < ActionController::Base
  12250. # helper :my_helper
  12251. # end
  12252. #
  12253. # ...and, same as above, used in any template rendered from MyController, like this:
  12254. #
  12255. # Let's hear what the helper has to say: <tt><%= hello_world %></tt>
  12256. module ClassMethods
  12257. # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
  12258. # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
  12259. # available to the templates.
  12260. def add_template_helper(helper_module) #:nodoc:
  12261. master_helper_module.send(:include, helper_module)
  12262. end
  12263. # Declare a helper:
  12264. # helper :foo
  12265. # requires 'foo_helper' and includes FooHelper in the template class.
  12266. # helper FooHelper
  12267. # includes FooHelper in the template class.
  12268. # helper { def foo() "#{bar} is the very best" end }
  12269. # evaluates the block in the template class, adding method #foo.
  12270. # helper(:three, BlindHelper) { def mice() 'mice' end }
  12271. # does all three.
  12272. def helper(*args, &block)
  12273. args.flatten.each do |arg|
  12274. case arg
  12275. when Module
  12276. add_template_helper(arg)
  12277. when String, Symbol
  12278. file_name = arg.to_s.underscore + '_helper'
  12279. class_name = file_name.camelize
  12280. begin
  12281. require_dependency(file_name)
  12282. rescue LoadError => load_error
  12283. requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
  12284. msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
  12285. raise LoadError.new(msg).copy_blame!(load_error)
  12286. end
  12287. add_template_helper(class_name.constantize)
  12288. else
  12289. raise ArgumentError, 'helper expects String, Symbol, or Module argument'
  12290. end
  12291. end
  12292. # Evaluate block in template class if given.
  12293. master_helper_module.module_eval(&block) if block_given?
  12294. end
  12295. # Declare a controller method as a helper. For example,
  12296. # helper_method :link_to
  12297. # def link_to(name, options) ... end
  12298. # makes the link_to controller method available in the view.
  12299. def helper_method(*methods)
  12300. methods.flatten.each do |method|
  12301. master_helper_module.module_eval <<-end_eval
  12302. def #{method}(*args, &block)
  12303. controller.send(%(#{method}), *args, &block)
  12304. end
  12305. end_eval
  12306. end
  12307. end
  12308. # Declare a controller attribute as a helper. For example,
  12309. # helper_attr :name
  12310. # attr_accessor :name
  12311. # makes the name and name= controller methods available in the view.
  12312. # The is a convenience wrapper for helper_method.
  12313. def helper_attr(*attrs)
  12314. attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
  12315. end
  12316. private
  12317. def default_helper_module!
  12318. module_name = name.sub(/Controller$|$/, 'Helper')
  12319. module_path = module_name.split('::').map { |m| m.underscore }.join('/')
  12320. require_dependency module_path
  12321. helper module_name.constantize
  12322. rescue LoadError
  12323. logger.debug("#{name}: missing default helper path #{module_path}") if logger
  12324. rescue NameError
  12325. logger.debug("#{name}: missing default helper module #{module_name}") if logger
  12326. end
  12327. def inherited_with_helper(child)
  12328. inherited_without_helper(child)
  12329. begin
  12330. child.master_helper_module = Module.new
  12331. child.master_helper_module.send :include, master_helper_module
  12332. child.send :default_helper_module!
  12333. rescue MissingSourceFile => e
  12334. raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
  12335. end
  12336. end
  12337. end
  12338. end
  12339. end
  12340. require 'dispatcher'
  12341. require 'stringio'
  12342. require 'uri'
  12343. module ActionController
  12344. module Integration #:nodoc:
  12345. # An integration Session instance represents a set of requests and responses
  12346. # performed sequentially by some virtual user. Becase you can instantiate
  12347. # multiple sessions and run them side-by-side, you can also mimic (to some
  12348. # limited extent) multiple simultaneous users interacting with your system.
  12349. #
  12350. # Typically, you will instantiate a new session using IntegrationTest#open_session,
  12351. # rather than instantiating Integration::Session directly.
  12352. class Session
  12353. include Test::Unit::Assertions
  12354. include ActionController::TestProcess
  12355. # The integer HTTP status code of the last request.
  12356. attr_reader :status
  12357. # The status message that accompanied the status code of the last request.
  12358. attr_reader :status_message
  12359. # The URI of the last request.
  12360. attr_reader :path
  12361. # The hostname used in the last request.
  12362. attr_accessor :host
  12363. # The remote_addr used in the last request.
  12364. attr_accessor :remote_addr
  12365. # The Accept header to send.
  12366. attr_accessor :accept
  12367. # A map of the cookies returned by the last response, and which will be
  12368. # sent with the next request.
  12369. attr_reader :cookies
  12370. # A map of the headers returned by the last response.
  12371. attr_reader :headers
  12372. # A reference to the controller instance used by the last request.
  12373. attr_reader :controller
  12374. # A reference to the request instance used by the last request.
  12375. attr_reader :request
  12376. # A reference to the response instance used by the last request.
  12377. attr_reader :response
  12378. # Create an initialize a new Session instance.
  12379. def initialize
  12380. reset!
  12381. end
  12382. # Resets the instance. This can be used to reset the state information
  12383. # in an existing session instance, so it can be used from a clean-slate
  12384. # condition.
  12385. #
  12386. # session.reset!
  12387. def reset!
  12388. @status = @path = @headers = nil
  12389. @result = @status_message = nil
  12390. @https = false
  12391. @cookies = {}
  12392. @controller = @request = @response = nil
  12393. self.host = "www.example.com"
  12394. self.remote_addr = "127.0.0.1"
  12395. self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
  12396. unless @named_routes_configured
  12397. # install the named routes in this session instance.
  12398. klass = class<<self; self; end
  12399. Routing::NamedRoutes.install(klass)
  12400. # the helpers are made protected by default--we make them public for
  12401. # easier access during testing and troubleshooting.
  12402. klass.send(:public, *Routing::NamedRoutes::Helpers)
  12403. @named_routes_configured = true
  12404. end
  12405. end
  12406. # Specify whether or not the session should mimic a secure HTTPS request.
  12407. #
  12408. # session.https!
  12409. # session.https!(false)
  12410. def https!(flag=true)
  12411. @https = flag
  12412. end
  12413. # Return +true+ if the session is mimicing a secure HTTPS request.
  12414. #
  12415. # if session.https?
  12416. # ...
  12417. # end
  12418. def https?
  12419. @https
  12420. end
  12421. # Set the host name to use in the next request.
  12422. #
  12423. # session.host! "www.example.com"
  12424. def host!(name)
  12425. @host = name
  12426. end
  12427. # Follow a single redirect response. If the last response was not a
  12428. # redirect, an exception will be raised. Otherwise, the redirect is
  12429. # performed on the location header.
  12430. def follow_redirect!
  12431. raise "not a redirect! #{@status} #{@status_message}" unless redirect?
  12432. get(interpret_uri(headers["location"].first))
  12433. status
  12434. end
  12435. # Performs a GET request, following any subsequent redirect. Note that
  12436. # the redirects are followed until the response is not a redirect--this
  12437. # means you may run into an infinite loop if your redirect loops back to
  12438. # itself.
  12439. def get_via_redirect(path, args={})
  12440. get path, args
  12441. follow_redirect! while redirect?
  12442. status
  12443. end
  12444. # Performs a POST request, following any subsequent redirect. This is
  12445. # vulnerable to infinite loops, the same as #get_via_redirect.
  12446. def post_via_redirect(path, args={})
  12447. post path, args
  12448. follow_redirect! while redirect?
  12449. status
  12450. end
  12451. # Returns +true+ if the last response was a redirect.
  12452. def redirect?
  12453. status/100 == 3
  12454. end
  12455. # Performs a GET request with the given parameters. The parameters may
  12456. # be +nil+, a Hash, or a string that is appropriately encoded
  12457. # (application/x-www-form-urlencoded or multipart/form-data). The headers
  12458. # should be a hash. The keys will automatically be upcased, with the
  12459. # prefix 'HTTP_' added if needed.
  12460. def get(path, parameters=nil, headers=nil)
  12461. process :get, path, parameters, headers
  12462. end
  12463. # Performs a POST request with the given parameters. The parameters may
  12464. # be +nil+, a Hash, or a string that is appropriately encoded
  12465. # (application/x-www-form-urlencoded or multipart/form-data). The headers
  12466. # should be a hash. The keys will automatically be upcased, with the
  12467. # prefix 'HTTP_' added if needed.
  12468. def post(path, parameters=nil, headers=nil)
  12469. process :post, path, parameters, headers
  12470. end
  12471. # Performs an XMLHttpRequest request with the given parameters, mimicing
  12472. # the request environment created by the Prototype library. The parameters
  12473. # may be +nil+, a Hash, or a string that is appropriately encoded
  12474. # (application/x-www-form-urlencoded or multipart/form-data). The headers
  12475. # should be a hash. The keys will automatically be upcased, with the
  12476. # prefix 'HTTP_' added if needed.
  12477. def xml_http_request(path, parameters=nil, headers=nil)
  12478. headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
  12479. post(path, parameters, headers)
  12480. end
  12481. # Returns the URL for the given options, according to the rules specified
  12482. # in the application's routes.
  12483. def url_for(options)
  12484. controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
  12485. end
  12486. private
  12487. class MockCGI < CGI #:nodoc:
  12488. attr_accessor :stdinput, :stdoutput, :env_table
  12489. def initialize(env, input=nil)
  12490. self.env_table = env
  12491. self.stdinput = StringIO.new(input || "")
  12492. self.stdoutput = StringIO.new
  12493. super()
  12494. end
  12495. end
  12496. # Tailors the session based on the given URI, setting the HTTPS value
  12497. # and the hostname.
  12498. def interpret_uri(path)
  12499. location = URI.parse(path)
  12500. https! URI::HTTPS === location if location.scheme
  12501. host! location.host if location.host
  12502. location.query ? "#{location.path}?#{location.query}" : location.path
  12503. end
  12504. # Performs the actual request.
  12505. def process(method, path, parameters=nil, headers=nil)
  12506. data = requestify(parameters)
  12507. path = interpret_uri(path) if path =~ %r{://}
  12508. path = "/#{path}" unless path[0] == ?/
  12509. @path = path
  12510. env = {}
  12511. if method == :get
  12512. env["QUERY_STRING"] = data
  12513. data = nil
  12514. end
  12515. env.update(
  12516. "REQUEST_METHOD" => method.to_s.upcase,
  12517. "REQUEST_URI" => path,
  12518. "HTTP_HOST" => host,
  12519. "REMOTE_ADDR" => remote_addr,
  12520. "SERVER_PORT" => (https? ? "443" : "80"),
  12521. "CONTENT_TYPE" => "application/x-www-form-urlencoded",
  12522. "CONTENT_LENGTH" => data ? data.length.to_s : nil,
  12523. "HTTP_COOKIE" => encode_cookies,
  12524. "HTTPS" => https? ? "on" : "off",
  12525. "HTTP_ACCEPT" => accept
  12526. )
  12527. (headers || {}).each do |key, value|
  12528. key = key.to_s.upcase.gsub(/-/, "_")
  12529. key = "HTTP_#{key}" unless env.has_key?(key) || env =~ /^X|HTTP/
  12530. env[key] = value
  12531. end
  12532. unless ActionController::Base.respond_to?(:clear_last_instantiation!)
  12533. ActionController::Base.send(:include, ControllerCapture)
  12534. end
  12535. ActionController::Base.clear_last_instantiation!
  12536. cgi = MockCGI.new(env, data)
  12537. Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
  12538. @result = cgi.stdoutput.string
  12539. @controller = ActionController::Base.last_instantiation
  12540. @request = @controller.request
  12541. @response = @controller.response
  12542. # Decorate the response with the standard behavior of the TestResponse
  12543. # so that things like assert_response can be used in integration
  12544. # tests.
  12545. @response.extend(TestResponseBehavior)
  12546. parse_result
  12547. return status
  12548. end
  12549. # Parses the result of the response and extracts the various values,
  12550. # like cookies, status, headers, etc.
  12551. def parse_result
  12552. headers, result_body = @result.split(/\r\n\r\n/, 2)
  12553. @headers = Hash.new { |h,k| h[k] = [] }
  12554. headers.each_line do |line|
  12555. key, value = line.strip.split(/:\s*/, 2)
  12556. @headers[key.downcase] << value
  12557. end
  12558. (@headers['set-cookie'] || [] ).each do |string|
  12559. name, value = string.match(/^(.*?)=(.*?);/)[1,2]
  12560. @cookies[name] = value
  12561. end
  12562. @status, @status_message = @headers["status"].first.split(/ /)
  12563. @status = @status.to_i
  12564. end
  12565. # Encode the cookies hash in a format suitable for passing to a
  12566. # request.
  12567. def encode_cookies
  12568. cookies.inject("") do |string, (name, value)|
  12569. string << "#{name}=#{value}; "
  12570. end
  12571. end
  12572. # Get a temporarly URL writer object
  12573. def generic_url_rewriter
  12574. cgi = MockCGI.new('REQUEST_METHOD' => "GET",
  12575. 'QUERY_STRING' => "",
  12576. "REQUEST_URI" => "/",
  12577. "HTTP_HOST" => host,
  12578. "SERVER_PORT" => https? ? "443" : "80",
  12579. "HTTPS" => https? ? "on" : "off")
  12580. ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
  12581. end
  12582. def name_with_prefix(prefix, name)
  12583. prefix ? "#{prefix}[#{name}]" : name.to_s
  12584. end
  12585. # Convert the given parameters to a request string. The parameters may
  12586. # be a string, +nil+, or a Hash.
  12587. def requestify(parameters, prefix=nil)
  12588. if Hash === parameters
  12589. return nil if parameters.empty?
  12590. parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
  12591. elsif Array === parameters
  12592. parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
  12593. elsif prefix.nil?
  12594. parameters
  12595. else
  12596. "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
  12597. end
  12598. end
  12599. end
  12600. # A module used to extend ActionController::Base, so that integration tests
  12601. # can capture the controller used to satisfy a request.
  12602. module ControllerCapture #:nodoc:
  12603. def self.included(base)
  12604. base.extend(ClassMethods)
  12605. base.class_eval do
  12606. class <<self
  12607. alias_method :new_without_capture, :new
  12608. alias_method :new, :new_with_capture
  12609. end
  12610. end
  12611. end
  12612. module ClassMethods #:nodoc:
  12613. mattr_accessor :last_instantiation
  12614. def clear_last_instantiation!
  12615. self.last_instantiation = nil
  12616. end
  12617. def new_with_capture(*args)
  12618. self.last_instantiation ||= new_without_capture(*args)
  12619. end
  12620. end
  12621. end
  12622. end
  12623. # An IntegrationTest is one that spans multiple controllers and actions,
  12624. # tying them all together to ensure they work together as expected. It tests
  12625. # more completely than either unit or functional tests do, exercising the
  12626. # entire stack, from the dispatcher to the database.
  12627. #
  12628. # At its simplest, you simply extend IntegrationTest and write your tests
  12629. # using the get/post methods:
  12630. #
  12631. # require "#{File.dirname(__FILE__)}/test_helper"
  12632. #
  12633. # class ExampleTest < ActionController::IntegrationTest
  12634. # fixtures :people
  12635. #
  12636. # def test_login
  12637. # # get the login page
  12638. # get "/login"
  12639. # assert_equal 200, status
  12640. #
  12641. # # post the login and follow through to the home page
  12642. # post "/login", :username => people(:jamis).username,
  12643. # :password => people(:jamis).password
  12644. # follow_redirect!
  12645. # assert_equal 200, status
  12646. # assert_equal "/home", path
  12647. # end
  12648. # end
  12649. #
  12650. # However, you can also have multiple session instances open per test, and
  12651. # even extend those instances with assertions and methods to create a very
  12652. # powerful testing DSL that is specific for your application. You can even
  12653. # reference any named routes you happen to have defined!
  12654. #
  12655. # require "#{File.dirname(__FILE__)}/test_helper"
  12656. #
  12657. # class AdvancedTest < ActionController::IntegrationTest
  12658. # fixtures :people, :rooms
  12659. #
  12660. # def test_login_and_speak
  12661. # jamis, david = login(:jamis), login(:david)
  12662. # room = rooms(:office)
  12663. #
  12664. # jamis.enter(room)
  12665. # jamis.speak(room, "anybody home?")
  12666. #
  12667. # david.enter(room)
  12668. # david.speak(room, "hello!")
  12669. # end
  12670. #
  12671. # private
  12672. #
  12673. # module CustomAssertions
  12674. # def enter(room)
  12675. # # reference a named route, for maximum internal consistency!
  12676. # get(room_url(:id => room.id))
  12677. # assert(...)
  12678. # ...
  12679. # end
  12680. #
  12681. # def speak(room, message)
  12682. # xml_http_request "/say/#{room.id}", :message => message
  12683. # assert(...)
  12684. # ...
  12685. # end
  12686. # end
  12687. #
  12688. # def login(who)
  12689. # open_session do |sess|
  12690. # sess.extend(CustomAssertions)
  12691. # who = people(who)
  12692. # sess.post "/login", :username => who.username,
  12693. # :password => who.password
  12694. # assert(...)
  12695. # end
  12696. # end
  12697. # end
  12698. class IntegrationTest < Test::Unit::TestCase
  12699. # Work around a bug in test/unit caused by the default test being named
  12700. # as a symbol (:default_test), which causes regex test filters
  12701. # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
  12702. # symbols.
  12703. def initialize(name) #:nodoc:
  12704. super(name.to_s)
  12705. end
  12706. # Work around test/unit's requirement that every subclass of TestCase have
  12707. # at least one test method. Note that this implementation extends to all
  12708. # subclasses, as well, so subclasses of IntegrationTest may also exist
  12709. # without any test methods.
  12710. def run(*args) #:nodoc:
  12711. return if @method_name == "default_test"
  12712. super
  12713. end
  12714. # Because of how use_instantiated_fixtures and use_transactional_fixtures
  12715. # are defined, we need to treat them as special cases. Otherwise, users
  12716. # would potentially have to set their values for both Test::Unit::TestCase
  12717. # ActionController::IntegrationTest, since by the time the value is set on
  12718. # TestCase, IntegrationTest has already been defined and cannot inherit
  12719. # changes to those variables. So, we make those two attributes copy-on-write.
  12720. class << self
  12721. def use_transactional_fixtures=(flag) #:nodoc:
  12722. @_use_transactional_fixtures = true
  12723. @use_transactional_fixtures = flag
  12724. end
  12725. def use_instantiated_fixtures=(flag) #:nodoc:
  12726. @_use_instantiated_fixtures = true
  12727. @use_instantiated_fixtures = flag
  12728. end
  12729. def use_transactional_fixtures #:nodoc:
  12730. @_use_transactional_fixtures ?
  12731. @use_transactional_fixtures :
  12732. superclass.use_transactional_fixtures
  12733. end
  12734. def use_instantiated_fixtures #:nodoc:
  12735. @_use_instantiated_fixtures ?
  12736. @use_instantiated_fixtures :
  12737. superclass.use_instantiated_fixtures
  12738. end
  12739. end
  12740. # Reset the current session. This is useful for testing multiple sessions
  12741. # in a single test case.
  12742. def reset!
  12743. @integration_session = open_session
  12744. end
  12745. %w(get post cookies assigns xml_http_request).each do |method|
  12746. define_method(method) do |*args|
  12747. reset! unless @integration_session
  12748. returning @integration_session.send(method, *args) do
  12749. copy_session_variables!
  12750. end
  12751. end
  12752. end
  12753. # Open a new session instance. If a block is given, the new session is
  12754. # yielded to the block before being returned.
  12755. #
  12756. # session = open_session do |sess|
  12757. # sess.extend(CustomAssertions)
  12758. # end
  12759. #
  12760. # By default, a single session is automatically created for you, but you
  12761. # can use this method to open multiple sessions that ought to be tested
  12762. # simultaneously.
  12763. def open_session
  12764. session = Integration::Session.new
  12765. # delegate the fixture accessors back to the test instance
  12766. extras = Module.new { attr_accessor :delegate, :test_result }
  12767. self.class.fixture_table_names.each do |table_name|
  12768. name = table_name.tr(".", "_")
  12769. next unless respond_to?(name)
  12770. extras.send(:define_method, name) { |*args| delegate.send(name, *args) }
  12771. end
  12772. # delegate add_assertion to the test case
  12773. extras.send(:define_method, :add_assertion) { test_result.add_assertion }
  12774. session.extend(extras)
  12775. session.delegate = self
  12776. session.test_result = @_result
  12777. yield session if block_given?
  12778. session
  12779. end
  12780. # Copy the instance variables from the current session instance into the
  12781. # test instance.
  12782. def copy_session_variables! #:nodoc:
  12783. return unless @integration_session
  12784. %w(controller response request).each do |var|
  12785. instance_variable_set("@#{var}", @integration_session.send(var))
  12786. end
  12787. end
  12788. # Delegate unhandled messages to the current session instance.
  12789. def method_missing(sym, *args, &block)
  12790. reset! unless @integration_session
  12791. returning @integration_session.send(sym, *args, &block) do
  12792. copy_session_variables!
  12793. end
  12794. end
  12795. end
  12796. end
  12797. module ActionController #:nodoc:
  12798. module Layout #:nodoc:
  12799. def self.included(base)
  12800. base.extend(ClassMethods)
  12801. base.class_eval do
  12802. alias_method :render_with_no_layout, :render
  12803. alias_method :render, :render_with_a_layout
  12804. class << self
  12805. alias_method :inherited_without_layout, :inherited
  12806. alias_method :inherited, :inherited_with_layout
  12807. end
  12808. end
  12809. end
  12810. # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
  12811. # repeated setups. The inclusion pattern has pages that look like this:
  12812. #
  12813. # <%= render "shared/header" %>
  12814. # Hello World
  12815. # <%= render "shared/footer" %>
  12816. #
  12817. # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
  12818. # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
  12819. #
  12820. # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
  12821. # that the header and footer are only mentioned in one place, like this:
  12822. #
  12823. # <!-- The header part of this layout -->
  12824. # <%= yield %>
  12825. # <!-- The footer part of this layout -->
  12826. #
  12827. # And then you have content pages that look like this:
  12828. #
  12829. # hello world
  12830. #
  12831. # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
  12832. # like this:
  12833. #
  12834. # <!-- The header part of this layout -->
  12835. # hello world
  12836. # <!-- The footer part of this layout -->
  12837. #
  12838. # == Accessing shared variables
  12839. #
  12840. # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
  12841. # references that won't materialize before rendering time:
  12842. #
  12843. # <h1><%= @page_title %></h1>
  12844. # <%= yield %>
  12845. #
  12846. # ...and content pages that fulfill these references _at_ rendering time:
  12847. #
  12848. # <% @page_title = "Welcome" %>
  12849. # Off-world colonies offers you a chance to start a new life
  12850. #
  12851. # The result after rendering is:
  12852. #
  12853. # <h1>Welcome</h1>
  12854. # Off-world colonies offers you a chance to start a new life
  12855. #
  12856. # == Automatic layout assignment
  12857. #
  12858. # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
  12859. # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
  12860. # <tt>app/views/layouts/weblog.rhtml</tt> or <tt>app/views/layouts/weblog.rxml</tt> exists then it will be automatically set as
  12861. # the layout for your WeblogController. You can create a layout with the name <tt>application.rhtml</tt> or <tt>application.rxml</tt>
  12862. # and this will be set as the default controller if there is no layout with the same name as the current controller and there is
  12863. # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
  12864. # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.rhtml</tt>.
  12865. # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
  12866. # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignement if the child
  12867. # class has a layout with the same name.
  12868. #
  12869. # == Inheritance for layouts
  12870. #
  12871. # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
  12872. #
  12873. # class BankController < ActionController::Base
  12874. # layout "bank_standard"
  12875. #
  12876. # class InformationController < BankController
  12877. #
  12878. # class VaultController < BankController
  12879. # layout :access_level_layout
  12880. #
  12881. # class EmployeeController < BankController
  12882. # layout nil
  12883. #
  12884. # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
  12885. # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
  12886. #
  12887. # == Types of layouts
  12888. #
  12889. # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
  12890. # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
  12891. # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
  12892. #
  12893. # The method reference is the preferred approach to variable layouts and is used like this:
  12894. #
  12895. # class WeblogController < ActionController::Base
  12896. # layout :writers_and_readers
  12897. #
  12898. # def index
  12899. # # fetching posts
  12900. # end
  12901. #
  12902. # private
  12903. # def writers_and_readers
  12904. # logged_in? ? "writer_layout" : "reader_layout"
  12905. # end
  12906. #
  12907. # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
  12908. # is logged in or not.
  12909. #
  12910. # If you want to use an inline method, such as a proc, do something like this:
  12911. #
  12912. # class WeblogController < ActionController::Base
  12913. # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
  12914. #
  12915. # Of course, the most common way of specifying a layout is still just as a plain template name:
  12916. #
  12917. # class WeblogController < ActionController::Base
  12918. # layout "weblog_standard"
  12919. #
  12920. # If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+.
  12921. #
  12922. # == Conditional layouts
  12923. #
  12924. # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
  12925. # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
  12926. # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
  12927. #
  12928. # class WeblogController < ActionController::Base
  12929. # layout "weblog_standard", :except => :rss
  12930. #
  12931. # # ...
  12932. #
  12933. # end
  12934. #
  12935. # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
  12936. # around the rendered view.
  12937. #
  12938. # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
  12939. # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
  12940. #
  12941. # == Using a different layout in the action render call
  12942. #
  12943. # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
  12944. # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
  12945. # This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
  12946. # qualified template and layout names as this example shows:
  12947. #
  12948. # class WeblogController < ActionController::Base
  12949. # def help
  12950. # render :action => "help/index", :layout => "help"
  12951. # end
  12952. # end
  12953. #
  12954. # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
  12955. # as the third.
  12956. #
  12957. # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
  12958. # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
  12959. module ClassMethods
  12960. # If a layout is specified, all rendered actions will have their result rendered
  12961. # when the layout<tt>yield</tt>'s. This layout can itself depend on instance variables assigned during action
  12962. # performance and have access to them as any normal template would.
  12963. def layout(template_name, conditions = {})
  12964. add_layout_conditions(conditions)
  12965. write_inheritable_attribute "layout", template_name
  12966. end
  12967. def layout_conditions #:nodoc:
  12968. @layout_conditions ||= read_inheritable_attribute("layout_conditions")
  12969. end
  12970. def default_layout #:nodoc:
  12971. @default_layout ||= read_inheritable_attribute("layout")
  12972. end
  12973. private
  12974. def inherited_with_layout(child)
  12975. inherited_without_layout(child)
  12976. child.send :include, Reloadable
  12977. layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
  12978. child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
  12979. end
  12980. def layout_list
  12981. Dir.glob("#{template_root}/layouts/**/*")
  12982. end
  12983. def add_layout_conditions(conditions)
  12984. write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
  12985. end
  12986. def normalize_conditions(conditions)
  12987. conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
  12988. end
  12989. def layout_directory_exists_cache
  12990. @@layout_directory_exists_cache ||= Hash.new do |h, dirname|
  12991. h[dirname] = File.directory? dirname
  12992. end
  12993. end
  12994. end
  12995. # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
  12996. # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
  12997. # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
  12998. # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
  12999. def active_layout(passed_layout = nil)
  13000. layout = passed_layout || self.class.default_layout
  13001. active_layout = case layout
  13002. when String then layout
  13003. when Symbol then send(layout)
  13004. when Proc then layout.call(self)
  13005. end
  13006. # Explicitly passed layout names with slashes are looked up relative to the template root,
  13007. # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
  13008. # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
  13009. if active_layout
  13010. if active_layout.include?('/') && ! layout_directory?(active_layout)
  13011. active_layout
  13012. else
  13013. "layouts/#{active_layout}"
  13014. end
  13015. end
  13016. end
  13017. def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
  13018. template_with_options = options.is_a?(Hash)
  13019. if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
  13020. options = options.merge :layout => false if template_with_options
  13021. logger.info("Rendering #{options} within #{layout}") if logger
  13022. if template_with_options
  13023. content_for_layout = render_with_no_layout(options, &block)
  13024. deprecated_status = options[:status] || deprecated_status
  13025. else
  13026. content_for_layout = render_with_no_layout(options, deprecated_status, &block)
  13027. end
  13028. erase_render_results
  13029. add_variables_to_assigns
  13030. @template.instance_variable_set("@content_for_layout", content_for_layout)
  13031. render_text(@template.render_file(layout, true), deprecated_status)
  13032. else
  13033. render_with_no_layout(options, deprecated_status, &block)
  13034. end
  13035. end
  13036. private
  13037. def apply_layout?(template_with_options, options)
  13038. return false if options == :update
  13039. template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
  13040. end
  13041. def candidate_for_layout?(options)
  13042. (options.has_key?(:layout) && options[:layout] != false) ||
  13043. options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
  13044. !template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
  13045. end
  13046. def pick_layout(template_with_options, options, deprecated_layout)
  13047. if deprecated_layout
  13048. deprecated_layout
  13049. elsif template_with_options
  13050. case layout = options[:layout]
  13051. when FalseClass
  13052. nil
  13053. when NilClass, TrueClass
  13054. active_layout if action_has_layout?
  13055. else
  13056. active_layout(layout)
  13057. end
  13058. else
  13059. active_layout if action_has_layout?
  13060. end
  13061. end
  13062. def action_has_layout?
  13063. if conditions = self.class.layout_conditions
  13064. case
  13065. when only = conditions[:only]
  13066. only.include?(action_name)
  13067. when except = conditions[:except]
  13068. !except.include?(action_name)
  13069. else
  13070. true
  13071. end
  13072. else
  13073. true
  13074. end
  13075. end
  13076. # Does a layout directory for this class exist?
  13077. # we cache this info in a class level hash
  13078. def layout_directory?(layout_name)
  13079. template_path = File.join(self.class.view_root, 'layouts', layout_name)
  13080. dirname = File.dirname(template_path)
  13081. self.class.send(:layout_directory_exists_cache)[dirname]
  13082. end
  13083. end
  13084. end
  13085. module ActionController
  13086. # Macros are class-level calls that add pre-defined actions to the controller based on the parameters passed in.
  13087. # Currently, they're used to bridge the JavaScript macros, like autocompletion and in-place editing, with the controller
  13088. # backing.
  13089. module Macros
  13090. module AutoComplete #:nodoc:
  13091. def self.append_features(base) #:nodoc:
  13092. super
  13093. base.extend(ClassMethods)
  13094. end
  13095. # Example:
  13096. #
  13097. # # Controller
  13098. # class BlogController < ApplicationController
  13099. # auto_complete_for :post, :title
  13100. # end
  13101. #
  13102. # # View
  13103. # <%= text_field_with_auto_complete :post, title %>
  13104. #
  13105. # By default, auto_complete_for limits the results to 10 entries,
  13106. # and sorts by the given field.
  13107. #
  13108. # auto_complete_for takes a third parameter, an options hash to
  13109. # the find method used to search for the records:
  13110. #
  13111. # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC'
  13112. #
  13113. # For help on defining text input fields with autocompletion,
  13114. # see ActionView::Helpers::JavaScriptHelper.
  13115. #
  13116. # For more examples, see script.aculo.us:
  13117. # * http://script.aculo.us/demos/ajax/autocompleter
  13118. # * http://script.aculo.us/demos/ajax/autocompleter_customized
  13119. module ClassMethods
  13120. def auto_complete_for(object, method, options = {})
  13121. define_method("auto_complete_for_#{object}_#{method}") do
  13122. find_options = {
  13123. :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ],
  13124. :order => "#{method} ASC",
  13125. :limit => 10 }.merge!(options)
  13126. @items = object.to_s.camelize.constantize.find(:all, find_options)
  13127. render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  13128. end
  13129. end
  13130. end
  13131. end
  13132. end
  13133. endmodule ActionController
  13134. module Macros
  13135. module InPlaceEditing #:nodoc:
  13136. def self.append_features(base) #:nodoc:
  13137. super
  13138. base.extend(ClassMethods)
  13139. end
  13140. # Example:
  13141. #
  13142. # # Controller
  13143. # class BlogController < ApplicationController
  13144. # in_place_edit_for :post, :title
  13145. # end
  13146. #
  13147. # # View
  13148. # <%= in_place_editor_field :post, 'title' %>
  13149. #
  13150. # For help on defining an in place editor in the browser,
  13151. # see ActionView::Helpers::JavaScriptHelper.
  13152. module ClassMethods
  13153. def in_place_edit_for(object, attribute, options = {})
  13154. define_method("set_#{object}_#{attribute}") do
  13155. @item = object.to_s.camelize.constantize.find(params[:id])
  13156. @item.update_attribute(attribute, params[:value])
  13157. render :text => @item.send(attribute)
  13158. end
  13159. end
  13160. end
  13161. end
  13162. end
  13163. end
  13164. module ActionController #:nodoc:
  13165. module MimeResponds #:nodoc:
  13166. def self.included(base)
  13167. base.send(:include, ActionController::MimeResponds::InstanceMethods)
  13168. end
  13169. module InstanceMethods
  13170. # Without web-service support, an action which collects the data for displaying a list of people
  13171. # might look something like this:
  13172. #
  13173. # def list
  13174. # @people = Person.find(:all)
  13175. # end
  13176. #
  13177. # Here's the same action, with web-service support baked in:
  13178. #
  13179. # def list
  13180. # @people = Person.find(:all)
  13181. #
  13182. # respond_to do |wants|
  13183. # wants.html
  13184. # wants.xml { render :xml => @people.to_xml }
  13185. # end
  13186. # end
  13187. #
  13188. # What that says is, "if the client wants HTML in response to this action, just respond as we
  13189. # would have before, but if the client wants XML, return them the list of people in XML format."
  13190. # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
  13191. #
  13192. # Supposing you have an action that adds a new person, optionally creating their company
  13193. # (by name) if it does not already exist, without web-services, it might look like this:
  13194. #
  13195. # def add
  13196. # @company = Company.find_or_create_by_name(params[:company][:name])
  13197. # @person = @company.people.create(params[:person])
  13198. #
  13199. # redirect_to(person_list_url)
  13200. # end
  13201. #
  13202. # Here's the same action, with web-service support baked in:
  13203. #
  13204. # def add
  13205. # company = params[:person].delete(:company)
  13206. # @company = Company.find_or_create_by_name(company[:name])
  13207. # @person = @company.people.create(params[:person])
  13208. #
  13209. # respond_to do |wants|
  13210. # wants.html { redirect_to(person_list_url) }
  13211. # wants.js
  13212. # wants.xml { render :xml => @person.to_xml(:include => @company) }
  13213. # end
  13214. # end
  13215. #
  13216. # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
  13217. # (wants.js), then it is an RJS request and we render the RJS template associated with this action.
  13218. # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
  13219. # include the persons company in the rendered XML, so you get something like this:
  13220. #
  13221. # <person>
  13222. # <id>...</id>
  13223. # ...
  13224. # <company>
  13225. # <id>...</id>
  13226. # <name>...</name>
  13227. # ...
  13228. # </company>
  13229. # </person>
  13230. #
  13231. # Note, however, the extra bit at the top of that action:
  13232. #
  13233. # company = params[:person].delete(:company)
  13234. # @company = Company.find_or_create_by_name(company[:name])
  13235. #
  13236. # This is because the incoming XML document (if a web-service request is in process) can only contain a
  13237. # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
  13238. #
  13239. # person[name]=...&person[company][name]=...&...
  13240. #
  13241. # And, like this (xml-encoded):
  13242. #
  13243. # <person>
  13244. # <name>...</name>
  13245. # <company>
  13246. # <name>...</name>
  13247. # </company>
  13248. # </person>
  13249. #
  13250. # In other words, we make the request so that it operates on a single entitya person. Then, in the action,
  13251. # we extract the company data from the request, find or create the company, and then create the new person
  13252. # with the remaining data.
  13253. #
  13254. # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
  13255. # in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow
  13256. # and accept Rails' defaults, life will be much easier.
  13257. #
  13258. # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
  13259. # environment.rb as follows.
  13260. #
  13261. # Mime::Type.register "image/jpg", :jpg
  13262. #
  13263. def respond_to(*types, &block)
  13264. raise ArgumentError, "respond_to takes either types or a block, never bot" unless types.any? ^ block
  13265. block ||= lambda { |responder| types.each { |type| responder.send(type) } }
  13266. responder = Responder.new(block.binding)
  13267. block.call(responder)
  13268. responder.respond
  13269. end
  13270. end
  13271. class Responder #:nodoc:
  13272. DEFAULT_BLOCKS = {
  13273. :html => 'Proc.new { render }',
  13274. :js => 'Proc.new { render :action => "#{action_name}.rjs" }',
  13275. :xml => 'Proc.new { render :action => "#{action_name}.rxml" }'
  13276. }
  13277. def initialize(block_binding)
  13278. @block_binding = block_binding
  13279. @mime_type_priority = eval("request.accepts", block_binding)
  13280. @order = []
  13281. @responses = {}
  13282. end
  13283. def custom(mime_type, &block)
  13284. mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
  13285. @order << mime_type
  13286. if block_given?
  13287. @responses[mime_type] = block
  13288. else
  13289. @responses[mime_type] = eval(DEFAULT_BLOCKS[mime_type.to_sym], @block_binding)
  13290. end
  13291. end
  13292. for mime_type in %w( all html js xml rss atom yaml )
  13293. eval <<-EOT
  13294. def #{mime_type}(&block)
  13295. custom(Mime::#{mime_type.upcase}, &block)
  13296. end
  13297. EOT
  13298. end
  13299. def any(*args, &block)
  13300. args.each { |type| send(type, &block) }
  13301. end
  13302. def respond
  13303. for priority in @mime_type_priority
  13304. if priority == Mime::ALL
  13305. @responses[@order.first].call
  13306. return
  13307. else
  13308. if priority === @order
  13309. @responses[priority].call
  13310. return # mime type match found, be happy and return
  13311. end
  13312. end
  13313. end
  13314. if @order.include?(Mime::ALL)
  13315. @responses[Mime::ALL].call
  13316. else
  13317. eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding
  13318. end
  13319. end
  13320. end
  13321. end
  13322. end
  13323. module Mime
  13324. class Type #:nodoc:
  13325. # A simple helper class used in parsing the accept header
  13326. class AcceptItem #:nodoc:
  13327. attr_accessor :order, :name, :q
  13328. def initialize(order, name, q=nil)
  13329. @order = order
  13330. @name = name.strip
  13331. q ||= 0.0 if @name == "*/*" # default "*/*" to end of list
  13332. @q = ((q || 1.0).to_f * 100).to_i
  13333. end
  13334. def to_s
  13335. @name
  13336. end
  13337. def <=>(item)
  13338. result = item.q <=> q
  13339. result = order <=> item.order if result == 0
  13340. result
  13341. end
  13342. def ==(item)
  13343. name == (item.respond_to?(:name) ? item.name : item)
  13344. end
  13345. end
  13346. class << self
  13347. def lookup(string)
  13348. LOOKUP[string]
  13349. end
  13350. def parse(accept_header)
  13351. # keep track of creation order to keep the subsequent sort stable
  13352. index = 0
  13353. list = accept_header.split(/,/).
  13354. map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort!
  13355. # Take care of the broken text/xml entry by renaming or deleting it
  13356. text_xml = list.index("text/xml")
  13357. app_xml = list.index("application/xml")
  13358. if text_xml && app_xml
  13359. # set the q value to the max of the two
  13360. list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
  13361. # make sure app_xml is ahead of text_xml in the list
  13362. if app_xml > text_xml
  13363. list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
  13364. app_xml, text_xml = text_xml, app_xml
  13365. end
  13366. # delete text_xml from the list
  13367. list.delete_at(text_xml)
  13368. elsif text_xml
  13369. list[text_xml].name = "application/xml"
  13370. end
  13371. # Look for more specific xml-based types and sort them ahead of app/xml
  13372. if app_xml
  13373. idx = app_xml
  13374. app_xml_type = list[app_xml]
  13375. while(idx < list.length)
  13376. type = list[idx]
  13377. break if type.q < app_xml_type.q
  13378. if type.name =~ /\+xml$/
  13379. list[app_xml], list[idx] = list[idx], list[app_xml]
  13380. app_xml = idx
  13381. end
  13382. idx += 1
  13383. end
  13384. end
  13385. list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
  13386. list
  13387. end
  13388. end
  13389. def initialize(string, symbol = nil, synonyms = [])
  13390. @symbol, @synonyms = symbol, synonyms
  13391. @string = string
  13392. end
  13393. def to_s
  13394. @string
  13395. end
  13396. def to_str
  13397. to_s
  13398. end
  13399. def to_sym
  13400. @symbol || @string.to_sym
  13401. end
  13402. def ===(list)
  13403. if list.is_a?(Array)
  13404. (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
  13405. else
  13406. super
  13407. end
  13408. end
  13409. def ==(mime_type)
  13410. (@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
  13411. end
  13412. end
  13413. ALL = Type.new "*/*", :all
  13414. HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
  13415. JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
  13416. XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
  13417. RSS = Type.new "application/rss+xml", :rss
  13418. ATOM = Type.new "application/atom+xml", :atom
  13419. YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
  13420. LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) }
  13421. LOOKUP["*/*"] = ALL
  13422. LOOKUP["text/html"] = HTML
  13423. LOOKUP["application/xhtml+xml"] = HTML
  13424. LOOKUP["application/xml"] = XML
  13425. LOOKUP["text/xml"] = XML
  13426. LOOKUP["application/x-xml"] = XML
  13427. LOOKUP["text/javascript"] = JS
  13428. LOOKUP["application/javascript"] = JS
  13429. LOOKUP["application/x-javascript"] = JS
  13430. LOOKUP["text/yaml"] = YAML
  13431. LOOKUP["application/x-yaml"] = YAML
  13432. LOOKUP["application/rss+xml"] = RSS
  13433. LOOKUP["application/atom+xml"] = ATOM
  13434. endmodule ActionController
  13435. # === Action Pack pagination for Active Record collections
  13436. #
  13437. # The Pagination module aids in the process of paging large collections of
  13438. # Active Record objects. It offers macro-style automatic fetching of your
  13439. # model for multiple views, or explicit fetching for single actions. And if
  13440. # the magic isn't flexible enough for your needs, you can create your own
  13441. # paginators with a minimal amount of code.
  13442. #
  13443. # The Pagination module can handle as much or as little as you wish. In the
  13444. # controller, have it automatically query your model for pagination; or,
  13445. # if you prefer, create Paginator objects yourself.
  13446. #
  13447. # Pagination is included automatically for all controllers.
  13448. #
  13449. # For help rendering pagination links, see
  13450. # ActionView::Helpers::PaginationHelper.
  13451. #
  13452. # ==== Automatic pagination for every action in a controller
  13453. #
  13454. # class PersonController < ApplicationController
  13455. # model :person
  13456. #
  13457. # paginate :people, :order => 'last_name, first_name',
  13458. # :per_page => 20
  13459. #
  13460. # # ...
  13461. # end
  13462. #
  13463. # Each action in this controller now has access to a <tt>@people</tt>
  13464. # instance variable, which is an ordered collection of model objects for the
  13465. # current page (at most 20, sorted by last name and first name), and a
  13466. # <tt>@person_pages</tt> Paginator instance. The current page is determined
  13467. # by the <tt>params[:page]</tt> variable.
  13468. #
  13469. # ==== Pagination for a single action
  13470. #
  13471. # def list
  13472. # @person_pages, @people =
  13473. # paginate :people, :order => 'last_name, first_name'
  13474. # end
  13475. #
  13476. # Like the previous example, but explicitly creates <tt>@person_pages</tt>
  13477. # and <tt>@people</tt> for a single action, and uses the default of 10 items
  13478. # per page.
  13479. #
  13480. # ==== Custom/"classic" pagination
  13481. #
  13482. # def list
  13483. # @person_pages = Paginator.new self, Person.count, 10, params[:page]
  13484. # @people = Person.find :all, :order => 'last_name, first_name',
  13485. # :limit => @person_pages.items_per_page,
  13486. # :offset => @person_pages.current.offset
  13487. # end
  13488. #
  13489. # Explicitly creates the paginator from the previous example and uses
  13490. # Paginator#to_sql to retrieve <tt>@people</tt> from the model.
  13491. #
  13492. module Pagination
  13493. unless const_defined?(:OPTIONS)
  13494. # A hash holding options for controllers using macro-style pagination
  13495. OPTIONS = Hash.new
  13496. # The default options for pagination
  13497. DEFAULT_OPTIONS = {
  13498. :class_name => nil,
  13499. :singular_name => nil,
  13500. :per_page => 10,
  13501. :conditions => nil,
  13502. :order_by => nil,
  13503. :order => nil,
  13504. :join => nil,
  13505. :joins => nil,
  13506. :count => nil,
  13507. :include => nil,
  13508. :select => nil,
  13509. :parameter => 'page'
  13510. }
  13511. end
  13512. def self.included(base) #:nodoc:
  13513. super
  13514. base.extend(ClassMethods)
  13515. end
  13516. def self.validate_options!(collection_id, options, in_action) #:nodoc:
  13517. options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
  13518. valid_options = DEFAULT_OPTIONS.keys
  13519. valid_options << :actions unless in_action
  13520. unknown_option_keys = options.keys - valid_options
  13521. raise ActionController::ActionControllerError,
  13522. "Unknown options: #{unknown_option_keys.join(', ')}" unless
  13523. unknown_option_keys.empty?
  13524. options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
  13525. options[:class_name] ||= Inflector.camelize(options[:singular_name])
  13526. end
  13527. # Returns a paginator and a collection of Active Record model instances
  13528. # for the paginator's current page. This is designed to be used in a
  13529. # single action; to automatically paginate multiple actions, consider
  13530. # ClassMethods#paginate.
  13531. #
  13532. # +options+ are:
  13533. # <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by
  13534. # singularizing the collection name
  13535. # <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
  13536. # camelizing the singular name
  13537. # <tt>:per_page</tt>:: the maximum number of items to include in a
  13538. # single page. Defaults to 10
  13539. # <tt>:conditions</tt>:: optional conditions passed to Model.find(:all, *params) and
  13540. # Model.count
  13541. # <tt>:order</tt>:: optional order parameter passed to Model.find(:all, *params)
  13542. # <tt>:order_by</tt>:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
  13543. # <tt>:joins</tt>:: optional joins parameter passed to Model.find(:all, *params)
  13544. # and Model.count
  13545. # <tt>:join</tt>:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params)
  13546. # and Model.count
  13547. # <tt>:include</tt>:: optional eager loading parameter passed to Model.find(:all, *params)
  13548. # and Model.count
  13549. # <tt>:select</tt>:: :select parameter passed to Model.find(:all, *params)
  13550. #
  13551. # <tt>:count</tt>:: parameter passed as :select option to Model.count(*params)
  13552. #
  13553. def paginate(collection_id, options={})
  13554. Pagination.validate_options!(collection_id, options, true)
  13555. paginator_and_collection_for(collection_id, options)
  13556. end
  13557. # These methods become class methods on any controller
  13558. module ClassMethods
  13559. # Creates a +before_filter+ which automatically paginates an Active
  13560. # Record model for all actions in a controller (or certain actions if
  13561. # specified with the <tt>:actions</tt> option).
  13562. #
  13563. # +options+ are the same as PaginationHelper#paginate, with the addition
  13564. # of:
  13565. # <tt>:actions</tt>:: an array of actions for which the pagination is
  13566. # active. Defaults to +nil+ (i.e., every action)
  13567. def paginate(collection_id, options={})
  13568. Pagination.validate_options!(collection_id, options, false)
  13569. module_eval do
  13570. before_filter :create_paginators_and_retrieve_collections
  13571. OPTIONS[self] ||= Hash.new
  13572. OPTIONS[self][collection_id] = options
  13573. end
  13574. end
  13575. end
  13576. def create_paginators_and_retrieve_collections #:nodoc:
  13577. Pagination::OPTIONS[self.class].each do |collection_id, options|
  13578. next unless options[:actions].include? action_name if
  13579. options[:actions]
  13580. paginator, collection =
  13581. paginator_and_collection_for(collection_id, options)
  13582. paginator_name = "@#{options[:singular_name]}_pages"
  13583. self.instance_variable_set(paginator_name, paginator)
  13584. collection_name = "@#{collection_id.to_s}"
  13585. self.instance_variable_set(collection_name, collection)
  13586. end
  13587. end
  13588. # Returns the total number of items in the collection to be paginated for
  13589. # the +model+ and given +conditions+. Override this method to implement a
  13590. # custom counter.
  13591. def count_collection_for_pagination(model, options)
  13592. model.count(:conditions => options[:conditions],
  13593. :joins => options[:join] || options[:joins],
  13594. :include => options[:include],
  13595. :select => options[:count])
  13596. end
  13597. # Returns a collection of items for the given +model+ and +options[conditions]+,
  13598. # ordered by +options[order]+, for the current page in the given +paginator+.
  13599. # Override this method to implement a custom finder.
  13600. def find_collection_for_pagination(model, options, paginator)
  13601. model.find(:all, :conditions => options[:conditions],
  13602. :order => options[:order_by] || options[:order],
  13603. :joins => options[:join] || options[:joins], :include => options[:include],
  13604. :select => options[:select], :limit => options[:per_page],
  13605. :offset => paginator.current.offset)
  13606. end
  13607. protected :create_paginators_and_retrieve_collections,
  13608. :count_collection_for_pagination,
  13609. :find_collection_for_pagination
  13610. def paginator_and_collection_for(collection_id, options) #:nodoc:
  13611. klass = options[:class_name].constantize
  13612. page = @params[options[:parameter]]
  13613. count = count_collection_for_pagination(klass, options)
  13614. paginator = Paginator.new(self, count, options[:per_page], page)
  13615. collection = find_collection_for_pagination(klass, options, paginator)
  13616. return paginator, collection
  13617. end
  13618. private :paginator_and_collection_for
  13619. # A class representing a paginator for an Active Record collection.
  13620. class Paginator
  13621. include Enumerable
  13622. # Creates a new Paginator on the given +controller+ for a set of items
  13623. # of size +item_count+ and having +items_per_page+ items per page.
  13624. # Raises ArgumentError if items_per_page is out of bounds (i.e., less
  13625. # than or equal to zero). The page CGI parameter for links defaults to
  13626. # "page" and can be overridden with +page_parameter+.
  13627. def initialize(controller, item_count, items_per_page, current_page=1)
  13628. raise ArgumentError, 'must have at least one item per page' if
  13629. items_per_page <= 0
  13630. @controller = controller
  13631. @item_count = item_count || 0
  13632. @items_per_page = items_per_page
  13633. @pages = {}
  13634. self.current_page = current_page
  13635. end
  13636. attr_reader :controller, :item_count, :items_per_page
  13637. # Sets the current page number of this paginator. If +page+ is a Page
  13638. # object, its +number+ attribute is used as the value; if the page does
  13639. # not belong to this Paginator, an ArgumentError is raised.
  13640. def current_page=(page)
  13641. if page.is_a? Page
  13642. raise ArgumentError, 'Page/Paginator mismatch' unless
  13643. page.paginator == self
  13644. end
  13645. page = page.to_i
  13646. @current_page_number = has_page_number?(page) ? page : 1
  13647. end
  13648. # Returns a Page object representing this paginator's current page.
  13649. def current_page
  13650. @current_page ||= self[@current_page_number]
  13651. end
  13652. alias current :current_page
  13653. # Returns a new Page representing the first page in this paginator.
  13654. def first_page
  13655. @first_page ||= self[1]
  13656. end
  13657. alias first :first_page
  13658. # Returns a new Page representing the last page in this paginator.
  13659. def last_page
  13660. @last_page ||= self[page_count]
  13661. end
  13662. alias last :last_page
  13663. # Returns the number of pages in this paginator.
  13664. def page_count
  13665. @page_count ||= @item_count.zero? ? 1 :
  13666. (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
  13667. end
  13668. alias length :page_count
  13669. # Returns true if this paginator contains the page of index +number+.
  13670. def has_page_number?(number)
  13671. number >= 1 and number <= page_count
  13672. end
  13673. # Returns a new Page representing the page with the given index
  13674. # +number+.
  13675. def [](number)
  13676. @pages[number] ||= Page.new(self, number)
  13677. end
  13678. # Successively yields all the paginator's pages to the given block.
  13679. def each(&block)
  13680. page_count.times do |n|
  13681. yield self[n+1]
  13682. end
  13683. end
  13684. # A class representing a single page in a paginator.
  13685. class Page
  13686. include Comparable
  13687. # Creates a new Page for the given +paginator+ with the index
  13688. # +number+. If +number+ is not in the range of valid page numbers or
  13689. # is not a number at all, it defaults to 1.
  13690. def initialize(paginator, number)
  13691. @paginator = paginator
  13692. @number = number.to_i
  13693. @number = 1 unless @paginator.has_page_number? @number
  13694. end
  13695. attr_reader :paginator, :number
  13696. alias to_i :number
  13697. # Compares two Page objects and returns true when they represent the
  13698. # same page (i.e., their paginators are the same and they have the
  13699. # same page number).
  13700. def ==(page)
  13701. return false if page.nil?
  13702. @paginator == page.paginator and
  13703. @number == page.number
  13704. end
  13705. # Compares two Page objects and returns -1 if the left-hand page comes
  13706. # before the right-hand page, 0 if the pages are equal, and 1 if the
  13707. # left-hand page comes after the right-hand page. Raises ArgumentError
  13708. # if the pages do not belong to the same Paginator object.
  13709. def <=>(page)
  13710. raise ArgumentError unless @paginator == page.paginator
  13711. @number <=> page.number
  13712. end
  13713. # Returns the item offset for the first item in this page.
  13714. def offset
  13715. @paginator.items_per_page * (@number - 1)
  13716. end
  13717. # Returns the number of the first item displayed.
  13718. def first_item
  13719. offset + 1
  13720. end
  13721. # Returns the number of the last item displayed.
  13722. def last_item
  13723. [@paginator.items_per_page * @number, @paginator.item_count].min
  13724. end
  13725. # Returns true if this page is the first page in the paginator.
  13726. def first?
  13727. self == @paginator.first
  13728. end
  13729. # Returns true if this page is the last page in the paginator.
  13730. def last?
  13731. self == @paginator.last
  13732. end
  13733. # Returns a new Page object representing the page just before this
  13734. # page, or nil if this is the first page.
  13735. def previous
  13736. if first? then nil else @paginator[@number - 1] end
  13737. end
  13738. # Returns a new Page object representing the page just after this
  13739. # page, or nil if this is the last page.
  13740. def next
  13741. if last? then nil else @paginator[@number + 1] end
  13742. end
  13743. # Returns a new Window object for this page with the specified
  13744. # +padding+.
  13745. def window(padding=2)
  13746. Window.new(self, padding)
  13747. end
  13748. # Returns the limit/offset array for this page.
  13749. def to_sql
  13750. [@paginator.items_per_page, offset]
  13751. end
  13752. def to_param #:nodoc:
  13753. @number.to_s
  13754. end
  13755. end
  13756. # A class for representing ranges around a given page.
  13757. class Window
  13758. # Creates a new Window object for the given +page+ with the specified
  13759. # +padding+.
  13760. def initialize(page, padding=2)
  13761. @paginator = page.paginator
  13762. @page = page
  13763. self.padding = padding
  13764. end
  13765. attr_reader :paginator, :page
  13766. # Sets the window's padding (the number of pages on either side of the
  13767. # window page).
  13768. def padding=(padding)
  13769. @padding = padding < 0 ? 0 : padding
  13770. # Find the beginning and end pages of the window
  13771. @first = @paginator.has_page_number?(@page.number - @padding) ?
  13772. @paginator[@page.number - @padding] : @paginator.first
  13773. @last = @paginator.has_page_number?(@page.number + @padding) ?
  13774. @paginator[@page.number + @padding] : @paginator.last
  13775. end
  13776. attr_reader :padding, :first, :last
  13777. # Returns an array of Page objects in the current window.
  13778. def pages
  13779. (@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
  13780. end
  13781. alias to_a :pages
  13782. end
  13783. end
  13784. end
  13785. end
  13786. module ActionController
  13787. # Subclassing AbstractRequest makes these methods available to the request objects used in production and testing,
  13788. # CgiRequest and TestRequest
  13789. class AbstractRequest
  13790. cattr_accessor :relative_url_root
  13791. # Returns the hash of environment variables for this request,
  13792. # such as { 'RAILS_ENV' => 'production' }.
  13793. attr_reader :env
  13794. # Returns both GET and POST parameters in a single hash.
  13795. def parameters
  13796. @parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
  13797. end
  13798. # Returns the HTTP request method as a lowercase symbol (:get, for example)
  13799. def method
  13800. @request_method ||= @env['REQUEST_METHOD'].downcase.to_sym
  13801. end
  13802. # Is this a GET request? Equivalent to request.method == :get
  13803. def get?
  13804. method == :get
  13805. end
  13806. # Is this a POST request? Equivalent to request.method == :post
  13807. def post?
  13808. method == :post
  13809. end
  13810. # Is this a PUT request? Equivalent to request.method == :put
  13811. def put?
  13812. method == :put
  13813. end
  13814. # Is this a DELETE request? Equivalent to request.method == :delete
  13815. def delete?
  13816. method == :delete
  13817. end
  13818. # Is this a HEAD request? Equivalent to request.method == :head
  13819. def head?
  13820. method == :head
  13821. end
  13822. # Determine whether the body of a HTTP call is URL-encoded (default)
  13823. # or matches one of the registered param_parsers.
  13824. #
  13825. # For backward compatibility, the post format is extracted from the
  13826. # X-Post-Data-Format HTTP header if present.
  13827. def content_type
  13828. @content_type ||=
  13829. begin
  13830. content_type = @env['CONTENT_TYPE'].to_s.downcase
  13831. if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
  13832. case x_post_format.to_s.downcase
  13833. when 'yaml'
  13834. content_type = 'application/x-yaml'
  13835. when 'xml'
  13836. content_type = 'application/xml'
  13837. end
  13838. end
  13839. Mime::Type.lookup(content_type)
  13840. end
  13841. end
  13842. # Returns the accepted MIME type for the request
  13843. def accepts
  13844. @accepts ||=
  13845. if @env['HTTP_ACCEPT'].to_s.strip.empty?
  13846. [ content_type, Mime::ALL ]
  13847. else
  13848. Mime::Type.parse(@env['HTTP_ACCEPT'])
  13849. end
  13850. end
  13851. # Returns true if the request's "X-Requested-With" header contains
  13852. # "XMLHttpRequest". (The Prototype Javascript library sends this header with
  13853. # every Ajax request.)
  13854. def xml_http_request?
  13855. not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
  13856. end
  13857. alias xhr? :xml_http_request?
  13858. # Determine originating IP address. REMOTE_ADDR is the standard
  13859. # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
  13860. # HTTP_X_FORWARDED_FOR are set by proxies so check for these before
  13861. # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
  13862. # delimited list in the case of multiple chained proxies; the first is
  13863. # the originating IP.
  13864. def remote_ip
  13865. return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
  13866. if @env.include? 'HTTP_X_FORWARDED_FOR' then
  13867. remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
  13868. ip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
  13869. end
  13870. return remote_ips.first.strip unless remote_ips.empty?
  13871. end
  13872. @env['REMOTE_ADDR']
  13873. end
  13874. # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
  13875. # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
  13876. def domain(tld_length = 1)
  13877. return nil if !/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.match(host).nil? or host.nil?
  13878. host.split('.').last(1 + tld_length).join('.')
  13879. end
  13880. # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
  13881. # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
  13882. # in "www.rubyonrails.co.uk".
  13883. def subdomains(tld_length = 1)
  13884. return [] unless host
  13885. parts = host.split('.')
  13886. parts[0..-(tld_length+2)]
  13887. end
  13888. # Receive the raw post data.
  13889. # This is useful for services such as REST, XMLRPC and SOAP
  13890. # which communicate over HTTP POST but don't use the traditional parameter format.
  13891. def raw_post
  13892. @env['RAW_POST_DATA']
  13893. end
  13894. # Returns the request URI correctly, taking into account the idiosyncracies
  13895. # of the various servers.
  13896. def request_uri
  13897. if uri = @env['REQUEST_URI']
  13898. (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri.
  13899. else # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME
  13900. script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
  13901. uri = @env['PATH_INFO']
  13902. uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
  13903. unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
  13904. uri << '?' << env_qs
  13905. end
  13906. uri
  13907. end
  13908. end
  13909. # Return 'https://' if this is an SSL request and 'http://' otherwise.
  13910. def protocol
  13911. ssl? ? 'https://' : 'http://'
  13912. end
  13913. # Is this an SSL request?
  13914. def ssl?
  13915. @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
  13916. end
  13917. # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
  13918. def path
  13919. path = (uri = request_uri) ? uri.split('?').first : ''
  13920. # Cut off the path to the installation directory if given
  13921. root = relative_url_root
  13922. path[0, root.length] = '' if root
  13923. path || ''
  13924. end
  13925. # Returns the path minus the web server relative installation directory.
  13926. # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
  13927. # It can be automatically extracted for Apache setups. If the server is not
  13928. # Apache, this method returns an empty string.
  13929. def relative_url_root
  13930. @@relative_url_root ||= case
  13931. when @env["RAILS_RELATIVE_URL_ROOT"]
  13932. @env["RAILS_RELATIVE_URL_ROOT"]
  13933. when server_software == 'apache'
  13934. @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
  13935. else
  13936. ''
  13937. end
  13938. end
  13939. # Returns the port number of this request as an integer.
  13940. def port
  13941. @port_as_int ||= @env['SERVER_PORT'].to_i
  13942. end
  13943. # Returns the standard port number for this request's protocol
  13944. def standard_port
  13945. case protocol
  13946. when 'https://' then 443
  13947. else 80
  13948. end
  13949. end
  13950. # Returns a port suffix like ":8080" if the port number of this request
  13951. # is not the default HTTP port 80 or HTTPS port 443.
  13952. def port_string
  13953. (port == standard_port) ? '' : ":#{port}"
  13954. end
  13955. # Returns a host:port string for this request, such as example.com or
  13956. # example.com:8080.
  13957. def host_with_port
  13958. host + port_string
  13959. end
  13960. def path_parameters=(parameters) #:nodoc:
  13961. @path_parameters = parameters
  13962. @symbolized_path_parameters = @parameters = nil
  13963. end
  13964. # The same as <tt>path_parameters</tt> with explicitly symbolized keys
  13965. def symbolized_path_parameters
  13966. @symbolized_path_parameters ||= path_parameters.symbolize_keys
  13967. end
  13968. # Returns a hash with the parameters used to form the path of the request
  13969. #
  13970. # Example:
  13971. #
  13972. # {:action => 'my_action', :controller => 'my_controller'}
  13973. def path_parameters
  13974. @path_parameters ||= {}
  13975. end
  13976. # Returns the lowercase name of the HTTP server software.
  13977. def server_software
  13978. (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
  13979. end
  13980. #--
  13981. # Must be implemented in the concrete request
  13982. #++
  13983. def query_parameters #:nodoc:
  13984. end
  13985. def request_parameters #:nodoc:
  13986. end
  13987. # Returns the host for this request, such as example.com.
  13988. def host
  13989. end
  13990. def cookies #:nodoc:
  13991. end
  13992. def session #:nodoc:
  13993. end
  13994. def session=(session) #:nodoc:
  13995. @session = session
  13996. end
  13997. def reset_session #:nodoc:
  13998. end
  13999. end
  14000. end
  14001. module ActionController #:nodoc:
  14002. # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
  14003. # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
  14004. # is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too
  14005. # could the decision on whether something is a public or a developer request.
  14006. #
  14007. # You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
  14008. module Rescue
  14009. def self.append_features(base) #:nodoc:
  14010. super
  14011. base.extend(ClassMethods)
  14012. base.class_eval do
  14013. alias_method :perform_action_without_rescue, :perform_action
  14014. alias_method :perform_action, :perform_action_with_rescue
  14015. end
  14016. end
  14017. module ClassMethods #:nodoc:
  14018. def process_with_exception(request, response, exception)
  14019. new.process(request, response, :rescue_action, exception)
  14020. end
  14021. end
  14022. protected
  14023. # Exception handler called when the performance of an action raises an exception.
  14024. def rescue_action(exception)
  14025. log_error(exception) if logger
  14026. erase_results if performed?
  14027. if consider_all_requests_local || local_request?
  14028. rescue_action_locally(exception)
  14029. else
  14030. rescue_action_in_public(exception)
  14031. end
  14032. end
  14033. # Overwrite to implement custom logging of errors. By default logs as fatal.
  14034. def log_error(exception) #:doc:
  14035. if ActionView::TemplateError === exception
  14036. logger.fatal(exception.to_s)
  14037. else
  14038. logger.fatal(
  14039. "\n\n#{exception.class} (#{exception.message}):\n " +
  14040. clean_backtrace(exception).join("\n ") +
  14041. "\n\n"
  14042. )
  14043. end
  14044. end
  14045. # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
  14046. def rescue_action_in_public(exception) #:doc:
  14047. case exception
  14048. when RoutingError, UnknownAction then
  14049. render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
  14050. else render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
  14051. end
  14052. end
  14053. # Overwrite to expand the meaning of a local request in order to show local rescues on other occurrences than
  14054. # the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
  14055. # remotely.
  14056. def local_request? #:doc:
  14057. [@request.remote_addr, @request.remote_ip] == ["127.0.0.1"] * 2
  14058. end
  14059. # Renders a detailed diagnostics screen on action exceptions.
  14060. def rescue_action_locally(exception)
  14061. add_variables_to_assigns
  14062. @template.instance_variable_set("@exception", exception)
  14063. @template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/")
  14064. @template.send(:assign_variables_from_controller)
  14065. @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
  14066. @headers["Content-Type"] = "text/html"
  14067. render_file(rescues_path("layout"), response_code_for_rescue(exception))
  14068. end
  14069. private
  14070. def perform_action_with_rescue #:nodoc:
  14071. begin
  14072. perform_action_without_rescue
  14073. rescue Object => exception
  14074. if defined?(Breakpoint) && @params["BP-RETRY"]
  14075. msg = exception.backtrace.first
  14076. if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
  14077. origin_file, origin_line = md[1], md[2].to_i
  14078. set_trace_func(lambda do |type, file, line, method, context, klass|
  14079. if file == origin_file and line == origin_line then
  14080. set_trace_func(nil)
  14081. @params["BP-RETRY"] = false
  14082. callstack = caller
  14083. callstack.slice!(0) if callstack.first["rescue.rb"]
  14084. file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
  14085. message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}." # `' ( for ruby-mode)
  14086. Breakpoint.handle_breakpoint(context, message, file, line)
  14087. end
  14088. end)
  14089. retry
  14090. end
  14091. end
  14092. rescue_action(exception)
  14093. end
  14094. end
  14095. def rescues_path(template_name)
  14096. File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml"
  14097. end
  14098. def template_path_for_local_rescue(exception)
  14099. rescues_path(
  14100. case exception
  14101. when MissingTemplate then "missing_template"
  14102. when RoutingError then "routing_error"
  14103. when UnknownAction then "unknown_action"
  14104. when ActionView::TemplateError then "template_error"
  14105. else "diagnostics"
  14106. end
  14107. )
  14108. end
  14109. def response_code_for_rescue(exception)
  14110. case exception
  14111. when UnknownAction, RoutingError then "404 Page Not Found"
  14112. else "500 Internal Error"
  14113. end
  14114. end
  14115. def clean_backtrace(exception)
  14116. exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }
  14117. end
  14118. end
  14119. end
  14120. module ActionController
  14121. class AbstractResponse #:nodoc:
  14122. DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
  14123. attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params
  14124. def initialize
  14125. @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
  14126. end
  14127. def redirect(to_url, permanently = false)
  14128. @headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
  14129. @headers["location"] = to_url
  14130. @body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
  14131. end
  14132. end
  14133. endmodule ActionController
  14134. module Routing #:nodoc:
  14135. class << self
  14136. def expiry_hash(options, recall)
  14137. k = v = nil
  14138. expire_on = {}
  14139. options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
  14140. expire_on
  14141. end
  14142. def extract_parameter_value(parameter) #:nodoc:
  14143. CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)
  14144. end
  14145. def controller_relative_to(controller, previous)
  14146. if controller.nil? then previous
  14147. elsif controller[0] == ?/ then controller[1..-1]
  14148. elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
  14149. else controller
  14150. end
  14151. end
  14152. def treat_hash(hash, keys_to_delete = [])
  14153. k = v = nil
  14154. hash.each do |k, v|
  14155. if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
  14156. else
  14157. hash.delete k
  14158. keys_to_delete << k
  14159. end
  14160. end
  14161. hash
  14162. end
  14163. def test_condition(expression, condition)
  14164. case condition
  14165. when String then "(#{expression} == #{condition.inspect})"
  14166. when Regexp then
  14167. condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
  14168. "(#{condition.inspect} =~ #{expression})"
  14169. when Array then
  14170. conds = condition.collect do |condition|
  14171. cond = test_condition(expression, condition)
  14172. (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
  14173. end
  14174. "(#{conds.join(' || ')})"
  14175. when true then expression
  14176. when nil then "! #{expression}"
  14177. else
  14178. raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
  14179. end
  14180. end
  14181. end
  14182. class Component #:nodoc:
  14183. def dynamic?() false end
  14184. def optional?() false end
  14185. def key() nil end
  14186. def self.new(string, *args)
  14187. return super(string, *args) unless self == Component
  14188. case string
  14189. when ':controller' then ControllerComponent.new(:controller, *args)
  14190. when /^:(\w+)$/ then DynamicComponent.new($1, *args)
  14191. when /^\*(\w+)$/ then PathComponent.new($1, *args)
  14192. else StaticComponent.new(string, *args)
  14193. end
  14194. end
  14195. end
  14196. class StaticComponent < Component #:nodoc:
  14197. attr_reader :value
  14198. def initialize(value)
  14199. @value = value
  14200. end
  14201. def write_recognition(g)
  14202. g.if_next_matches(value) do |gp|
  14203. gp.move_forward {|gpp| gpp.continue}
  14204. end
  14205. end
  14206. def write_generation(g)
  14207. g.add_segment(value) {|gp| gp.continue }
  14208. end
  14209. end
  14210. class DynamicComponent < Component #:nodoc:
  14211. attr_reader :key, :default
  14212. attr_accessor :condition
  14213. def dynamic?() true end
  14214. def optional?() @optional end
  14215. def default=(default)
  14216. @optional = true
  14217. @default = default
  14218. end
  14219. def initialize(key, options = {})
  14220. @key = key.to_sym
  14221. @optional = false
  14222. default, @condition = options[:default], options[:condition]
  14223. self.default = default if options.key?(:default)
  14224. end
  14225. def default_check(g)
  14226. presence = "#{g.hash_value(key, !! default)}"
  14227. if default
  14228. "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
  14229. else
  14230. "! #{presence}"
  14231. end
  14232. end
  14233. def write_generation(g)
  14234. wrote_dropout = write_dropout_generation(g)
  14235. write_continue_generation(g, wrote_dropout)
  14236. end
  14237. def write_dropout_generation(g)
  14238. return false unless optional? && g.after.all? {|c| c.optional?}
  14239. check = [default_check(g)]
  14240. gp = g.dup # Use another generator to write the conditions after the first &&
  14241. # We do this to ensure that the generator will not assume x_value is set. It will
  14242. # not be set if it follows a false condition -- for example, false && (x = 2)
  14243. check += gp.after.map {|c| c.default_check gp}
  14244. gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
  14245. true
  14246. end
  14247. def write_continue_generation(g, use_else)
  14248. test = Routing.test_condition(g.hash_value(key, true, default), condition || true)
  14249. check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
  14250. g.send(*check) do |gp|
  14251. gp.expire_for_keys(key) unless gp.after.empty?
  14252. add_segments_to(gp) {|gpp| gpp.continue}
  14253. end
  14254. end
  14255. def add_segments_to(g)
  14256. g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
  14257. end
  14258. def recognition_check(g)
  14259. test_type = [true, nil].include?(condition) ? :presence : :constraint
  14260. prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
  14261. check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
  14262. g.if(check) {|gp| yield gp, test_type}
  14263. end
  14264. def write_recognition(g)
  14265. test_type = nil
  14266. recognition_check(g) do |gp, test_type|
  14267. assign_result(gp) {|gpp| gpp.continue}
  14268. end
  14269. if optional? && g.after.all? {|c| c.optional?}
  14270. call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
  14271. g.send(*call) do |gp|
  14272. assign_default(gp)
  14273. gp.after.each {|c| c.assign_default(gp)}
  14274. gp.finish(false)
  14275. end
  14276. end
  14277. end
  14278. def assign_result(g, with_default = false)
  14279. g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
  14280. g.move_forward {|gp| yield gp}
  14281. end
  14282. def assign_default(g)
  14283. g.constant_result key, default unless default.nil?
  14284. end
  14285. end
  14286. class ControllerComponent < DynamicComponent #:nodoc:
  14287. def key() :controller end
  14288. def add_segments_to(g)
  14289. g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
  14290. end
  14291. def recognition_check(g)
  14292. g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
  14293. g.if('controller_result') do |gp|
  14294. gp << 'controller_value, segments_to_controller = controller_result'
  14295. if condition
  14296. gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
  14297. gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
  14298. gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
  14299. end
  14300. else
  14301. gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
  14302. end
  14303. end
  14304. end
  14305. def assign_result(g)
  14306. g.result key, 'controller_value'
  14307. yield g
  14308. end
  14309. def assign_default(g)
  14310. ControllerComponent.assign_controller(g, default)
  14311. end
  14312. class << self
  14313. def assign_controller(g, controller)
  14314. expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
  14315. g.result :controller, expr, true
  14316. end
  14317. def traverse_to_controller(segments, start_at = 0)
  14318. mod = ::Object
  14319. length = segments.length
  14320. index = start_at
  14321. mod_name = controller_name = segment = nil
  14322. while index < length
  14323. return nil unless /\A[A-Za-z][A-Za-z\d_]*\Z/ =~ (segment = segments[index])
  14324. index += 1
  14325. mod_name = segment.camelize
  14326. controller_name = "#{mod_name}Controller"
  14327. path_suffix = File.join(segments[start_at..(index - 1)])
  14328. next_mod = nil
  14329. # If the controller is already present, or if we load it, return it.
  14330. if mod.const_defined?(controller_name) || attempt_load(mod, controller_name, path_suffix + "_controller") == :defined
  14331. controller = mod.const_get(controller_name)
  14332. return nil unless controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) # it's not really a controller?
  14333. return [controller, (index - start_at)]
  14334. end
  14335. # No controller? Look for the module
  14336. if mod.const_defined? mod_name
  14337. next_mod = mod.send(:const_get, mod_name)
  14338. next_mod = nil unless next_mod.is_a?(Module)
  14339. else
  14340. # Try to load a file that defines the module we want.
  14341. case attempt_load(mod, mod_name, path_suffix)
  14342. when :defined then next_mod = mod.const_get mod_name
  14343. when :dir then # We didn't find a file, but there's a dir.
  14344. next_mod = Module.new # So create a module for the directory
  14345. mod.send :const_set, mod_name, next_mod
  14346. else
  14347. return nil
  14348. end
  14349. end
  14350. mod = next_mod
  14351. return nil unless mod && mod.is_a?(Module)
  14352. end
  14353. nil
  14354. end
  14355. protected
  14356. def safe_load_paths #:nodoc:
  14357. if defined?(RAILS_ROOT)
  14358. $LOAD_PATH.select do |base|
  14359. base = File.expand_path(base)
  14360. extended_root = File.expand_path(RAILS_ROOT)
  14361. # Exclude all paths that are not nested within app, lib, or components.
  14362. base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin}
  14363. end
  14364. else
  14365. $LOAD_PATH
  14366. end
  14367. end
  14368. def attempt_load(mod, const_name, path)
  14369. has_dir = false
  14370. safe_load_paths.each do |load_path|
  14371. full_path = File.join(load_path, path)
  14372. file_path = full_path + '.rb'
  14373. if File.file?(file_path) # Found a .rb file? Load it up
  14374. require_dependency(file_path)
  14375. return :defined if mod.const_defined? const_name
  14376. else
  14377. has_dir ||= File.directory?(full_path)
  14378. end
  14379. end
  14380. return (has_dir ? :dir : nil)
  14381. end
  14382. end
  14383. end
  14384. class PathComponent < DynamicComponent #:nodoc:
  14385. def optional?() true end
  14386. def default() [] end
  14387. def condition() nil end
  14388. def default=(value)
  14389. raise RoutingError, "All path components have an implicit default of []" unless value == []
  14390. end
  14391. def write_generation(g)
  14392. raise RoutingError, 'Path components must occur last' unless g.after.empty?
  14393. g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
  14394. g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
  14395. g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
  14396. end
  14397. g.else { g.finish }
  14398. end
  14399. def write_recognition(g)
  14400. raise RoutingError, "Path components must occur last" unless g.after.empty?
  14401. start = g.index_name
  14402. start = "(#{start})" unless /^\w+$/ =~ start
  14403. value_expr = "#{g.path_name}[#{start}..-1] || []"
  14404. g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
  14405. g.finish(false)
  14406. end
  14407. class Result < ::Array #:nodoc:
  14408. def to_s() join '/' end
  14409. def self.new_escaped(strings)
  14410. new strings.collect {|str| CGI.unescape str}
  14411. end
  14412. end
  14413. end
  14414. class Route #:nodoc:
  14415. attr_accessor :components, :known
  14416. attr_reader :path, :options, :keys, :defaults
  14417. def initialize(path, options = {})
  14418. @path, @options = path, options
  14419. initialize_components path
  14420. defaults, conditions = initialize_hashes options.dup
  14421. @defaults = defaults.dup
  14422. configure_components(defaults, conditions)
  14423. add_default_requirements
  14424. initialize_keys
  14425. end
  14426. def inspect
  14427. "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
  14428. end
  14429. def write_generation(generator = CodeGeneration::GenerationGenerator.new)
  14430. generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
  14431. if known.empty? then generator.go
  14432. else
  14433. # Alter the conditions to allow :action => 'index' to also catch :action => nil
  14434. altered_known = known.collect do |k, v|
  14435. if k == :action && v== 'index' then [k, [nil, 'index']]
  14436. else [k, v]
  14437. end
  14438. end
  14439. generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
  14440. end
  14441. generator
  14442. end
  14443. def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
  14444. g = generator.dup
  14445. g.share_locals_with generator
  14446. g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
  14447. known.each do |key, value|
  14448. if key == :controller then ControllerComponent.assign_controller(g, value)
  14449. else g.constant_result(key, value)
  14450. end
  14451. end
  14452. g.go
  14453. generator
  14454. end
  14455. def initialize_keys
  14456. @keys = (components.collect {|c| c.key} + known.keys).compact
  14457. @keys.freeze
  14458. end
  14459. def extra_keys(options)
  14460. options.keys - @keys
  14461. end
  14462. def matches_controller?(controller)
  14463. if known[:controller] then known[:controller] == controller
  14464. else
  14465. c = components.find {|c| c.key == :controller}
  14466. return false unless c
  14467. return c.condition.nil? || eval(Routing.test_condition('controller', c.condition))
  14468. end
  14469. end
  14470. protected
  14471. def initialize_components(path)
  14472. path = path.split('/') if path.is_a? String
  14473. path.shift if path.first.blank?
  14474. self.components = path.collect {|str| Component.new str}
  14475. end
  14476. def initialize_hashes(options)
  14477. path_keys = components.collect {|c| c.key }.compact
  14478. self.known = {}
  14479. defaults = options.delete(:defaults) || {}
  14480. conditions = options.delete(:require) || {}
  14481. conditions.update(options.delete(:requirements) || {})
  14482. options.each do |k, v|
  14483. if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
  14484. else known[k] = v
  14485. end
  14486. end
  14487. [defaults, conditions]
  14488. end
  14489. def configure_components(defaults, conditions)
  14490. components.each do |component|
  14491. if defaults.key?(component.key) then component.default = defaults[component.key]
  14492. elsif component.key == :action then component.default = 'index'
  14493. elsif component.key == :id then component.default = nil
  14494. end
  14495. component.condition = conditions[component.key] if conditions.key?(component.key)
  14496. end
  14497. end
  14498. def add_default_requirements
  14499. component_keys = components.collect {|c| c.key}
  14500. known[:action] ||= 'index' unless component_keys.include? :action
  14501. end
  14502. end
  14503. class RouteSet #:nodoc:
  14504. attr_reader :routes, :categories, :controller_to_selector
  14505. def initialize
  14506. @routes = []
  14507. @generation_methods = Hash.new(:generate_default_path)
  14508. end
  14509. def generate(options, request_or_recall_hash = {})
  14510. recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters
  14511. use_recall = true
  14512. controller = options[:controller]
  14513. options[:action] ||= 'index' if controller
  14514. recall_controller = recall[:controller]
  14515. if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/))
  14516. recall = {} if controller && controller[0] == ?/
  14517. options[:controller] = Routing.controller_relative_to(controller, recall_controller)
  14518. end
  14519. options = recall.dup if options.empty? # XXX move to url_rewriter?
  14520. keys_to_delete = []
  14521. Routing.treat_hash(options, keys_to_delete)
  14522. merged = recall.merge(options)
  14523. keys_to_delete.each {|key| merged.delete key}
  14524. expire_on = Routing.expiry_hash(options, recall)
  14525. generate_path(merged, options, expire_on)
  14526. end
  14527. def generate_path(merged, options, expire_on)
  14528. send @generation_methods[merged[:controller]], merged, options, expire_on
  14529. end
  14530. def generate_default_path(*args)
  14531. write_generation
  14532. generate_default_path(*args)
  14533. end
  14534. def write_generation
  14535. method_sources = []
  14536. @generation_methods = Hash.new(:generate_default_path)
  14537. categorize_routes.each do |controller, routes|
  14538. next unless routes.length < @routes.length
  14539. ivar = controller.gsub('/', '__')
  14540. method_name = "generate_path_for_#{ivar}".to_sym
  14541. instance_variable_set "@#{ivar}", routes
  14542. code = generation_code_for(ivar, method_name).to_s
  14543. method_sources << code
  14544. filename = "generated_code/routing/generation_for_controller_#{controller}.rb"
  14545. eval(code, nil, filename)
  14546. @generation_methods[controller.to_s] = method_name
  14547. @generation_methods[controller.to_sym] = method_name
  14548. end
  14549. code = generation_code_for('routes', 'generate_default_path').to_s
  14550. eval(code, nil, 'generated_code/routing/generation.rb')
  14551. return (method_sources << code)
  14552. end
  14553. def recognize(request)
  14554. string_path = request.path
  14555. string_path.chomp! if string_path[0] == ?/
  14556. path = string_path.split '/'
  14557. path.shift
  14558. hash = recognize_path(path)
  14559. return recognition_failed(request) unless hash && hash['controller']
  14560. controller = hash['controller']
  14561. hash['controller'] = controller.controller_path
  14562. request.path_parameters = hash
  14563. controller.new
  14564. end
  14565. alias :recognize! :recognize
  14566. def recognition_failed(request)
  14567. raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}"
  14568. end
  14569. def write_recognition
  14570. g = generator = CodeGeneration::RecognitionGenerator.new
  14571. g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"}
  14572. g.def "self.recognize_path(path)" do
  14573. each do |route|
  14574. g << 'index = 0'
  14575. route.write_recognition(g)
  14576. end
  14577. end
  14578. eval g.to_s, nil, 'generated/routing/recognition.rb'
  14579. return g.to_s
  14580. end
  14581. def generation_code_for(ivar = 'routes', method_name = nil)
  14582. routes = instance_variable_get('@' + ivar)
  14583. key_ivar = "@keys_for_#{ivar}"
  14584. instance_variable_set(key_ivar, routes.collect {|route| route.keys})
  14585. g = generator = CodeGeneration::GenerationGenerator.new
  14586. g.def "self.#{method_name}(merged, options, expire_on)" do
  14587. g << 'unused_count = options.length + 1'
  14588. g << "unused_keys = keys = options.keys"
  14589. g << 'path = nil'
  14590. routes.each_with_index do |route, index|
  14591. g << "new_unused_keys = keys - #{key_ivar}[#{index}]"
  14592. g << 'new_path = ('
  14593. g.source.indent do
  14594. if index.zero?
  14595. g << "new_unused_count = new_unused_keys.length"
  14596. g << "hash = merged; not_expired = true"
  14597. route.write_generation(g.dup)
  14598. else
  14599. g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp|
  14600. gp << "hash = merged; not_expired = true"
  14601. route.write_generation(gp)
  14602. end
  14603. end
  14604. end
  14605. g.source.lines.last << ' )' # Add the closing brace to the end line
  14606. g.if 'new_path' do
  14607. g << 'return new_path, [] if new_unused_count.zero?'
  14608. g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count'
  14609. end
  14610. end
  14611. g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path"
  14612. g << "return path, unused_keys"
  14613. end
  14614. return g
  14615. end
  14616. def categorize_routes
  14617. @categorized_routes = by_controller = Hash.new(self)
  14618. known_controllers.each do |name|
  14619. set = by_controller[name] = []
  14620. each do |route|
  14621. set << route if route.matches_controller? name
  14622. end
  14623. end
  14624. @categorized_routes
  14625. end
  14626. def known_controllers
  14627. @routes.inject([]) do |known, route|
  14628. if (controller = route.known[:controller])
  14629. if controller.is_a?(Regexp)
  14630. known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word}
  14631. else known << controller
  14632. end
  14633. end
  14634. known
  14635. end.uniq
  14636. end
  14637. def reload
  14638. NamedRoutes.clear
  14639. if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb'))
  14640. else connect(':controller/:action/:id', :action => 'index', :id => nil)
  14641. end
  14642. NamedRoutes.install
  14643. end
  14644. def connect(*args)
  14645. new_route = Route.new(*args)
  14646. @routes << new_route
  14647. return new_route
  14648. end
  14649. def draw
  14650. old_routes = @routes
  14651. @routes = []
  14652. begin yield self
  14653. rescue
  14654. @routes = old_routes
  14655. raise
  14656. end
  14657. write_generation
  14658. write_recognition
  14659. end
  14660. def empty?() @routes.empty? end
  14661. def each(&block) @routes.each(&block) end
  14662. # Defines a new named route with the provided name and arguments.
  14663. # This method need only be used when you wish to use a name that a RouteSet instance
  14664. # method exists for, such as categories.
  14665. #
  14666. # For example, map.categories '/categories', :controller => 'categories' will not work
  14667. # due to RouteSet#categories.
  14668. def named_route(name, path, hash = {})
  14669. route = connect(path, hash)
  14670. NamedRoutes.name_route(route, name)
  14671. route
  14672. end
  14673. def method_missing(name, *args)
  14674. (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args)
  14675. end
  14676. def extra_keys(options, recall = {})
  14677. generate(options.dup, recall).last
  14678. end
  14679. end
  14680. module NamedRoutes #:nodoc:
  14681. Helpers = []
  14682. class << self
  14683. def clear() Helpers.clear end
  14684. def hash_access_name(name)
  14685. "hash_for_#{name}_url"
  14686. end
  14687. def url_helper_name(name)
  14688. "#{name}_url"
  14689. end
  14690. def known_hash_for_route(route)
  14691. hash = route.known.symbolize_keys
  14692. route.defaults.each do |key, value|
  14693. hash[key.to_sym] ||= value if value
  14694. end
  14695. hash[:controller] = "/#{hash[:controller]}"
  14696. hash
  14697. end
  14698. def define_hash_access_method(route, name)
  14699. hash = known_hash_for_route(route)
  14700. define_method(hash_access_name(name)) do |*args|
  14701. args.first ? hash.merge(args.first) : hash
  14702. end
  14703. end
  14704. def name_route(route, name)
  14705. define_hash_access_method(route, name)
  14706. module_eval(%{def #{url_helper_name name}(options = {})
  14707. url_for(#{hash_access_name(name)}.merge(options))
  14708. end}, "generated/routing/named_routes/#{name}.rb")
  14709. protected url_helper_name(name), hash_access_name(name)
  14710. Helpers << url_helper_name(name).to_sym
  14711. Helpers << hash_access_name(name).to_sym
  14712. Helpers.uniq!
  14713. end
  14714. def install(cls = ActionController::Base)
  14715. cls.send :include, self
  14716. if cls.respond_to? :helper_method
  14717. Helpers.each do |helper_name|
  14718. cls.send :helper_method, helper_name
  14719. end
  14720. end
  14721. end
  14722. end
  14723. end
  14724. Routes = RouteSet.new
  14725. end
  14726. end
  14727. module ActionController
  14728. module Scaffolding # :nodoc:
  14729. def self.append_features(base)
  14730. super
  14731. base.extend(ClassMethods)
  14732. end
  14733. # Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions
  14734. # for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come
  14735. # with both controller logic and default templates that through introspection already know which fields to display
  14736. # and which input types to use. Example:
  14737. #
  14738. # class WeblogController < ActionController::Base
  14739. # scaffold :entry
  14740. # end
  14741. #
  14742. # This tiny piece of code will add all of the following methods to the controller:
  14743. #
  14744. # class WeblogController < ActionController::Base
  14745. # verify :method => :post, :only => [ :destroy, :create, :update ],
  14746. # :redirect_to => { :action => :list }
  14747. #
  14748. # def index
  14749. # list
  14750. # end
  14751. #
  14752. # def list
  14753. # @entries = Entry.find_all
  14754. # render_scaffold "list"
  14755. # end
  14756. #
  14757. # def show
  14758. # @entry = Entry.find(params[:id])
  14759. # render_scaffold
  14760. # end
  14761. #
  14762. # def destroy
  14763. # Entry.find(params[:id]).destroy
  14764. # redirect_to :action => "list"
  14765. # end
  14766. #
  14767. # def new
  14768. # @entry = Entry.new
  14769. # render_scaffold
  14770. # end
  14771. #
  14772. # def create
  14773. # @entry = Entry.new(params[:entry])
  14774. # if @entry.save
  14775. # flash[:notice] = "Entry was successfully created"
  14776. # redirect_to :action => "list"
  14777. # else
  14778. # render_scaffold('new')
  14779. # end
  14780. # end
  14781. #
  14782. # def edit
  14783. # @entry = Entry.find(params[:id])
  14784. # render_scaffold
  14785. # end
  14786. #
  14787. # def update
  14788. # @entry = Entry.find(params[:id])
  14789. # @entry.attributes = params[:entry]
  14790. #
  14791. # if @entry.save
  14792. # flash[:notice] = "Entry was successfully updated"
  14793. # redirect_to :action => "show", :id => @entry
  14794. # else
  14795. # render_scaffold('edit')
  14796. # end
  14797. # end
  14798. # end
  14799. #
  14800. # The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
  14801. # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
  14802. # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
  14803. # and one action at a time while relying on the rest of the scaffolded templates and actions.
  14804. module ClassMethods
  14805. # Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
  14806. # one is specifically provide through <tt>options[:class_name]</tt>. So <tt>scaffold :post</tt> would use Post as the class
  14807. # and @post/@posts for the instance variables.
  14808. #
  14809. # It's possible to use more than one scaffold in a single controller by specifying <tt>options[:suffix] = true</tt>. This will
  14810. # make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
  14811. # instead of just list, show, and post. If suffix is used, then no index method is added.
  14812. def scaffold(model_id, options = {})
  14813. options.assert_valid_keys(:class_name, :suffix)
  14814. singular_name = model_id.to_s
  14815. class_name = options[:class_name] || singular_name.camelize
  14816. plural_name = singular_name.pluralize
  14817. suffix = options[:suffix] ? "_#{singular_name}" : ""
  14818. unless options[:suffix]
  14819. module_eval <<-"end_eval", __FILE__, __LINE__
  14820. def index
  14821. list
  14822. end
  14823. end_eval
  14824. end
  14825. module_eval <<-"end_eval", __FILE__, __LINE__
  14826. verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
  14827. :redirect_to => { :action => :list#{suffix} }
  14828. def list#{suffix}
  14829. @#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10
  14830. render#{suffix}_scaffold "list#{suffix}"
  14831. end
  14832. def show#{suffix}
  14833. @#{singular_name} = #{class_name}.find(params[:id])
  14834. render#{suffix}_scaffold
  14835. end
  14836. def destroy#{suffix}
  14837. #{class_name}.find(params[:id]).destroy
  14838. redirect_to :action => "list#{suffix}"
  14839. end
  14840. def new#{suffix}
  14841. @#{singular_name} = #{class_name}.new
  14842. render#{suffix}_scaffold
  14843. end
  14844. def create#{suffix}
  14845. @#{singular_name} = #{class_name}.new(params[:#{singular_name}])
  14846. if @#{singular_name}.save
  14847. flash[:notice] = "#{class_name} was successfully created"
  14848. redirect_to :action => "list#{suffix}"
  14849. else
  14850. render#{suffix}_scaffold('new')
  14851. end
  14852. end
  14853. def edit#{suffix}
  14854. @#{singular_name} = #{class_name}.find(params[:id])
  14855. render#{suffix}_scaffold
  14856. end
  14857. def update#{suffix}
  14858. @#{singular_name} = #{class_name}.find(params[:id])
  14859. @#{singular_name}.attributes = params[:#{singular_name}]
  14860. if @#{singular_name}.save
  14861. flash[:notice] = "#{class_name} was successfully updated"
  14862. redirect_to :action => "show#{suffix}", :id => @#{singular_name}
  14863. else
  14864. render#{suffix}_scaffold('edit')
  14865. end
  14866. end
  14867. private
  14868. def render#{suffix}_scaffold(action=nil)
  14869. action ||= caller_method_name(caller)
  14870. # logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger
  14871. if template_exists?("\#{self.class.controller_path}/\#{action}")
  14872. render_action(action)
  14873. else
  14874. @scaffold_class = #{class_name}
  14875. @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
  14876. @scaffold_suffix = "#{suffix}"
  14877. add_instance_variables_to_assigns
  14878. @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))
  14879. if !active_layout.nil?
  14880. render_file(active_layout, nil, true)
  14881. else
  14882. render_file(scaffold_path("layout"))
  14883. end
  14884. end
  14885. end
  14886. def scaffold_path(template_name)
  14887. File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
  14888. end
  14889. def caller_method_name(caller)
  14890. caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
  14891. end
  14892. end_eval
  14893. end
  14894. end
  14895. end
  14896. end
  14897. require 'cgi'
  14898. require 'cgi/session'
  14899. require 'digest/md5'
  14900. require 'base64'
  14901. class CGI
  14902. class Session
  14903. # Return this session's underlying Session instance. Useful for the DB-backed session stores.
  14904. def model
  14905. @dbman.model if @dbman
  14906. end
  14907. # A session store backed by an Active Record class. A default class is
  14908. # provided, but any object duck-typing to an Active Record +Session+ class
  14909. # with text +session_id+ and +data+ attributes is sufficient.
  14910. #
  14911. # The default assumes a +sessions+ tables with columns:
  14912. # +id+ (numeric primary key),
  14913. # +session_id+ (text, or longtext if your session data exceeds 65K), and
  14914. # +data+ (text or longtext; careful if your session data exceeds 65KB).
  14915. # The +session_id+ column should always be indexed for speedy lookups.
  14916. # Session data is marshaled to the +data+ column in Base64 format.
  14917. # If the data you write is larger than the column's size limit,
  14918. # ActionController::SessionOverflowError will be raised.
  14919. #
  14920. # You may configure the table name, primary key, and data column.
  14921. # For example, at the end of config/environment.rb:
  14922. # CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
  14923. # CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
  14924. # CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
  14925. # Note that setting the primary key to the session_id frees you from
  14926. # having a separate id column if you don't want it. However, you must
  14927. # set session.model.id = session.session_id by hand! A before_filter
  14928. # on ApplicationController is a good place.
  14929. #
  14930. # Since the default class is a simple Active Record, you get timestamps
  14931. # for free if you add +created_at+ and +updated_at+ datetime columns to
  14932. # the +sessions+ table, making periodic session expiration a snap.
  14933. #
  14934. # You may provide your own session class implementation, whether a
  14935. # feature-packed Active Record or a bare-metal high-performance SQL
  14936. # store, by setting
  14937. # +CGI::Session::ActiveRecordStore.session_class = MySessionClass+
  14938. # You must implement these methods:
  14939. # self.find_by_session_id(session_id)
  14940. # initialize(hash_of_session_id_and_data)
  14941. # attr_reader :session_id
  14942. # attr_accessor :data
  14943. # save
  14944. # destroy
  14945. #
  14946. # The example SqlBypass class is a generic SQL session store. You may
  14947. # use it as a basis for high-performance database-specific stores.
  14948. class ActiveRecordStore
  14949. # The default Active Record class.
  14950. class Session < ActiveRecord::Base
  14951. # Customizable data column name. Defaults to 'data'.
  14952. cattr_accessor :data_column_name
  14953. self.data_column_name = 'data'
  14954. before_save :marshal_data!
  14955. before_save :raise_on_session_data_overflow!
  14956. class << self
  14957. # Don't try to reload ARStore::Session in dev mode.
  14958. def reloadable? #:nodoc:
  14959. false
  14960. end
  14961. def data_column_size_limit
  14962. @data_column_size_limit ||= columns_hash[@@data_column_name].limit
  14963. end
  14964. # Hook to set up sessid compatibility.
  14965. def find_by_session_id(session_id)
  14966. setup_sessid_compatibility!
  14967. find_by_session_id(session_id)
  14968. end
  14969. def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
  14970. def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
  14971. def create_table!
  14972. connection.execute <<-end_sql
  14973. CREATE TABLE #{table_name} (
  14974. id INTEGER PRIMARY KEY,
  14975. #{connection.quote_column_name('session_id')} TEXT UNIQUE,
  14976. #{connection.quote_column_name(@@data_column_name)} TEXT(255)
  14977. )
  14978. end_sql
  14979. end
  14980. def drop_table!
  14981. connection.execute "DROP TABLE #{table_name}"
  14982. end
  14983. private
  14984. # Compatibility with tables using sessid instead of session_id.
  14985. def setup_sessid_compatibility!
  14986. # Reset column info since it may be stale.
  14987. reset_column_information
  14988. if columns_hash['sessid']
  14989. def self.find_by_session_id(*args)
  14990. find_by_sessid(*args)
  14991. end
  14992. define_method(:session_id) { sessid }
  14993. define_method(:session_id=) { |session_id| self.sessid = session_id }
  14994. else
  14995. def self.find_by_session_id(session_id)
  14996. find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
  14997. end
  14998. end
  14999. end
  15000. end
  15001. # Lazy-unmarshal session state.
  15002. def data
  15003. @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
  15004. end
  15005. # Has the session been loaded yet?
  15006. def loaded?
  15007. !! @data
  15008. end
  15009. private
  15010. attr_writer :data
  15011. def marshal_data!
  15012. return false if !loaded?
  15013. write_attribute(@@data_column_name, self.class.marshal(self.data))
  15014. end
  15015. # Ensures that the data about to be stored in the database is not
  15016. # larger than the data storage column. Raises
  15017. # ActionController::SessionOverflowError.
  15018. def raise_on_session_data_overflow!
  15019. return false if !loaded?
  15020. limit = self.class.data_column_size_limit
  15021. if loaded? and limit and read_attribute(@@data_column_name).size > limit
  15022. raise ActionController::SessionOverflowError
  15023. end
  15024. end
  15025. end
  15026. # A barebones session store which duck-types with the default session
  15027. # store but bypasses Active Record and issues SQL directly. This is
  15028. # an example session model class meant as a basis for your own classes.
  15029. #
  15030. # The database connection, table name, and session id and data columns
  15031. # are configurable class attributes. Marshaling and unmarshaling
  15032. # are implemented as class methods that you may override. By default,
  15033. # marshaling data is +Base64.encode64(Marshal.dump(data))+ and
  15034. # unmarshaling data is +Marshal.load(Base64.decode64(data))+.
  15035. #
  15036. # This marshaling behavior is intended to store the widest range of
  15037. # binary session data in a +text+ column. For higher performance,
  15038. # store in a +blob+ column instead and forgo the Base64 encoding.
  15039. class SqlBypass
  15040. # Use the ActiveRecord::Base.connection by default.
  15041. cattr_accessor :connection
  15042. # The table name defaults to 'sessions'.
  15043. cattr_accessor :table_name
  15044. @@table_name = 'sessions'
  15045. # The session id field defaults to 'session_id'.
  15046. cattr_accessor :session_id_column
  15047. @@session_id_column = 'session_id'
  15048. # The data field defaults to 'data'.
  15049. cattr_accessor :data_column
  15050. @@data_column = 'data'
  15051. class << self
  15052. def connection
  15053. @@connection ||= ActiveRecord::Base.connection
  15054. end
  15055. # Look up a session by id and unmarshal its data if found.
  15056. def find_by_session_id(session_id)
  15057. if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
  15058. new(:session_id => session_id, :marshaled_data => record['data'])
  15059. end
  15060. end
  15061. def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
  15062. def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
  15063. def create_table!
  15064. @@connection.execute <<-end_sql
  15065. CREATE TABLE #{table_name} (
  15066. id INTEGER PRIMARY KEY,
  15067. #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
  15068. #{@@connection.quote_column_name(data_column)} TEXT
  15069. )
  15070. end_sql
  15071. end
  15072. def drop_table!
  15073. @@connection.execute "DROP TABLE #{table_name}"
  15074. end
  15075. end
  15076. attr_reader :session_id
  15077. attr_writer :data
  15078. # Look for normal and marshaled data, self.find_by_session_id's way of
  15079. # telling us to postpone unmarshaling until the data is requested.
  15080. # We need to handle a normal data attribute in case of a new record.
  15081. def initialize(attributes)
  15082. @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
  15083. @new_record = @marshaled_data.nil?
  15084. end
  15085. def new_record?
  15086. @new_record
  15087. end
  15088. # Lazy-unmarshal session state.
  15089. def data
  15090. unless @data
  15091. if @marshaled_data
  15092. @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
  15093. else
  15094. @data = {}
  15095. end
  15096. end
  15097. @data
  15098. end
  15099. def loaded?
  15100. !! @data
  15101. end
  15102. def save
  15103. return false if !loaded?
  15104. marshaled_data = self.class.marshal(data)
  15105. if @new_record
  15106. @new_record = false
  15107. @@connection.update <<-end_sql, 'Create session'
  15108. INSERT INTO #{@@table_name} (
  15109. #{@@connection.quote_column_name(@@session_id_column)},
  15110. #{@@connection.quote_column_name(@@data_column)} )
  15111. VALUES (
  15112. #{@@connection.quote(session_id)},
  15113. #{@@connection.quote(marshaled_data)} )
  15114. end_sql
  15115. else
  15116. @@connection.update <<-end_sql, 'Update session'
  15117. UPDATE #{@@table_name}
  15118. SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
  15119. WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
  15120. end_sql
  15121. end
  15122. end
  15123. def destroy
  15124. unless @new_record
  15125. @@connection.delete <<-end_sql, 'Destroy session'
  15126. DELETE FROM #{@@table_name}
  15127. WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
  15128. end_sql
  15129. end
  15130. end
  15131. end
  15132. # The class used for session storage. Defaults to
  15133. # CGI::Session::ActiveRecordStore::Session.
  15134. cattr_accessor :session_class
  15135. self.session_class = Session
  15136. # Find or instantiate a session given a CGI::Session.
  15137. def initialize(session, option = nil)
  15138. session_id = session.session_id
  15139. unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
  15140. unless session.new_session
  15141. raise CGI::Session::NoSession, 'uninitialized session'
  15142. end
  15143. @session = @@session_class.new(:session_id => session_id, :data => {})
  15144. # session saving can be lazy again, because of improved component implementation
  15145. # therefore next line gets commented out:
  15146. # @session.save
  15147. end
  15148. end
  15149. # Access the underlying session model.
  15150. def model
  15151. @session
  15152. end
  15153. # Restore session state. The session model handles unmarshaling.
  15154. def restore
  15155. if @session
  15156. @session.data
  15157. end
  15158. end
  15159. # Save session store.
  15160. def update
  15161. if @session
  15162. ActiveRecord::Base.silence { @session.save }
  15163. end
  15164. end
  15165. # Save and close the session store.
  15166. def close
  15167. if @session
  15168. update
  15169. @session = nil
  15170. end
  15171. end
  15172. # Delete and close the session store.
  15173. def delete
  15174. if @session
  15175. ActiveRecord::Base.silence { @session.destroy }
  15176. @session = nil
  15177. end
  15178. end
  15179. protected
  15180. def logger
  15181. ActionController::Base.logger rescue nil
  15182. end
  15183. end
  15184. end
  15185. end
  15186. #!/usr/local/bin/ruby -w
  15187. # This is a really simple session storage daemon, basically just a hash,
  15188. # which is enabled for DRb access.
  15189. require 'drb'
  15190. session_hash = Hash.new
  15191. session_hash.instance_eval { @mutex = Mutex.new }
  15192. class <<session_hash
  15193. def []=(key, value)
  15194. @mutex.synchronize do
  15195. super(key, value)
  15196. end
  15197. end
  15198. def [](key)
  15199. @mutex.synchronize do
  15200. super(key)
  15201. end
  15202. end
  15203. def delete(key)
  15204. @mutex.synchronize do
  15205. super(key)
  15206. end
  15207. end
  15208. end
  15209. DRb.start_service('druby://127.0.0.1:9192', session_hash)
  15210. DRb.thread.joinrequire 'cgi'
  15211. require 'cgi/session'
  15212. require 'drb'
  15213. class CGI #:nodoc:all
  15214. class Session
  15215. class DRbStore
  15216. @@session_data = DRbObject.new(nil, 'druby://localhost:9192')
  15217. def initialize(session, option=nil)
  15218. @session_id = session.session_id
  15219. end
  15220. def restore
  15221. @h = @@session_data[@session_id] || {}
  15222. end
  15223. def update
  15224. @@session_data[@session_id] = @h
  15225. end
  15226. def close
  15227. update
  15228. end
  15229. def delete
  15230. @@session_data.delete(@session_id)
  15231. end
  15232. end
  15233. end
  15234. end
  15235. # cgi/session/memcached.rb - persistent storage of marshalled session data
  15236. #
  15237. # == Overview
  15238. #
  15239. # This file provides the CGI::Session::MemCache class, which builds
  15240. # persistence of storage data on top of the MemCache library. See
  15241. # cgi/session.rb for more details on session storage managers.
  15242. #
  15243. begin
  15244. require 'cgi/session'
  15245. require 'memcache'
  15246. class CGI
  15247. class Session
  15248. # MemCache-based session storage class.
  15249. #
  15250. # This builds upon the top-level MemCache class provided by the
  15251. # library file memcache.rb. Session data is marshalled and stored
  15252. # in a memcached cache.
  15253. class MemCacheStore
  15254. def check_id(id) #:nodoc:#
  15255. /[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
  15256. end
  15257. # Create a new CGI::Session::MemCache instance
  15258. #
  15259. # This constructor is used internally by CGI::Session. The
  15260. # user does not generally need to call it directly.
  15261. #
  15262. # +session+ is the session for which this instance is being
  15263. # created. The session id must only contain alphanumeric
  15264. # characters; automatically generated session ids observe
  15265. # this requirement.
  15266. #
  15267. # +options+ is a hash of options for the initializer. The
  15268. # following options are recognized:
  15269. #
  15270. # cache:: an instance of a MemCache client to use as the
  15271. # session cache.
  15272. #
  15273. # expires:: an expiry time value to use for session entries in
  15274. # the session cache. +expires+ is interpreted in seconds
  15275. # relative to the current time if it's less than 60*60*24*30
  15276. # (30 days), or as an absolute Unix time (e.g., Time#to_i) if
  15277. # greater. If +expires+ is +0+, or not passed on +options+,
  15278. # the entry will never expire.
  15279. #
  15280. # This session's memcache entry will be created if it does
  15281. # not exist, or retrieved if it does.
  15282. def initialize(session, options = {})
  15283. id = session.session_id
  15284. unless check_id(id)
  15285. raise ArgumentError, "session_id '%s' is invalid" % id
  15286. end
  15287. @cache = options['cache'] || MemCache.new('localhost')
  15288. @expires = options['expires'] || 0
  15289. @session_key = "session:#{id}"
  15290. @session_data = {}
  15291. end
  15292. # Restore session state from the session's memcache entry.
  15293. #
  15294. # Returns the session state as a hash.
  15295. def restore
  15296. begin
  15297. @session_data = @cache[@session_key] || {}
  15298. rescue
  15299. @session_data = {}
  15300. end
  15301. end
  15302. # Save session state to the session's memcache entry.
  15303. def update
  15304. begin
  15305. @cache.set(@session_key, @session_data, @expires)
  15306. rescue
  15307. # Ignore session update failures.
  15308. end
  15309. end
  15310. # Update and close the session's memcache entry.
  15311. def close
  15312. update
  15313. end
  15314. # Delete the session's memcache entry.
  15315. def delete
  15316. begin
  15317. @cache.delete(@session_key)
  15318. rescue
  15319. # Ignore session delete failures.
  15320. end
  15321. @session_data = {}
  15322. end
  15323. end
  15324. end
  15325. end
  15326. rescue LoadError
  15327. # MemCache wasn't available so neither can the store be
  15328. end
  15329. require 'action_controller/session/drb_store'
  15330. require 'action_controller/session/mem_cache_store'
  15331. if Object.const_defined?(:ActiveRecord)
  15332. require 'action_controller/session/active_record_store'
  15333. end
  15334. module ActionController #:nodoc:
  15335. module SessionManagement #:nodoc:
  15336. def self.included(base)
  15337. base.extend(ClassMethods)
  15338. base.send :alias_method, :process_without_session_management_support, :process
  15339. base.send :alias_method, :process, :process_with_session_management_support
  15340. base.send :alias_method, :process_cleanup_without_session_management_support, :process_cleanup
  15341. base.send :alias_method, :process_cleanup, :process_cleanup_with_session_management_support
  15342. end
  15343. module ClassMethods
  15344. # Set the session store to be used for keeping the session data between requests. The default is using the
  15345. # file system, but you can also specify one of the other included stores (:active_record_store, :drb_store,
  15346. # :mem_cache_store, or :memory_store) or use your own class.
  15347. def session_store=(store)
  15348. ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
  15349. store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
  15350. end
  15351. # Returns the session store class currently used.
  15352. def session_store
  15353. ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
  15354. end
  15355. # Returns the hash used to configure the session. Example use:
  15356. #
  15357. # ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
  15358. def session_options
  15359. ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
  15360. end
  15361. # Specify how sessions ought to be managed for a subset of the actions on
  15362. # the controller. Like filters, you can specify <tt>:only</tt> and
  15363. # <tt>:except</tt> clauses to restrict the subset, otherwise options
  15364. # apply to all actions on this controller.
  15365. #
  15366. # The session options are inheritable, as well, so if you specify them in
  15367. # a parent controller, they apply to controllers that extend the parent.
  15368. #
  15369. # Usage:
  15370. #
  15371. # # turn off session management for all actions.
  15372. # session :off
  15373. #
  15374. # # turn off session management for all actions _except_ foo and bar.
  15375. # session :off, :except => %w(foo bar)
  15376. #
  15377. # # turn off session management for only the foo and bar actions.
  15378. # session :off, :only => %w(foo bar)
  15379. #
  15380. # # the session will only work over HTTPS, but only for the foo action
  15381. # session :only => :foo, :session_secure => true
  15382. #
  15383. # # the session will only be disabled for 'foo', and only if it is
  15384. # # requested as a web service
  15385. # session :off, :only => :foo,
  15386. # :if => Proc.new { |req| req.parameters[:ws] }
  15387. #
  15388. # All session options described for ActionController::Base.process_cgi
  15389. # are valid arguments.
  15390. def session(*args)
  15391. options = Hash === args.last ? args.pop : {}
  15392. options[:disabled] = true if !args.empty?
  15393. options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
  15394. options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
  15395. if options[:only] && options[:except]
  15396. raise ArgumentError, "only one of either :only or :except are allowed"
  15397. end
  15398. write_inheritable_array("session_options", [options])
  15399. end
  15400. def cached_session_options #:nodoc:
  15401. @session_options ||= read_inheritable_attribute("session_options") || []
  15402. end
  15403. def session_options_for(request, action) #:nodoc:
  15404. if (session_options = cached_session_options).empty?
  15405. {}
  15406. else
  15407. options = {}
  15408. action = action.to_s
  15409. session_options.each do |opts|
  15410. next if opts[:if] && !opts[:if].call(request)
  15411. if opts[:only] && opts[:only].include?(action)
  15412. options.merge!(opts)
  15413. elsif opts[:except] && !opts[:except].include?(action)
  15414. options.merge!(opts)
  15415. elsif !opts[:only] && !opts[:except]
  15416. options.merge!(opts)
  15417. end
  15418. end
  15419. if options.empty? then options
  15420. else
  15421. options.delete :only
  15422. options.delete :except
  15423. options.delete :if
  15424. options[:disabled] ? false : options
  15425. end
  15426. end
  15427. end
  15428. end
  15429. def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
  15430. set_session_options(request)
  15431. process_without_session_management_support(request, response, method, *arguments)
  15432. end
  15433. private
  15434. def set_session_options(request)
  15435. request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
  15436. end
  15437. def process_cleanup_with_session_management_support
  15438. process_cleanup_without_session_management_support
  15439. clear_persistent_model_associations
  15440. end
  15441. # Clear cached associations in session data so they don't overflow
  15442. # the database field. Only applies to ActiveRecordStore since there
  15443. # is not a standard way to iterate over session data.
  15444. def clear_persistent_model_associations #:doc:
  15445. if defined?(@session) && @session.instance_variables.include?('@data')
  15446. session_data = @session.instance_variable_get('@data')
  15447. if session_data && session_data.respond_to?(:each_value)
  15448. session_data.each_value do |obj|
  15449. obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
  15450. end
  15451. end
  15452. end
  15453. end
  15454. end
  15455. end
  15456. module ActionController #:nodoc:
  15457. # Methods for sending files and streams to the browser instead of rendering.
  15458. module Streaming
  15459. DEFAULT_SEND_FILE_OPTIONS = {
  15460. :type => 'application/octet-stream'.freeze,
  15461. :disposition => 'attachment'.freeze,
  15462. :stream => true,
  15463. :buffer_size => 4096
  15464. }.freeze
  15465. protected
  15466. # Sends the file by streaming it 4096 bytes at a time. This way the
  15467. # whole file doesn't need to be read into memory at once. This makes
  15468. # it feasible to send even large files.
  15469. #
  15470. # Be careful to sanitize the path parameter if it coming from a web
  15471. # page. send_file(params[:path]) allows a malicious user to
  15472. # download any file on your server.
  15473. #
  15474. # Options:
  15475. # * <tt>:filename</tt> - suggests a filename for the browser to use.
  15476. # Defaults to File.basename(path).
  15477. # * <tt>:type</tt> - specifies an HTTP content type.
  15478. # Defaults to 'application/octet-stream'.
  15479. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
  15480. # Valid values are 'inline' and 'attachment' (default).
  15481. # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
  15482. # or to read the entire file before sending (false). Defaults to true.
  15483. # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
  15484. # Defaults to 4096.
  15485. # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
  15486. #
  15487. # The default Content-Type and Content-Disposition headers are
  15488. # set to download arbitrary binary files in as many browsers as
  15489. # possible. IE versions 4, 5, 5.5, and 6 are all known to have
  15490. # a variety of quirks (especially when downloading over SSL).
  15491. #
  15492. # Simple download:
  15493. # send_file '/path/to.zip'
  15494. #
  15495. # Show a JPEG in the browser:
  15496. # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
  15497. #
  15498. # Show a 404 page in the browser:
  15499. # send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404
  15500. #
  15501. # Read about the other Content-* HTTP headers if you'd like to
  15502. # provide the user with more information (such as Content-Description).
  15503. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
  15504. #
  15505. # Also be aware that the document may be cached by proxies and browsers.
  15506. # The Pragma and Cache-Control headers declare how the file may be cached
  15507. # by intermediaries. They default to require clients to validate with
  15508. # the server before releasing cached responses. See
  15509. # http://www.mnot.net/cache_docs/ for an overview of web caching and
  15510. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
  15511. # for the Cache-Control header spec.
  15512. def send_file(path, options = {}) #:doc:
  15513. raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
  15514. options[:length] ||= File.size(path)
  15515. options[:filename] ||= File.basename(path)
  15516. send_file_headers! options
  15517. @performed_render = false
  15518. if options[:stream]
  15519. render :status => options[:status], :text => Proc.new { |response, output|
  15520. logger.info "Streaming file #{path}" unless logger.nil?
  15521. len = options[:buffer_size] || 4096
  15522. File.open(path, 'rb') do |file|
  15523. if output.respond_to?(:syswrite)
  15524. begin
  15525. while true
  15526. output.syswrite(file.sysread(len))
  15527. end
  15528. rescue EOFError
  15529. end
  15530. else
  15531. while buf = file.read(len)
  15532. output.write(buf)
  15533. end
  15534. end
  15535. end
  15536. }
  15537. else
  15538. logger.info "Sending file #{path}" unless logger.nil?
  15539. File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
  15540. end
  15541. end
  15542. # Send binary data to the user as a file download. May set content type, apparent file name,
  15543. # and specify whether to show data inline or download as an attachment.
  15544. #
  15545. # Options:
  15546. # * <tt>:filename</tt> - Suggests a filename for the browser to use.
  15547. # * <tt>:type</tt> - specifies an HTTP content type.
  15548. # Defaults to 'application/octet-stream'.
  15549. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
  15550. # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
  15551. # Valid values are 'inline' and 'attachment' (default).
  15552. #
  15553. # Generic data download:
  15554. # send_data buffer
  15555. #
  15556. # Download a dynamically-generated tarball:
  15557. # send_data generate_tgz('dir'), :filename => 'dir.tgz'
  15558. #
  15559. # Display an image Active Record in the browser:
  15560. # send_data image.data, :type => image.content_type, :disposition => 'inline'
  15561. #
  15562. # See +send_file+ for more information on HTTP Content-* headers and caching.
  15563. def send_data(data, options = {}) #:doc:
  15564. logger.info "Sending data #{options[:filename]}" unless logger.nil?
  15565. send_file_headers! options.merge(:length => data.size)
  15566. @performed_render = false
  15567. render :status => options[:status], :text => data
  15568. end
  15569. private
  15570. def send_file_headers!(options)
  15571. options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
  15572. [:length, :type, :disposition].each do |arg|
  15573. raise ArgumentError, ":#{arg} option required" if options[arg].nil?
  15574. end
  15575. disposition = options[:disposition].dup || 'attachment'
  15576. disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
  15577. @headers.update(
  15578. 'Content-Length' => options[:length],
  15579. 'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
  15580. 'Content-Disposition' => disposition,
  15581. 'Content-Transfer-Encoding' => 'binary'
  15582. )
  15583. # Fix a problem with IE 6.0 on opening downloaded files:
  15584. # If Cache-Control: no-cache is set (which Rails does by default),
  15585. # IE removes the file it just downloaded from its cache immediately
  15586. # after it displays the "open/save" dialog, which means that if you
  15587. # hit "open" the file isn't there anymore when the application that
  15588. # is called for handling the download is run, so let's workaround that
  15589. @headers['Cache-Control'] = 'private' if @headers['Cache-Control'] == 'no-cache'
  15590. end
  15591. end
  15592. end
  15593. require File.dirname(__FILE__) + '/assertions'
  15594. require File.dirname(__FILE__) + '/deprecated_assertions'
  15595. module ActionController #:nodoc:
  15596. class Base
  15597. # Process a test request called with a +TestRequest+ object.
  15598. def self.process_test(request)
  15599. new.process_test(request)
  15600. end
  15601. def process_test(request) #:nodoc:
  15602. process(request, TestResponse.new)
  15603. end
  15604. def process_with_test(*args)
  15605. returning process_without_test(*args) do
  15606. add_variables_to_assigns
  15607. end
  15608. end
  15609. alias_method :process_without_test, :process
  15610. alias_method :process, :process_with_test
  15611. end
  15612. class TestRequest < AbstractRequest #:nodoc:
  15613. attr_accessor :cookies, :session_options
  15614. attr_accessor :query_parameters, :request_parameters, :path, :session, :env
  15615. attr_accessor :host
  15616. def initialize(query_parameters = nil, request_parameters = nil, session = nil)
  15617. @query_parameters = query_parameters || {}
  15618. @request_parameters = request_parameters || {}
  15619. @session = session || TestSession.new
  15620. initialize_containers
  15621. initialize_default_values
  15622. super()
  15623. end
  15624. def reset_session
  15625. @session = {}
  15626. end
  15627. def raw_post
  15628. if raw_post = env['RAW_POST_DATA']
  15629. raw_post
  15630. else
  15631. params = self.request_parameters.dup
  15632. %w(controller action only_path).each do |k|
  15633. params.delete(k)
  15634. params.delete(k.to_sym)
  15635. end
  15636. params.map { |k,v| [ CGI.escape(k.to_s), CGI.escape(v.to_s) ].join('=') }.sort.join('&')
  15637. end
  15638. end
  15639. def port=(number)
  15640. @env["SERVER_PORT"] = number.to_i
  15641. @port_as_int = nil
  15642. end
  15643. def action=(action_name)
  15644. @query_parameters.update({ "action" => action_name })
  15645. @parameters = nil
  15646. end
  15647. # Used to check AbstractRequest's request_uri functionality.
  15648. # Disables the use of @path and @request_uri so superclass can handle those.
  15649. def set_REQUEST_URI(value)
  15650. @env["REQUEST_URI"] = value
  15651. @request_uri = nil
  15652. @path = nil
  15653. end
  15654. def request_uri=(uri)
  15655. @request_uri = uri
  15656. @path = uri.split("?").first
  15657. end
  15658. def remote_addr=(addr)
  15659. @env['REMOTE_ADDR'] = addr
  15660. end
  15661. def remote_addr
  15662. @env['REMOTE_ADDR']
  15663. end
  15664. def request_uri
  15665. @request_uri || super()
  15666. end
  15667. def path
  15668. @path || super()
  15669. end
  15670. def assign_parameters(controller_path, action, parameters)
  15671. parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
  15672. extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
  15673. non_path_parameters = get? ? query_parameters : request_parameters
  15674. parameters.each do |key, value|
  15675. if value.is_a? Fixnum
  15676. value = value.to_s
  15677. elsif value.is_a? Array
  15678. value = ActionController::Routing::PathComponent::Result.new(value)
  15679. end
  15680. if extra_keys.include?(key.to_sym)
  15681. non_path_parameters[key] = value
  15682. else
  15683. path_parameters[key.to_s] = value
  15684. end
  15685. end
  15686. end
  15687. def recycle!
  15688. self.request_parameters = {}
  15689. self.query_parameters = {}
  15690. self.path_parameters = {}
  15691. @request_method, @accepts, @content_type = nil, nil, nil
  15692. end
  15693. private
  15694. def initialize_containers
  15695. @env, @cookies = {}, {}
  15696. end
  15697. def initialize_default_values
  15698. @host = "test.host"
  15699. @request_uri = "/"
  15700. self.remote_addr = "0.0.0.0"
  15701. @env["SERVER_PORT"] = 80
  15702. @env['REQUEST_METHOD'] = "GET"
  15703. end
  15704. end
  15705. # A refactoring of TestResponse to allow the same behavior to be applied
  15706. # to the "real" CgiResponse class in integration tests.
  15707. module TestResponseBehavior #:nodoc:
  15708. # the response code of the request
  15709. def response_code
  15710. headers['Status'][0,3].to_i rescue 0
  15711. end
  15712. # returns a String to ensure compatibility with Net::HTTPResponse
  15713. def code
  15714. headers['Status'].to_s.split(' ')[0]
  15715. end
  15716. def message
  15717. headers['Status'].to_s.split(' ',2)[1]
  15718. end
  15719. # was the response successful?
  15720. def success?
  15721. response_code == 200
  15722. end
  15723. # was the URL not found?
  15724. def missing?
  15725. response_code == 404
  15726. end
  15727. # were we redirected?
  15728. def redirect?
  15729. (300..399).include?(response_code)
  15730. end
  15731. # was there a server-side error?
  15732. def error?
  15733. (500..599).include?(response_code)
  15734. end
  15735. alias_method :server_error?, :error?
  15736. # returns the redirection location or nil
  15737. def redirect_url
  15738. redirect? ? headers['location'] : nil
  15739. end
  15740. # does the redirect location match this regexp pattern?
  15741. def redirect_url_match?( pattern )
  15742. return false if redirect_url.nil?
  15743. p = Regexp.new(pattern) if pattern.class == String
  15744. p = pattern if pattern.class == Regexp
  15745. return false if p.nil?
  15746. p.match(redirect_url) != nil
  15747. end
  15748. # returns the template path of the file which was used to
  15749. # render this response (or nil)
  15750. def rendered_file(with_controller=false)
  15751. unless template.first_render.nil?
  15752. unless with_controller
  15753. template.first_render
  15754. else
  15755. template.first_render.split('/').last || template.first_render
  15756. end
  15757. end
  15758. end
  15759. # was this template rendered by a file?
  15760. def rendered_with_file?
  15761. !rendered_file.nil?
  15762. end
  15763. # a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!)
  15764. def flash
  15765. session['flash'] || {}
  15766. end
  15767. # do we have a flash?
  15768. def has_flash?
  15769. !session['flash'].empty?
  15770. end
  15771. # do we have a flash that has contents?
  15772. def has_flash_with_contents?
  15773. !flash.empty?
  15774. end
  15775. # does the specified flash object exist?
  15776. def has_flash_object?(name=nil)
  15777. !flash[name].nil?
  15778. end
  15779. # does the specified object exist in the session?
  15780. def has_session_object?(name=nil)
  15781. !session[name].nil?
  15782. end
  15783. # a shortcut to the template.assigns
  15784. def template_objects
  15785. template.assigns || {}
  15786. end
  15787. # does the specified template object exist?
  15788. def has_template_object?(name=nil)
  15789. !template_objects[name].nil?
  15790. end
  15791. # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
  15792. # Example:
  15793. #
  15794. # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
  15795. def cookies
  15796. headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
  15797. end
  15798. # Returns binary content (downloadable file), converted to a String
  15799. def binary_content
  15800. raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc)
  15801. require 'stringio'
  15802. sio = StringIO.new
  15803. begin
  15804. $stdout = sio
  15805. body.call
  15806. ensure
  15807. $stdout = STDOUT
  15808. end
  15809. sio.rewind
  15810. sio.read
  15811. end
  15812. end
  15813. class TestResponse < AbstractResponse #:nodoc:
  15814. include TestResponseBehavior
  15815. end
  15816. class TestSession #:nodoc:
  15817. def initialize(attributes = {})
  15818. @attributes = attributes
  15819. end
  15820. def [](key)
  15821. @attributes[key]
  15822. end
  15823. def []=(key, value)
  15824. @attributes[key] = value
  15825. end
  15826. def session_id
  15827. ""
  15828. end
  15829. def update() end
  15830. def close() end
  15831. def delete() @attributes = {} end
  15832. end
  15833. # Essentially generates a modified Tempfile object similar to the object
  15834. # you'd get from the standard library CGI module in a multipart
  15835. # request. This means you can use an ActionController::TestUploadedFile
  15836. # object in the params of a test request in order to simulate
  15837. # a file upload.
  15838. #
  15839. # Usage example, within a functional test:
  15840. # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
  15841. class TestUploadedFile
  15842. # The filename, *not* including the path, of the "uploaded" file
  15843. attr_reader :original_filename
  15844. # The content type of the "uploaded" file
  15845. attr_reader :content_type
  15846. def initialize(path, content_type = 'text/plain')
  15847. raise "file does not exist" unless File.exist?(path)
  15848. @content_type = content_type
  15849. @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
  15850. @tempfile = Tempfile.new(@original_filename)
  15851. FileUtils.copy_file(path, @tempfile.path)
  15852. end
  15853. def path #:nodoc:
  15854. @tempfile.path
  15855. end
  15856. alias local_path path
  15857. def method_missing(method_name, *args, &block) #:nodoc:
  15858. @tempfile.send(method_name, *args, &block)
  15859. end
  15860. end
  15861. module TestProcess
  15862. def self.included(base)
  15863. # execute the request simulating a specific http method and set/volley the response
  15864. %w( get post put delete head ).each do |method|
  15865. base.class_eval <<-EOV, __FILE__, __LINE__
  15866. def #{method}(action, parameters = nil, session = nil, flash = nil)
  15867. @request.env['REQUEST_METHOD'] = "#{method.upcase}" if @request
  15868. process(action, parameters, session, flash)
  15869. end
  15870. EOV
  15871. end
  15872. end
  15873. # execute the request and set/volley the response
  15874. def process(action, parameters = nil, session = nil, flash = nil)
  15875. # Sanity check for required instance variables so we can give an
  15876. # understandable error message.
  15877. %w(controller request response).each do |iv_name|
  15878. raise "@#{iv_name} is nil: make sure you set it in your test's setup method." if instance_variable_get("@#{iv_name}").nil?
  15879. end
  15880. @request.recycle!
  15881. @html_document = nil
  15882. @request.env['REQUEST_METHOD'] ||= "GET"
  15883. @request.action = action.to_s
  15884. parameters ||= {}
  15885. @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
  15886. @request.session = ActionController::TestSession.new(session) unless session.nil?
  15887. @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
  15888. build_request_uri(action, parameters)
  15889. @controller.process(@request, @response)
  15890. end
  15891. def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
  15892. @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
  15893. @request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
  15894. returning self.send(request_method, action, parameters, session, flash) do
  15895. @request.env.delete 'HTTP_X_REQUESTED_WITH'
  15896. @request.env.delete 'HTTP_ACCEPT'
  15897. end
  15898. end
  15899. alias xhr :xml_http_request
  15900. def follow_redirect
  15901. if @response.redirected_to[:controller]
  15902. raise "Can't follow redirects outside of current controller (#{@response.redirected_to[:controller]})"
  15903. end
  15904. get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
  15905. end
  15906. def assigns(key = nil)
  15907. if key.nil?
  15908. @response.template.assigns
  15909. else
  15910. @response.template.assigns[key.to_s]
  15911. end
  15912. end
  15913. def session
  15914. @response.session
  15915. end
  15916. def flash
  15917. @response.flash
  15918. end
  15919. def cookies
  15920. @response.cookies
  15921. end
  15922. def redirect_to_url
  15923. @response.redirect_url
  15924. end
  15925. def build_request_uri(action, parameters)
  15926. unless @request.env['REQUEST_URI']
  15927. options = @controller.send(:rewrite_options, parameters)
  15928. options.update(:only_path => true, :action => action)
  15929. url = ActionController::UrlRewriter.new(@request, parameters)
  15930. @request.set_REQUEST_URI(url.rewrite(options))
  15931. end
  15932. end
  15933. def html_document
  15934. @html_document ||= HTML::Document.new(@response.body)
  15935. end
  15936. def find_tag(conditions)
  15937. html_document.find(conditions)
  15938. end
  15939. def find_all_tag(conditions)
  15940. html_document.find_all(conditions)
  15941. end
  15942. def method_missing(selector, *args)
  15943. return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
  15944. return super
  15945. end
  15946. # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example:
  15947. # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
  15948. def fixture_file_upload(path, mime_type = nil)
  15949. ActionController::TestUploadedFile.new(
  15950. Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
  15951. mime_type
  15952. )
  15953. end
  15954. # A helper to make it easier to test different route configurations.
  15955. # This method temporarily replaces ActionController::Routing::Routes
  15956. # with a new RouteSet instance.
  15957. #
  15958. # The new instance is yielded to the passed block. Typically the block
  15959. # will create some routes using map.draw { map.connect ... }:
  15960. #
  15961. # with_routing do |set|
  15962. # set.draw { set.connect ':controller/:id/:action' }
  15963. # assert_equal(
  15964. # ['/content/10/show', {}],
  15965. # set.generate(:controller => 'content', :id => 10, :action => 'show')
  15966. # )
  15967. # end
  15968. #
  15969. def with_routing
  15970. real_routes = ActionController::Routing::Routes
  15971. ActionController::Routing.send :remove_const, :Routes
  15972. temporary_routes = ActionController::Routing::RouteSet.new
  15973. ActionController::Routing.send :const_set, :Routes, temporary_routes
  15974. yield temporary_routes
  15975. ensure
  15976. if ActionController::Routing.const_defined? :Routes
  15977. ActionController::Routing.send(:remove_const, :Routes)
  15978. end
  15979. ActionController::Routing.const_set(:Routes, real_routes) if real_routes
  15980. end
  15981. end
  15982. end
  15983. module Test
  15984. module Unit
  15985. class TestCase #:nodoc:
  15986. include ActionController::TestProcess
  15987. end
  15988. end
  15989. end
  15990. module ActionController
  15991. # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
  15992. class UrlRewriter #:nodoc:
  15993. RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :trailing_slash, :skip_relative_url_root]
  15994. def initialize(request, parameters)
  15995. @request, @parameters = request, parameters
  15996. end
  15997. def rewrite(options = {})
  15998. rewrite_url(rewrite_path(options), options)
  15999. end
  16000. def to_str
  16001. "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
  16002. end
  16003. alias_method :to_s, :to_str
  16004. private
  16005. def rewrite_url(path, options)
  16006. rewritten_url = ""
  16007. unless options[:only_path]
  16008. rewritten_url << (options[:protocol] || @request.protocol)
  16009. rewritten_url << (options[:host] || @request.host_with_port)
  16010. end
  16011. rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
  16012. rewritten_url << path
  16013. rewritten_url << '/' if options[:trailing_slash]
  16014. rewritten_url << "##{options[:anchor]}" if options[:anchor]
  16015. rewritten_url
  16016. end
  16017. def rewrite_path(options)
  16018. options = options.symbolize_keys
  16019. options.update(options[:params].symbolize_keys) if options[:params]
  16020. if (overwrite = options.delete(:overwrite_params))
  16021. options.update(@parameters.symbolize_keys)
  16022. options.update(overwrite)
  16023. end
  16024. RESERVED_OPTIONS.each {|k| options.delete k}
  16025. path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash
  16026. path << build_query_string(options, extra_keys) unless extra_keys.empty?
  16027. path
  16028. end
  16029. # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
  16030. # be added as a path element instead of a regular parameter pair.
  16031. def build_query_string(hash, only_keys = nil)
  16032. elements = []
  16033. query_string = ""
  16034. only_keys ||= hash.keys
  16035. only_keys.each do |key|
  16036. value = hash[key]
  16037. key = CGI.escape key.to_s
  16038. if value.class == Array
  16039. key << '[]'
  16040. else
  16041. value = [ value ]
  16042. end
  16043. value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
  16044. end
  16045. query_string << ("?" + elements.join("&")) unless elements.empty?
  16046. query_string
  16047. end
  16048. end
  16049. end
  16050. require File.dirname(__FILE__) + '/tokenizer'
  16051. require File.dirname(__FILE__) + '/node'
  16052. module HTML #:nodoc:
  16053. # A top-level HTMl document. You give it a body of text, and it will parse that
  16054. # text into a tree of nodes.
  16055. class Document #:nodoc:
  16056. # The root of the parsed document.
  16057. attr_reader :root
  16058. # Create a new Document from the given text.
  16059. def initialize(text, strict=false, xml=false)
  16060. tokenizer = Tokenizer.new(text)
  16061. @root = Node.new(nil)
  16062. node_stack = [ @root ]
  16063. while token = tokenizer.next
  16064. node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)
  16065. node_stack.last.children << node unless node.tag? && node.closing == :close
  16066. if node.tag?
  16067. if node_stack.length > 1 && node.closing == :close
  16068. if node_stack.last.name == node.name
  16069. node_stack.pop
  16070. else
  16071. open_start = node_stack.last.position - 20
  16072. open_start = 0 if open_start < 0
  16073. close_start = node.position - 20
  16074. close_start = 0 if close_start < 0
  16075. msg = <<EOF.strip
  16076. ignoring attempt to close #{node_stack.last.name} with #{node.name}
  16077. opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
  16078. closed at byte #{node.position}, line #{node.line}
  16079. attributes at open: #{node_stack.last.attributes.inspect}
  16080. text around open: #{text[open_start,40].inspect}
  16081. text around close: #{text[close_start,40].inspect}
  16082. EOF
  16083. strict ? raise(msg) : warn(msg)
  16084. end
  16085. elsif !node.childless?(xml) && node.closing != :close
  16086. node_stack.push node
  16087. end
  16088. end
  16089. end
  16090. end
  16091. # Search the tree for (and return) the first node that matches the given
  16092. # conditions. The conditions are interpreted differently for different node
  16093. # types, see HTML::Text#find and HTML::Tag#find.
  16094. def find(conditions)
  16095. @root.find(conditions)
  16096. end
  16097. # Search the tree for (and return) all nodes that match the given
  16098. # conditions. The conditions are interpreted differently for different node
  16099. # types, see HTML::Text#find and HTML::Tag#find.
  16100. def find_all(conditions)
  16101. @root.find_all(conditions)
  16102. end
  16103. end
  16104. end
  16105. require 'strscan'
  16106. module HTML #:nodoc:
  16107. class Conditions < Hash #:nodoc:
  16108. def initialize(hash)
  16109. super()
  16110. hash = { :content => hash } unless Hash === hash
  16111. hash = keys_to_symbols(hash)
  16112. hash.each do |k,v|
  16113. case k
  16114. when :tag, :content then
  16115. # keys are valid, and require no further processing
  16116. when :attributes then
  16117. hash[k] = keys_to_strings(v)
  16118. when :parent, :child, :ancestor, :descendant, :sibling, :before,
  16119. :after
  16120. hash[k] = Conditions.new(v)
  16121. when :children
  16122. hash[k] = v = keys_to_symbols(v)
  16123. v.each do |k,v2|
  16124. case k
  16125. when :count, :greater_than, :less_than
  16126. # keys are valid, and require no further processing
  16127. when :only
  16128. v[k] = Conditions.new(v2)
  16129. else
  16130. raise "illegal key #{k.inspect} => #{v2.inspect}"
  16131. end
  16132. end
  16133. else
  16134. raise "illegal key #{k.inspect} => #{v.inspect}"
  16135. end
  16136. end
  16137. update hash
  16138. end
  16139. private
  16140. def keys_to_strings(hash)
  16141. hash.keys.inject({}) do |h,k|
  16142. h[k.to_s] = hash[k]
  16143. h
  16144. end
  16145. end
  16146. def keys_to_symbols(hash)
  16147. hash.keys.inject({}) do |h,k|
  16148. raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
  16149. h[k.to_sym] = hash[k]
  16150. h
  16151. end
  16152. end
  16153. end
  16154. # The base class of all nodes, textual and otherwise, in an HTML document.
  16155. class Node #:nodoc:
  16156. # The array of children of this node. Not all nodes have children.
  16157. attr_reader :children
  16158. # The parent node of this node. All nodes have a parent, except for the
  16159. # root node.
  16160. attr_reader :parent
  16161. # The line number of the input where this node was begun
  16162. attr_reader :line
  16163. # The byte position in the input where this node was begun
  16164. attr_reader :position
  16165. # Create a new node as a child of the given parent.
  16166. def initialize(parent, line=0, pos=0)
  16167. @parent = parent
  16168. @children = []
  16169. @line, @position = line, pos
  16170. end
  16171. # Return a textual representation of the node.
  16172. def to_s
  16173. s = ""
  16174. @children.each { |child| s << child.to_s }
  16175. s
  16176. end
  16177. # Return false (subclasses must override this to provide specific matching
  16178. # behavior.) +conditions+ may be of any type.
  16179. def match(conditions)
  16180. false
  16181. end
  16182. # Search the children of this node for the first node for which #find
  16183. # returns non +nil+. Returns the result of the #find call that succeeded.
  16184. def find(conditions)
  16185. conditions = validate_conditions(conditions)
  16186. @children.each do |child|
  16187. node = child.find(conditions)
  16188. return node if node
  16189. end
  16190. nil
  16191. end
  16192. # Search for all nodes that match the given conditions, and return them
  16193. # as an array.
  16194. def find_all(conditions)
  16195. conditions = validate_conditions(conditions)
  16196. matches = []
  16197. matches << self if match(conditions)
  16198. @children.each do |child|
  16199. matches.concat child.find_all(conditions)
  16200. end
  16201. matches
  16202. end
  16203. # Returns +false+. Subclasses may override this if they define a kind of
  16204. # tag.
  16205. def tag?
  16206. false
  16207. end
  16208. def validate_conditions(conditions)
  16209. Conditions === conditions ? conditions : Conditions.new(conditions)
  16210. end
  16211. def ==(node)
  16212. return false unless self.class == node.class && children.size == node.children.size
  16213. equivalent = true
  16214. children.size.times do |i|
  16215. equivalent &&= children[i] == node.children[i]
  16216. end
  16217. equivalent
  16218. end
  16219. class <<self
  16220. def parse(parent, line, pos, content, strict=true)
  16221. if content !~ /^<\S/
  16222. Text.new(parent, line, pos, content)
  16223. else
  16224. scanner = StringScanner.new(content)
  16225. unless scanner.skip(/</)
  16226. if strict
  16227. raise "expected <"
  16228. else
  16229. return Text.new(parent, line, pos, content)
  16230. end
  16231. end
  16232. if scanner.skip(/!\[CDATA\[/)
  16233. scanner.scan_until(/\]\]>/)
  16234. return CDATA.new(parent, line, pos, scanner.pre_match)
  16235. end
  16236. closing = ( scanner.scan(/\//) ? :close : nil )
  16237. return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:]+/)
  16238. name.downcase!
  16239. unless closing
  16240. scanner.skip(/\s*/)
  16241. attributes = {}
  16242. while attr = scanner.scan(/[-\w:]+/)
  16243. value = true
  16244. if scanner.scan(/\s*=\s*/)
  16245. if delim = scanner.scan(/['"]/)
  16246. value = ""
  16247. while text = scanner.scan(/[^#{delim}\\]+|./)
  16248. case text
  16249. when "\\" then
  16250. value << text
  16251. value << scanner.getch
  16252. when delim
  16253. break
  16254. else value << text
  16255. end
  16256. end
  16257. else
  16258. value = scanner.scan(/[^\s>\/]+/)
  16259. end
  16260. end
  16261. attributes[attr.downcase] = value
  16262. scanner.skip(/\s*/)
  16263. end
  16264. closing = ( scanner.scan(/\//) ? :self : nil )
  16265. end
  16266. unless scanner.scan(/\s*>/)
  16267. if strict
  16268. raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
  16269. else
  16270. # throw away all text until we find what we're looking for
  16271. scanner.skip_until(/>/) or scanner.terminate
  16272. end
  16273. end
  16274. Tag.new(parent, line, pos, name, attributes, closing)
  16275. end
  16276. end
  16277. end
  16278. end
  16279. # A node that represents text, rather than markup.
  16280. class Text < Node #:nodoc:
  16281. attr_reader :content
  16282. # Creates a new text node as a child of the given parent, with the given
  16283. # content.
  16284. def initialize(parent, line, pos, content)
  16285. super(parent, line, pos)
  16286. @content = content
  16287. end
  16288. # Returns the content of this node.
  16289. def to_s
  16290. @content
  16291. end
  16292. # Returns +self+ if this node meets the given conditions. Text nodes support
  16293. # conditions of the following kinds:
  16294. #
  16295. # * if +conditions+ is a string, it must be a substring of the node's
  16296. # content
  16297. # * if +conditions+ is a regular expression, it must match the node's
  16298. # content
  16299. # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
  16300. # is either a string or a regexp, and which is interpreted as described
  16301. # above.
  16302. def find(conditions)
  16303. match(conditions) && self
  16304. end
  16305. # Returns non-+nil+ if this node meets the given conditions, or +nil+
  16306. # otherwise. See the discussion of #find for the valid conditions.
  16307. def match(conditions)
  16308. case conditions
  16309. when String
  16310. @content.index(conditions)
  16311. when Regexp
  16312. @content =~ conditions
  16313. when Hash
  16314. conditions = validate_conditions(conditions)
  16315. # Text nodes only have :content, :parent, :ancestor
  16316. unless (conditions.keys - [:content, :parent, :ancestor]).empty?
  16317. return false
  16318. end
  16319. match(conditions[:content])
  16320. else
  16321. nil
  16322. end
  16323. end
  16324. def ==(node)
  16325. return false unless super
  16326. content == node.content
  16327. end
  16328. end
  16329. # A CDATA node is simply a text node with a specialized way of displaying
  16330. # itself.
  16331. class CDATA < Text #:nodoc:
  16332. def to_s
  16333. "<![CDATA[#{super}]>"
  16334. end
  16335. end
  16336. # A Tag is any node that represents markup. It may be an opening tag, a
  16337. # closing tag, or a self-closing tag. It has a name, and may have a hash of
  16338. # attributes.
  16339. class Tag < Node #:nodoc:
  16340. # Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
  16341. attr_reader :closing
  16342. # Either +nil+, or a hash of attributes for this node.
  16343. attr_reader :attributes
  16344. # The name of this tag.
  16345. attr_reader :name
  16346. # Create a new node as a child of the given parent, using the given content
  16347. # to describe the node. It will be parsed and the node name, attributes and
  16348. # closing status extracted.
  16349. def initialize(parent, line, pos, name, attributes, closing)
  16350. super(parent, line, pos)
  16351. @name = name
  16352. @attributes = attributes
  16353. @closing = closing
  16354. end
  16355. # A convenience for obtaining an attribute of the node. Returns +nil+ if
  16356. # the node has no attributes.
  16357. def [](attr)
  16358. @attributes ? @attributes[attr] : nil
  16359. end
  16360. # Returns non-+nil+ if this tag can contain child nodes.
  16361. def childless?(xml = false)
  16362. return false if xml && @closing.nil?
  16363. !@closing.nil? ||
  16364. @name =~ /^(img|br|hr|link|meta|area|base|basefont|
  16365. col|frame|input|isindex|param)$/ox
  16366. end
  16367. # Returns a textual representation of the node
  16368. def to_s
  16369. if @closing == :close
  16370. "</#{@name}>"
  16371. else
  16372. s = "<#{@name}"
  16373. @attributes.each do |k,v|
  16374. s << " #{k}"
  16375. s << "='#{v.gsub(/'/,"\\\\'")}'" if String === v
  16376. end
  16377. s << " /" if @closing == :self
  16378. s << ">"
  16379. @children.each { |child| s << child.to_s }
  16380. s << "</#{@name}>" if @closing != :self && !@children.empty?
  16381. s
  16382. end
  16383. end
  16384. # If either the node or any of its children meet the given conditions, the
  16385. # matching node is returned. Otherwise, +nil+ is returned. (See the
  16386. # description of the valid conditions in the +match+ method.)
  16387. def find(conditions)
  16388. match(conditions) && self || super
  16389. end
  16390. # Returns +true+, indicating that this node represents an HTML tag.
  16391. def tag?
  16392. true
  16393. end
  16394. # Returns +true+ if the node meets any of the given conditions. The
  16395. # +conditions+ parameter must be a hash of any of the following keys
  16396. # (all are optional):
  16397. #
  16398. # * <tt>:tag</tt>: the node name must match the corresponding value
  16399. # * <tt>:attributes</tt>: a hash. The node's values must match the
  16400. # corresponding values in the hash.
  16401. # * <tt>:parent</tt>: a hash. The node's parent must match the
  16402. # corresponding hash.
  16403. # * <tt>:child</tt>: a hash. At least one of the node's immediate children
  16404. # must meet the criteria described by the hash.
  16405. # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
  16406. # meet the criteria described by the hash.
  16407. # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
  16408. # must meet the criteria described by the hash.
  16409. # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
  16410. # meet the criteria described by the hash.
  16411. # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
  16412. # the criteria described by the hash, and at least one sibling must match.
  16413. # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
  16414. # the criteria described by the hash, and at least one sibling must match.
  16415. # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
  16416. # keys:
  16417. # ** <tt>:count</tt>: either a number or a range which must equal (or
  16418. # include) the number of children that match.
  16419. # ** <tt>:less_than</tt>: the number of matching children must be less than
  16420. # this number.
  16421. # ** <tt>:greater_than</tt>: the number of matching children must be
  16422. # greater than this number.
  16423. # ** <tt>:only</tt>: another hash consisting of the keys to use
  16424. # to match on the children, and only matching children will be
  16425. # counted.
  16426. #
  16427. # Conditions are matched using the following algorithm:
  16428. #
  16429. # * if the condition is a string, it must be a substring of the value.
  16430. # * if the condition is a regexp, it must match the value.
  16431. # * if the condition is a number, the value must match number.to_s.
  16432. # * if the condition is +true+, the value must not be +nil+.
  16433. # * if the condition is +false+ or +nil+, the value must be +nil+.
  16434. #
  16435. # Usage:
  16436. #
  16437. # # test if the node is a "span" tag
  16438. # node.match :tag => "span"
  16439. #
  16440. # # test if the node's parent is a "div"
  16441. # node.match :parent => { :tag => "div" }
  16442. #
  16443. # # test if any of the node's ancestors are "table" tags
  16444. # node.match :ancestor => { :tag => "table" }
  16445. #
  16446. # # test if any of the node's immediate children are "em" tags
  16447. # node.match :child => { :tag => "em" }
  16448. #
  16449. # # test if any of the node's descendants are "strong" tags
  16450. # node.match :descendant => { :tag => "strong" }
  16451. #
  16452. # # test if the node has between 2 and 4 span tags as immediate children
  16453. # node.match :children => { :count => 2..4, :only => { :tag => "span" } }
  16454. #
  16455. # # get funky: test to see if the node is a "div", has a "ul" ancestor
  16456. # # and an "li" parent (with "class" = "enum"), and whether or not it has
  16457. # # a "span" descendant that contains # text matching /hello world/:
  16458. # node.match :tag => "div",
  16459. # :ancestor => { :tag => "ul" },
  16460. # :parent => { :tag => "li",
  16461. # :attributes => { :class => "enum" } },
  16462. # :descendant => { :tag => "span",
  16463. # :child => /hello world/ }
  16464. def match(conditions)
  16465. conditions = validate_conditions(conditions)
  16466. # check content of child nodes
  16467. if conditions[:content]
  16468. if children.empty?
  16469. return false unless match_condition("", conditions[:content])
  16470. else
  16471. return false unless children.find { |child| child.match(conditions[:content]) }
  16472. end
  16473. end
  16474. # test the name
  16475. return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
  16476. # test attributes
  16477. (conditions[:attributes] || {}).each do |key, value|
  16478. return false unless match_condition(self[key], value)
  16479. end
  16480. # test parent
  16481. return false unless parent.match(conditions[:parent]) if conditions[:parent]
  16482. # test children
  16483. return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
  16484. # test ancestors
  16485. if conditions[:ancestor]
  16486. return false unless catch :found do
  16487. p = self
  16488. throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
  16489. end
  16490. end
  16491. # test descendants
  16492. if conditions[:descendant]
  16493. return false unless children.find do |child|
  16494. # test the child
  16495. child.match(conditions[:descendant]) ||
  16496. # test the child's descendants
  16497. child.match(:descendant => conditions[:descendant])
  16498. end
  16499. end
  16500. # count children
  16501. if opts = conditions[:children]
  16502. matches = children.select do |c|
  16503. c.match(/./) or
  16504. (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
  16505. end
  16506. matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
  16507. opts.each do |key, value|
  16508. next if key == :only
  16509. case key
  16510. when :count
  16511. if Integer === value
  16512. return false if matches.length != value
  16513. else
  16514. return false unless value.include?(matches.length)
  16515. end
  16516. when :less_than
  16517. return false unless matches.length < value
  16518. when :greater_than
  16519. return false unless matches.length > value
  16520. else raise "unknown count condition #{key}"
  16521. end
  16522. end
  16523. end
  16524. # test siblings
  16525. if conditions[:sibling] || conditions[:before] || conditions[:after]
  16526. siblings = parent ? parent.children : []
  16527. self_index = siblings.index(self)
  16528. if conditions[:sibling]
  16529. return false unless siblings.detect do |s|
  16530. s != self && s.match(conditions[:sibling])
  16531. end
  16532. end
  16533. if conditions[:before]
  16534. return false unless siblings[self_index+1..-1].detect do |s|
  16535. s != self && s.match(conditions[:before])
  16536. end
  16537. end
  16538. if conditions[:after]
  16539. return false unless siblings[0,self_index].detect do |s|
  16540. s != self && s.match(conditions[:after])
  16541. end
  16542. end
  16543. end
  16544. true
  16545. end
  16546. def ==(node)
  16547. return false unless super
  16548. return false unless closing == node.closing && self.name == node.name
  16549. attributes == node.attributes
  16550. end
  16551. private
  16552. # Match the given value to the given condition.
  16553. def match_condition(value, condition)
  16554. case condition
  16555. when String
  16556. value && value == condition
  16557. when Regexp
  16558. value && value.match(condition)
  16559. when Numeric
  16560. value == condition.to_s
  16561. when true
  16562. !value.nil?
  16563. when false, nil
  16564. value.nil?
  16565. else
  16566. false
  16567. end
  16568. end
  16569. end
  16570. end
  16571. require 'strscan'
  16572. module HTML #:nodoc:
  16573. # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
  16574. # token is a string. Each string represents either "text", or an HTML element.
  16575. #
  16576. # This currently assumes valid XHTML, which means no free < or > characters.
  16577. #
  16578. # Usage:
  16579. #
  16580. # tokenizer = HTML::Tokenizer.new(text)
  16581. # while token = tokenizer.next
  16582. # p token
  16583. # end
  16584. class Tokenizer #:nodoc:
  16585. # The current (byte) position in the text
  16586. attr_reader :position
  16587. # The current line number
  16588. attr_reader :line
  16589. # Create a new Tokenizer for the given text.
  16590. def initialize(text)
  16591. @scanner = StringScanner.new(text)
  16592. @position = 0
  16593. @line = 0
  16594. @current_line = 1
  16595. end
  16596. # Return the next token in the sequence, or +nil+ if there are no more tokens in
  16597. # the stream.
  16598. def next
  16599. return nil if @scanner.eos?
  16600. @position = @scanner.pos
  16601. @line = @current_line
  16602. if @scanner.check(/<\S/)
  16603. update_current_line(scan_tag)
  16604. else
  16605. update_current_line(scan_text)
  16606. end
  16607. end
  16608. private
  16609. # Treat the text at the current position as a tag, and scan it. Supports
  16610. # comments, doctype tags, and regular tags, and ignores less-than and
  16611. # greater-than characters within quoted strings.
  16612. def scan_tag
  16613. tag = @scanner.getch
  16614. if @scanner.scan(/!--/) # comment
  16615. tag << @scanner.matched
  16616. tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
  16617. elsif @scanner.scan(/!\[CDATA\[/)
  16618. tag << @scanner.matched
  16619. tag << @scanner.scan_until(/\]\]>/)
  16620. elsif @scanner.scan(/!/) # doctype
  16621. tag << @scanner.matched
  16622. tag << consume_quoted_regions
  16623. else
  16624. tag << consume_quoted_regions
  16625. end
  16626. tag
  16627. end
  16628. # Scan all text up to the next < character and return it.
  16629. def scan_text
  16630. "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
  16631. end
  16632. # Counts the number of newlines in the text and updates the current line
  16633. # accordingly.
  16634. def update_current_line(text)
  16635. text.scan(/\r?\n/) { @current_line += 1 }
  16636. end
  16637. # Skips over quoted strings, so that less-than and greater-than characters
  16638. # within the strings are ignored.
  16639. def consume_quoted_regions
  16640. text = ""
  16641. loop do
  16642. match = @scanner.scan_until(/['"<>]/) or break
  16643. delim = @scanner.matched
  16644. if delim == "<"
  16645. match = match.chop
  16646. @scanner.pos -= 1
  16647. end
  16648. text << match
  16649. break if delim == "<" || delim == ">"
  16650. # consume the quoted region
  16651. while match = @scanner.scan_until(/[\\#{delim}]/)
  16652. text << match
  16653. break if @scanner.matched == delim
  16654. text << @scanner.getch # skip the escaped character
  16655. end
  16656. end
  16657. text
  16658. end
  16659. end
  16660. end
  16661. module HTML #:nodoc:
  16662. module Version #:nodoc:
  16663. MAJOR = 0
  16664. MINOR = 5
  16665. TINY = 3
  16666. STRING = [ MAJOR, MINOR, TINY ].join(".")
  16667. end
  16668. end
  16669. require 'rexml/document'
  16670. # SimpleXML like xml parser. Written by leon breet from the ruby on rails Mailing list
  16671. class XmlNode #:nodoc:
  16672. attr :node
  16673. def initialize(node, options = {})
  16674. @node = node
  16675. @children = {}
  16676. @raise_errors = options[:raise_errors]
  16677. end
  16678. def self.from_xml(xml_or_io)
  16679. document = REXML::Document.new(xml_or_io)
  16680. if document.root
  16681. XmlNode.new(document.root)
  16682. else
  16683. XmlNode.new(document)
  16684. end
  16685. end
  16686. def node_encoding
  16687. @node.encoding
  16688. end
  16689. def node_name
  16690. @node.name
  16691. end
  16692. def node_value
  16693. @node.text
  16694. end
  16695. def node_value=(value)
  16696. @node.text = value
  16697. end
  16698. def xpath(expr)
  16699. matches = nil
  16700. REXML::XPath.each(@node, expr) do |element|
  16701. matches ||= XmlNodeList.new
  16702. matches << (@children[element] ||= XmlNode.new(element))
  16703. end
  16704. matches
  16705. end
  16706. def method_missing(name, *args)
  16707. name = name.to_s
  16708. nodes = nil
  16709. @node.each_element(name) do |element|
  16710. nodes ||= XmlNodeList.new
  16711. nodes << (@children[element] ||= XmlNode.new(element))
  16712. end
  16713. nodes
  16714. end
  16715. def <<(node)
  16716. if node.is_a? REXML::Node
  16717. child = node
  16718. elsif node.respond_to? :node
  16719. child = node.node
  16720. end
  16721. @node.add_element child
  16722. @children[child] ||= XmlNode.new(child)
  16723. end
  16724. def [](name)
  16725. @node.attributes[name.to_s]
  16726. end
  16727. def []=(name, value)
  16728. @node.attributes[name.to_s] = value
  16729. end
  16730. def to_s
  16731. @node.to_s
  16732. end
  16733. def to_i
  16734. to_s.to_i
  16735. end
  16736. end
  16737. class XmlNodeList < Array #:nodoc:
  16738. def [](i)
  16739. i.is_a?(String) ? super(0)[i] : super(i)
  16740. end
  16741. def []=(i, value)
  16742. i.is_a?(String) ? self[0][i] = value : super(i, value)
  16743. end
  16744. def method_missing(name, *args)
  16745. name = name.to_s
  16746. self[0].__send__(name, *args)
  16747. end
  16748. end# = XmlSimple
  16749. #
  16750. # Author:: Maik Schmidt <contact@maik-schmidt.de>
  16751. # Copyright:: Copyright (c) 2003 Maik Schmidt
  16752. # License:: Distributes under the same terms as Ruby.
  16753. #
  16754. require 'rexml/document'
  16755. # Easy API to maintain XML (especially configuration files).
  16756. class XmlSimple #:nodoc:
  16757. include REXML
  16758. @@VERSION = '1.0.2'
  16759. # A simple cache for XML documents that were already transformed
  16760. # by xml_in.
  16761. class Cache #:nodoc:
  16762. # Creates and initializes a new Cache object.
  16763. def initialize
  16764. @mem_share_cache = {}
  16765. @mem_copy_cache = {}
  16766. end
  16767. # Saves a data structure into a file.
  16768. #
  16769. # data::
  16770. # Data structure to be saved.
  16771. # filename::
  16772. # Name of the file belonging to the data structure.
  16773. def save_storable(data, filename)
  16774. cache_file = get_cache_filename(filename)
  16775. File.open(cache_file, "w+") { |f| Marshal.dump(data, f) }
  16776. end
  16777. # Restores a data structure from a file. If restoring the data
  16778. # structure failed for any reason, nil will be returned.
  16779. #
  16780. # filename::
  16781. # Name of the file belonging to the data structure.
  16782. def restore_storable(filename)
  16783. cache_file = get_cache_filename(filename)
  16784. return nil unless File::exist?(cache_file)
  16785. return nil unless File::mtime(cache_file).to_i > File::mtime(filename).to_i
  16786. data = nil
  16787. File.open(cache_file) { |f| data = Marshal.load(f) }
  16788. data
  16789. end
  16790. # Saves a data structure in a shared memory cache.
  16791. #
  16792. # data::
  16793. # Data structure to be saved.
  16794. # filename::
  16795. # Name of the file belonging to the data structure.
  16796. def save_mem_share(data, filename)
  16797. @mem_share_cache[filename] = [Time::now.to_i, data]
  16798. end
  16799. # Restores a data structure from a shared memory cache. You
  16800. # should consider these elements as "read only". If restoring
  16801. # the data structure failed for any reason, nil will be
  16802. # returned.
  16803. #
  16804. # filename::
  16805. # Name of the file belonging to the data structure.
  16806. def restore_mem_share(filename)
  16807. get_from_memory_cache(filename, @mem_share_cache)
  16808. end
  16809. # Copies a data structure to a memory cache.
  16810. #
  16811. # data::
  16812. # Data structure to be copied.
  16813. # filename::
  16814. # Name of the file belonging to the data structure.
  16815. def save_mem_copy(data, filename)
  16816. @mem_share_cache[filename] = [Time::now.to_i, Marshal.dump(data)]
  16817. end
  16818. # Restores a data structure from a memory cache. If restoring
  16819. # the data structure failed for any reason, nil will be
  16820. # returned.
  16821. #
  16822. # filename::
  16823. # Name of the file belonging to the data structure.
  16824. def restore_mem_copy(filename)
  16825. data = get_from_memory_cache(filename, @mem_share_cache)
  16826. data = Marshal.load(data) unless data.nil?
  16827. data
  16828. end
  16829. private
  16830. # Returns the "cache filename" belonging to a filename, i.e.
  16831. # the extension '.xml' in the original filename will be replaced
  16832. # by '.stor'. If filename does not have this extension, '.stor'
  16833. # will be appended.
  16834. #
  16835. # filename::
  16836. # Filename to get "cache filename" for.
  16837. def get_cache_filename(filename)
  16838. filename.sub(/(\.xml)?$/, '.stor')
  16839. end
  16840. # Returns a cache entry from a memory cache belonging to a
  16841. # certain filename. If no entry could be found for any reason,
  16842. # nil will be returned.
  16843. #
  16844. # filename::
  16845. # Name of the file the cache entry belongs to.
  16846. # cache::
  16847. # Memory cache to get entry from.
  16848. def get_from_memory_cache(filename, cache)
  16849. return nil unless cache[filename]
  16850. return nil unless cache[filename][0] > File::mtime(filename).to_i
  16851. return cache[filename][1]
  16852. end
  16853. end
  16854. # Create a "global" cache.
  16855. @@cache = Cache.new
  16856. # Creates and intializes a new XmlSimple object.
  16857. #
  16858. # defaults::
  16859. # Default values for options.
  16860. def initialize(defaults = nil)
  16861. unless defaults.nil? || defaults.instance_of?(Hash)
  16862. raise ArgumentError, "Options have to be a Hash."
  16863. end
  16864. @default_options = normalize_option_names(defaults, KNOWN_OPTIONS['in'] & KNOWN_OPTIONS['out'])
  16865. @options = Hash.new
  16866. @_var_values = nil
  16867. end
  16868. # Converts an XML document in the same way as the Perl module XML::Simple.
  16869. #
  16870. # string::
  16871. # XML source. Could be one of the following:
  16872. #
  16873. # - nil: Tries to load and parse '<scriptname>.xml'.
  16874. # - filename: Tries to load and parse filename.
  16875. # - IO object: Reads from object until EOF is detected and parses result.
  16876. # - XML string: Parses string.
  16877. #
  16878. # options::
  16879. # Options to be used.
  16880. def xml_in(string = nil, options = nil)
  16881. handle_options('in', options)
  16882. # If no XML string or filename was supplied look for scriptname.xml.
  16883. if string.nil?
  16884. string = File::basename($0)
  16885. string.sub!(/\.[^.]+$/, '')
  16886. string += '.xml'
  16887. directory = File::dirname($0)
  16888. @options['searchpath'].unshift(directory) unless directory.nil?
  16889. end
  16890. if string.instance_of?(String)
  16891. if string =~ /<.*?>/m
  16892. @doc = parse(string)
  16893. elsif string == '-'
  16894. @doc = parse($stdin.readlines.to_s)
  16895. else
  16896. filename = find_xml_file(string, @options['searchpath'])
  16897. if @options.has_key?('cache')
  16898. @options['cache'].each { |scheme|
  16899. case(scheme)
  16900. when 'storable'
  16901. content = @@cache.restore_storable(filename)
  16902. when 'mem_share'
  16903. content = @@cache.restore_mem_share(filename)
  16904. when 'mem_copy'
  16905. content = @@cache.restore_mem_copy(filename)
  16906. else
  16907. raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
  16908. end
  16909. return content if content
  16910. }
  16911. end
  16912. @doc = load_xml_file(filename)
  16913. end
  16914. elsif string.kind_of?(IO)
  16915. @doc = parse(string.readlines.to_s)
  16916. else
  16917. raise ArgumentError, "Could not parse object of type: <#{string.type}>."
  16918. end
  16919. result = collapse(@doc.root)
  16920. result = @options['keeproot'] ? merge({}, @doc.root.name, result) : result
  16921. put_into_cache(result, filename)
  16922. result
  16923. end
  16924. # This is the functional version of the instance method xml_in.
  16925. def XmlSimple.xml_in(string = nil, options = nil)
  16926. xml_simple = XmlSimple.new
  16927. xml_simple.xml_in(string, options)
  16928. end
  16929. # Converts a data structure into an XML document.
  16930. #
  16931. # ref::
  16932. # Reference to data structure to be converted into XML.
  16933. # options::
  16934. # Options to be used.
  16935. def xml_out(ref, options = nil)
  16936. handle_options('out', options)
  16937. if ref.instance_of?(Array)
  16938. ref = { @options['anonymoustag'] => ref }
  16939. end
  16940. if @options['keeproot']
  16941. keys = ref.keys
  16942. if keys.size == 1
  16943. ref = ref[keys[0]]
  16944. @options['rootname'] = keys[0]
  16945. end
  16946. elsif @options['rootname'] == ''
  16947. if ref.instance_of?(Hash)
  16948. refsave = ref
  16949. ref = {}
  16950. refsave.each { |key, value|
  16951. if !scalar(value)
  16952. ref[key] = value
  16953. else
  16954. ref[key] = [ value.to_s ]
  16955. end
  16956. }
  16957. end
  16958. end
  16959. @ancestors = []
  16960. xml = value_to_xml(ref, @options['rootname'], '')
  16961. @ancestors = nil
  16962. if @options['xmldeclaration']
  16963. xml = @options['xmldeclaration'] + "\n" + xml
  16964. end
  16965. if @options.has_key?('outputfile')
  16966. if @options['outputfile'].kind_of?(IO)
  16967. return @options['outputfile'].write(xml)
  16968. else
  16969. File.open(@options['outputfile'], "w") { |file| file.write(xml) }
  16970. end
  16971. end
  16972. xml
  16973. end
  16974. # This is the functional version of the instance method xml_out.
  16975. def XmlSimple.xml_out(hash, options = nil)
  16976. xml_simple = XmlSimple.new
  16977. xml_simple.xml_out(hash, options)
  16978. end
  16979. private
  16980. # Declare options that are valid for xml_in and xml_out.
  16981. KNOWN_OPTIONS = {
  16982. 'in' => %w(
  16983. keyattr keeproot forcecontent contentkey noattr
  16984. searchpath forcearray suppressempty anonymoustag
  16985. cache grouptags normalisespace normalizespace
  16986. variables varattr
  16987. ),
  16988. 'out' => %w(
  16989. keyattr keeproot contentkey noattr rootname
  16990. xmldeclaration outputfile noescape suppressempty
  16991. anonymoustag indent grouptags noindent
  16992. )
  16993. }
  16994. # Define some reasonable defaults.
  16995. DEF_KEY_ATTRIBUTES = []
  16996. DEF_ROOT_NAME = 'opt'
  16997. DEF_CONTENT_KEY = 'content'
  16998. DEF_XML_DECLARATION = "<?xml version='1.0' standalone='yes'?>"
  16999. DEF_ANONYMOUS_TAG = 'anon'
  17000. DEF_FORCE_ARRAY = true
  17001. DEF_INDENTATION = ' '
  17002. # Normalizes option names in a hash, i.e., turns all
  17003. # characters to lower case and removes all underscores.
  17004. # Additionally, this method checks, if an unknown option
  17005. # was used and raises an according exception.
  17006. #
  17007. # options::
  17008. # Hash to be normalized.
  17009. # known_options::
  17010. # List of known options.
  17011. def normalize_option_names(options, known_options)
  17012. return nil if options.nil?
  17013. result = Hash.new
  17014. options.each { |key, value|
  17015. lkey = key.downcase
  17016. lkey.gsub!(/_/, '')
  17017. if !known_options.member?(lkey)
  17018. raise ArgumentError, "Unrecognised option: #{lkey}."
  17019. end
  17020. result[lkey] = value
  17021. }
  17022. result
  17023. end
  17024. # Merges a set of options with the default options.
  17025. #
  17026. # direction::
  17027. # 'in': If options should be handled for xml_in.
  17028. # 'out': If options should be handled for xml_out.
  17029. # options::
  17030. # Options to be merged with the default options.
  17031. def handle_options(direction, options)
  17032. @options = options || Hash.new
  17033. raise ArgumentError, "Options must be a Hash!" unless @options.instance_of?(Hash)
  17034. unless KNOWN_OPTIONS.has_key?(direction)
  17035. raise ArgumentError, "Unknown direction: <#{direction}>."
  17036. end
  17037. known_options = KNOWN_OPTIONS[direction]
  17038. @options = normalize_option_names(@options, known_options)
  17039. unless @default_options.nil?
  17040. known_options.each { |option|
  17041. unless @options.has_key?(option)
  17042. if @default_options.has_key?(option)
  17043. @options[option] = @default_options[option]
  17044. end
  17045. end
  17046. }
  17047. end
  17048. unless @options.has_key?('noattr')
  17049. @options['noattr'] = false
  17050. end
  17051. if @options.has_key?('rootname')
  17052. @options['rootname'] = '' if @options['rootname'].nil?
  17053. else
  17054. @options['rootname'] = DEF_ROOT_NAME
  17055. end
  17056. if @options.has_key?('xmldeclaration') && @options['xmldeclaration'] == true
  17057. @options['xmldeclaration'] = DEF_XML_DECLARATION
  17058. end
  17059. if @options.has_key?('contentkey')
  17060. if @options['contentkey'] =~ /^-(.*)$/
  17061. @options['contentkey'] = $1
  17062. @options['collapseagain'] = true
  17063. end
  17064. else
  17065. @options['contentkey'] = DEF_CONTENT_KEY
  17066. end
  17067. unless @options.has_key?('normalisespace')
  17068. @options['normalisespace'] = @options['normalizespace']
  17069. end
  17070. @options['normalisespace'] = 0 if @options['normalisespace'].nil?
  17071. if @options.has_key?('searchpath')
  17072. unless @options['searchpath'].instance_of?(Array)
  17073. @options['searchpath'] = [ @options['searchpath'] ]
  17074. end
  17075. else
  17076. @options['searchpath'] = []
  17077. end
  17078. if @options.has_key?('cache') && scalar(@options['cache'])
  17079. @options['cache'] = [ @options['cache'] ]
  17080. end
  17081. @options['anonymoustag'] = DEF_ANONYMOUS_TAG unless @options.has_key?('anonymoustag')
  17082. if !@options.has_key?('indent') || @options['indent'].nil?
  17083. @options['indent'] = DEF_INDENTATION
  17084. end
  17085. @options['indent'] = '' if @options.has_key?('noindent')
  17086. # Special cleanup for 'keyattr' which could be an array or
  17087. # a hash or left to default to array.
  17088. if @options.has_key?('keyattr')
  17089. if !scalar(@options['keyattr'])
  17090. # Convert keyattr => { elem => '+attr' }
  17091. # to keyattr => { elem => ['attr', '+'] }
  17092. if @options['keyattr'].instance_of?(Hash)
  17093. @options['keyattr'].each { |key, value|
  17094. if value =~ /^([-+])?(.*)$/
  17095. @options['keyattr'][key] = [$2, $1 ? $1 : '']
  17096. end
  17097. }
  17098. elsif !@options['keyattr'].instance_of?(Array)
  17099. raise ArgumentError, "'keyattr' must be String, Hash, or Array!"
  17100. end
  17101. else
  17102. @options['keyattr'] = [ @options['keyattr'] ]
  17103. end
  17104. else
  17105. @options['keyattr'] = DEF_KEY_ATTRIBUTES
  17106. end
  17107. if @options.has_key?('forcearray')
  17108. if @options['forcearray'].instance_of?(Regexp)
  17109. @options['forcearray'] = [ @options['forcearray'] ]
  17110. end
  17111. if @options['forcearray'].instance_of?(Array)
  17112. force_list = @options['forcearray']
  17113. unless force_list.empty?
  17114. @options['forcearray'] = {}
  17115. force_list.each { |tag|
  17116. if tag.instance_of?(Regexp)
  17117. unless @options['forcearray']['_regex'].instance_of?(Array)
  17118. @options['forcearray']['_regex'] = []
  17119. end
  17120. @options['forcearray']['_regex'] << tag
  17121. else
  17122. @options['forcearray'][tag] = true
  17123. end
  17124. }
  17125. else
  17126. @options['forcearray'] = false
  17127. end
  17128. else
  17129. @options['forcearray'] = @options['forcearray'] ? true : false
  17130. end
  17131. else
  17132. @options['forcearray'] = DEF_FORCE_ARRAY
  17133. end
  17134. if @options.has_key?('grouptags') && !@options['grouptags'].instance_of?(Hash)
  17135. raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash."
  17136. end
  17137. if @options.has_key?('variables') && !@options['variables'].instance_of?(Hash)
  17138. raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash."
  17139. end
  17140. if @options.has_key?('variables')
  17141. @_var_values = @options['variables']
  17142. elsif @options.has_key?('varattr')
  17143. @_var_values = {}
  17144. end
  17145. end
  17146. # Actually converts an XML document element into a data structure.
  17147. #
  17148. # element::
  17149. # The document element to be collapsed.
  17150. def collapse(element)
  17151. result = @options['noattr'] ? {} : get_attributes(element)
  17152. if @options['normalisespace'] == 2
  17153. result.each { |k, v| result[k] = normalise_space(v) }
  17154. end
  17155. if element.has_elements?
  17156. element.each_element { |child|
  17157. value = collapse(child)
  17158. if empty(value) && (element.attributes.empty? || @options['noattr'])
  17159. next if @options.has_key?('suppressempty') && @options['suppressempty'] == true
  17160. end
  17161. result = merge(result, child.name, value)
  17162. }
  17163. if has_mixed_content?(element)
  17164. # normalisespace?
  17165. content = element.texts.map { |x| x.to_s }
  17166. content = content[0] if content.size == 1
  17167. result[@options['contentkey']] = content
  17168. end
  17169. elsif element.has_text? # i.e. it has only text.
  17170. return collapse_text_node(result, element)
  17171. end
  17172. # Turn Arrays into Hashes if key fields present.
  17173. count = fold_arrays(result)
  17174. # Disintermediate grouped tags.
  17175. if @options.has_key?('grouptags')
  17176. result.each { |key, value|
  17177. next unless (value.instance_of?(Hash) && (value.size == 1))
  17178. child_key, child_value = value.to_a[0]
  17179. if @options['grouptags'][key] == child_key
  17180. result[key] = child_value
  17181. end
  17182. }
  17183. end
  17184. # Fold Hases containing a single anonymous Array up into just the Array.
  17185. if count == 1
  17186. anonymoustag = @options['anonymoustag']
  17187. if result.has_key?(anonymoustag) && result[anonymoustag].instance_of?(Array)
  17188. return result[anonymoustag]
  17189. end
  17190. end
  17191. if result.empty? && @options.has_key?('suppressempty')
  17192. return @options['suppressempty'] == '' ? '' : nil
  17193. end
  17194. result
  17195. end
  17196. # Collapses a text node and merges it with an existing Hash, if
  17197. # possible.
  17198. # Thanks to Curtis Schofield for reporting a subtle bug.
  17199. #
  17200. # hash::
  17201. # Hash to merge text node value with, if possible.
  17202. # element::
  17203. # Text node to be collapsed.
  17204. def collapse_text_node(hash, element)
  17205. value = node_to_text(element)
  17206. if empty(value) && !element.has_attributes?
  17207. return {}
  17208. end
  17209. if element.has_attributes? && !@options['noattr']
  17210. return merge(hash, @options['contentkey'], value)
  17211. else
  17212. if @options['forcecontent']
  17213. return merge(hash, @options['contentkey'], value)
  17214. else
  17215. return value
  17216. end
  17217. end
  17218. end
  17219. # Folds all arrays in a Hash.
  17220. #
  17221. # hash::
  17222. # Hash to be folded.
  17223. def fold_arrays(hash)
  17224. fold_amount = 0
  17225. keyattr = @options['keyattr']
  17226. if (keyattr.instance_of?(Array) || keyattr.instance_of?(Hash))
  17227. hash.each { |key, value|
  17228. if value.instance_of?(Array)
  17229. if keyattr.instance_of?(Array)
  17230. hash[key] = fold_array(value)
  17231. else
  17232. hash[key] = fold_array_by_name(key, value)
  17233. end
  17234. fold_amount += 1
  17235. end
  17236. }
  17237. end
  17238. fold_amount
  17239. end
  17240. # Folds an Array to a Hash, if possible. Folding happens
  17241. # according to the content of keyattr, which has to be
  17242. # an array.
  17243. #
  17244. # array::
  17245. # Array to be folded.
  17246. def fold_array(array)
  17247. hash = Hash.new
  17248. array.each { |x|
  17249. return array unless x.instance_of?(Hash)
  17250. key_matched = false
  17251. @options['keyattr'].each { |key|
  17252. if x.has_key?(key)
  17253. key_matched = true
  17254. value = x[key]
  17255. return array if value.instance_of?(Hash) || value.instance_of?(Array)
  17256. value = normalise_space(value) if @options['normalisespace'] == 1
  17257. x.delete(key)
  17258. hash[value] = x
  17259. break
  17260. end
  17261. }
  17262. return array unless key_matched
  17263. }
  17264. hash = collapse_content(hash) if @options['collapseagain']
  17265. hash
  17266. end
  17267. # Folds an Array to a Hash, if possible. Folding happens
  17268. # according to the content of keyattr, which has to be
  17269. # a Hash.
  17270. #
  17271. # name::
  17272. # Name of the attribute to be folded upon.
  17273. # array::
  17274. # Array to be folded.
  17275. def fold_array_by_name(name, array)
  17276. return array unless @options['keyattr'].has_key?(name)
  17277. key, flag = @options['keyattr'][name]
  17278. hash = Hash.new
  17279. array.each { |x|
  17280. if x.instance_of?(Hash) && x.has_key?(key)
  17281. value = x[key]
  17282. return array if value.instance_of?(Hash) || value.instance_of?(Array)
  17283. value = normalise_space(value) if @options['normalisespace'] == 1
  17284. hash[value] = x
  17285. hash[value]["-#{key}"] = hash[value][key] if flag == '-'
  17286. hash[value].delete(key) unless flag == '+'
  17287. else
  17288. $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.")
  17289. return array
  17290. end
  17291. }
  17292. hash = collapse_content(hash) if @options['collapseagain']
  17293. hash
  17294. end
  17295. # Tries to collapse a Hash even more ;-)
  17296. #
  17297. # hash::
  17298. # Hash to be collapsed again.
  17299. def collapse_content(hash)
  17300. content_key = @options['contentkey']
  17301. hash.each_value { |value|
  17302. return hash unless value.instance_of?(Hash) && value.size == 1 && value.has_key?(content_key)
  17303. hash.each_key { |key| hash[key] = hash[key][content_key] }
  17304. }
  17305. hash
  17306. end
  17307. # Adds a new key/value pair to an existing Hash. If the key to be added
  17308. # does already exist and the existing value associated with key is not
  17309. # an Array, it will be converted into an Array. Then the new value is
  17310. # appended to that Array.
  17311. #
  17312. # hash::
  17313. # Hash to add key/value pair to.
  17314. # key::
  17315. # Key to be added.
  17316. # value::
  17317. # Value to be associated with key.
  17318. def merge(hash, key, value)
  17319. if value.instance_of?(String)
  17320. value = normalise_space(value) if @options['normalisespace'] == 2
  17321. # do variable substitutions
  17322. unless @_var_values.nil? || @_var_values.empty?
  17323. value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) }
  17324. end
  17325. # look for variable definitions
  17326. if @options.has_key?('varattr')
  17327. varattr = @options['varattr']
  17328. if hash.has_key?(varattr)
  17329. set_var(hash[varattr], value)
  17330. end
  17331. end
  17332. end
  17333. if hash.has_key?(key)
  17334. if hash[key].instance_of?(Array)
  17335. hash[key] << value
  17336. else
  17337. hash[key] = [ hash[key], value ]
  17338. end
  17339. elsif value.instance_of?(Array) # Handle anonymous arrays.
  17340. hash[key] = [ value ]
  17341. else
  17342. if force_array?(key)
  17343. hash[key] = [ value ]
  17344. else
  17345. hash[key] = value
  17346. end
  17347. end
  17348. hash
  17349. end
  17350. # Checks, if the 'forcearray' option has to be used for
  17351. # a certain key.
  17352. def force_array?(key)
  17353. return false if key == @options['contentkey']
  17354. return true if @options['forcearray'] == true
  17355. forcearray = @options['forcearray']
  17356. if forcearray.instance_of?(Hash)
  17357. return true if forcearray.has_key?(key)
  17358. return false unless forcearray.has_key?('_regex')
  17359. forcearray['_regex'].each { |x| return true if key =~ x }
  17360. end
  17361. return false
  17362. end
  17363. # Converts the attributes array of a document node into a Hash.
  17364. # Returns an empty Hash, if node has no attributes.
  17365. #
  17366. # node::
  17367. # Document node to extract attributes from.
  17368. def get_attributes(node)
  17369. attributes = {}
  17370. node.attributes.each { |n,v| attributes[n] = v }
  17371. attributes
  17372. end
  17373. # Determines, if a document element has mixed content.
  17374. #
  17375. # element::
  17376. # Document element to be checked.
  17377. def has_mixed_content?(element)
  17378. if element.has_text? && element.has_elements?
  17379. return true if element.texts.join('') !~ /^\s*$/s
  17380. end
  17381. false
  17382. end
  17383. # Called when a variable definition is encountered in the XML.
  17384. # A variable definition looks like
  17385. # <element attrname="name">value</element>
  17386. # where attrname matches the varattr setting.
  17387. def set_var(name, value)
  17388. @_var_values[name] = value
  17389. end
  17390. # Called during variable substitution to get the value for the
  17391. # named variable.
  17392. def get_var(name)
  17393. if @_var_values.has_key?(name)
  17394. return @_var_values[name]
  17395. else
  17396. return "${#{name}}"
  17397. end
  17398. end
  17399. # Recurses through a data structure building up and returning an
  17400. # XML representation of that structure as a string.
  17401. #
  17402. # ref::
  17403. # Reference to the data structure to be encoded.
  17404. # name::
  17405. # The XML tag name to be used for this item.
  17406. # indent::
  17407. # A string of spaces for use as the current indent level.
  17408. def value_to_xml(ref, name, indent)
  17409. named = !name.nil? && name != ''
  17410. nl = @options.has_key?('noindent') ? '' : "\n"
  17411. if !scalar(ref)
  17412. if @ancestors.member?(ref)
  17413. raise ArgumentError, "Circular data structures not supported!"
  17414. end
  17415. @ancestors << ref
  17416. else
  17417. if named
  17418. return [indent, '<', name, '>', @options['noescape'] ? ref.to_s : escape_value(ref.to_s), '</', name, '>', nl].join('')
  17419. else
  17420. return ref.to_s + nl
  17421. end
  17422. end
  17423. # Unfold hash to array if possible.
  17424. if ref.instance_of?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != ''
  17425. ref = hash_to_array(name, ref)
  17426. end
  17427. result = []
  17428. if ref.instance_of?(Hash)
  17429. # Reintermediate grouped values if applicable.
  17430. if @options.has_key?('grouptags')
  17431. ref.each { |key, value|
  17432. if @options['grouptags'].has_key?(key)
  17433. ref[key] = { @options['grouptags'][key] => value }
  17434. end
  17435. }
  17436. end
  17437. nested = []
  17438. text_content = nil
  17439. if named
  17440. result << indent << '<' << name
  17441. end
  17442. if !ref.empty?
  17443. ref.each { |key, value|
  17444. next if !key.nil? && key[0, 1] == '-'
  17445. if value.nil?
  17446. unless @options.has_key?('suppressempty') && @options['suppressempty'].nil?
  17447. raise ArgumentError, "Use of uninitialized value!"
  17448. end
  17449. value = {}
  17450. end
  17451. if !scalar(value) || @options['noattr']
  17452. nested << value_to_xml(value, key, indent + @options['indent'])
  17453. else
  17454. value = value.to_s
  17455. value = escape_value(value) unless @options['noescape']
  17456. if key == @options['contentkey']
  17457. text_content = value
  17458. else
  17459. result << ' ' << key << '="' << value << '"'
  17460. end
  17461. end
  17462. }
  17463. else
  17464. text_content = ''
  17465. end
  17466. if !nested.empty? || !text_content.nil?
  17467. if named
  17468. result << '>'
  17469. if !text_content.nil?
  17470. result << text_content
  17471. nested[0].sub!(/^\s+/, '') if !nested.empty?
  17472. else
  17473. result << nl
  17474. end
  17475. if !nested.empty?
  17476. result << nested << indent
  17477. end
  17478. result << '</' << name << '>' << nl
  17479. else
  17480. result << nested
  17481. end
  17482. else
  17483. result << ' />' << nl
  17484. end
  17485. elsif ref.instance_of?(Array)
  17486. ref.each { |value|
  17487. if scalar(value)
  17488. result << indent << '<' << name << '>'
  17489. result << (@options['noescape'] ? value.to_s : escape_value(value.to_s))
  17490. result << '</' << name << '>' << nl
  17491. elsif value.instance_of?(Hash)
  17492. result << value_to_xml(value, name, indent)
  17493. else
  17494. result << indent << '<' << name << '>' << nl
  17495. result << value_to_xml(value, @options['anonymoustag'], indent + @options['indent'])
  17496. result << indent << '</' << name << '>' << nl
  17497. end
  17498. }
  17499. else
  17500. # Probably, this is obsolete.
  17501. raise ArgumentError, "Can't encode a value of type: #{ref.type}."
  17502. end
  17503. @ancestors.pop if !scalar(ref)
  17504. result.join('')
  17505. end
  17506. # Checks, if a certain value is a "scalar" value. Whatever
  17507. # that will be in Ruby ... ;-)
  17508. #
  17509. # value::
  17510. # Value to be checked.
  17511. def scalar(value)
  17512. return false if value.instance_of?(Hash) || value.instance_of?(Array)
  17513. return true
  17514. end
  17515. # Attempts to unfold a hash of hashes into an array of hashes. Returns
  17516. # a reference to th array on success or the original hash, if unfolding
  17517. # is not possible.
  17518. #
  17519. # parent::
  17520. #
  17521. # hashref::
  17522. # Reference to the hash to be unfolded.
  17523. def hash_to_array(parent, hashref)
  17524. arrayref = []
  17525. hashref.each { |key, value|
  17526. return hashref unless value.instance_of?(Hash)
  17527. if @options['keyattr'].instance_of?(Hash)
  17528. return hashref unless @options['keyattr'].has_key?(parent)
  17529. arrayref << { @options['keyattr'][parent][0] => key }.update(value)
  17530. else
  17531. arrayref << { @options['keyattr'][0] => key }.update(value)
  17532. end
  17533. }
  17534. arrayref
  17535. end
  17536. # Replaces XML markup characters by their external entities.
  17537. #
  17538. # data::
  17539. # The string to be escaped.
  17540. def escape_value(data)
  17541. return data if data.nil? || data == ''
  17542. result = data.dup
  17543. result.gsub!('&', '&amp;')
  17544. result.gsub!('<', '&lt;')
  17545. result.gsub!('>', '&gt;')
  17546. result.gsub!('"', '&quot;')
  17547. result.gsub!("'", '&apos;')
  17548. result
  17549. end
  17550. # Removes leading and trailing whitespace and sequences of
  17551. # whitespaces from a string.
  17552. #
  17553. # text::
  17554. # String to be normalised.
  17555. def normalise_space(text)
  17556. text.sub!(/^\s+/, '')
  17557. text.sub!(/\s+$/, '')
  17558. text.gsub!(/\s\s+/, ' ')
  17559. text
  17560. end
  17561. # Checks, if an object is nil, an empty String or an empty Hash.
  17562. # Thanks to Norbert Gawor for a bugfix.
  17563. #
  17564. # value::
  17565. # Value to be checked for emptyness.
  17566. def empty(value)
  17567. case value
  17568. when Hash
  17569. return value.empty?
  17570. when String
  17571. return value !~ /\S/m
  17572. else
  17573. return value.nil?
  17574. end
  17575. end
  17576. # Converts a document node into a String.
  17577. # If the node could not be converted into a String
  17578. # for any reason, default will be returned.
  17579. #
  17580. # node::
  17581. # Document node to be converted.
  17582. # default::
  17583. # Value to be returned, if node could not be converted.
  17584. def node_to_text(node, default = nil)
  17585. if node.instance_of?(Element)
  17586. return node.texts.join('')
  17587. elsif node.instance_of?(Attribute)
  17588. return node.value.nil? ? default : node.value.strip
  17589. elsif node.instance_of?(Text)
  17590. return node.to_s.strip
  17591. else
  17592. return default
  17593. end
  17594. end
  17595. # Parses an XML string and returns the according document.
  17596. #
  17597. # xml_string::
  17598. # XML string to be parsed.
  17599. #
  17600. # The following exception may be raised:
  17601. #
  17602. # REXML::ParseException::
  17603. # If the specified file is not wellformed.
  17604. def parse(xml_string)
  17605. Document.new(xml_string)
  17606. end
  17607. # Searches in a list of paths for a certain file. Returns
  17608. # the full path to the file, if it could be found. Otherwise,
  17609. # an exception will be raised.
  17610. #
  17611. # filename::
  17612. # Name of the file to search for.
  17613. # searchpath::
  17614. # List of paths to search in.
  17615. def find_xml_file(file, searchpath)
  17616. filename = File::basename(file)
  17617. if filename != file
  17618. return file if File::file?(file)
  17619. else
  17620. searchpath.each { |path|
  17621. full_path = File::join(path, filename)
  17622. return full_path if File::file?(full_path)
  17623. }
  17624. end
  17625. if searchpath.empty?
  17626. return file if File::file?(file)
  17627. raise ArgumentError, "File does not exist: #{file}."
  17628. end
  17629. raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
  17630. end
  17631. # Loads and parses an XML configuration file.
  17632. #
  17633. # filename::
  17634. # Name of the configuration file to be loaded.
  17635. #
  17636. # The following exceptions may be raised:
  17637. #
  17638. # Errno::ENOENT::
  17639. # If the specified file does not exist.
  17640. # REXML::ParseException::
  17641. # If the specified file is not wellformed.
  17642. def load_xml_file(filename)
  17643. parse(File.readlines(filename).to_s)
  17644. end
  17645. # Caches the data belonging to a certain file.
  17646. #
  17647. # data::
  17648. # Data to be cached.
  17649. # filename::
  17650. # Name of file the data was read from.
  17651. def put_into_cache(data, filename)
  17652. if @options.has_key?('cache')
  17653. @options['cache'].each { |scheme|
  17654. case(scheme)
  17655. when 'storable'
  17656. @@cache.save_storable(data, filename)
  17657. when 'mem_share'
  17658. @@cache.save_mem_share(data, filename)
  17659. when 'mem_copy'
  17660. @@cache.save_mem_copy(data, filename)
  17661. else
  17662. raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
  17663. end
  17664. }
  17665. end
  17666. end
  17667. end
  17668. # vim:sw=2
  17669. module ActionController #:nodoc:
  17670. module Verification #:nodoc:
  17671. def self.append_features(base) #:nodoc:
  17672. super
  17673. base.extend(ClassMethods)
  17674. end
  17675. # This module provides a class-level method for specifying that certain
  17676. # actions are guarded against being called without certain prerequisites
  17677. # being met. This is essentially a special kind of before_filter.
  17678. #
  17679. # An action may be guarded against being invoked without certain request
  17680. # parameters being set, or without certain session values existing.
  17681. #
  17682. # When a verification is violated, values may be inserted into the flash, and
  17683. # a specified redirection is triggered.
  17684. #
  17685. # Usage:
  17686. #
  17687. # class GlobalController < ActionController::Base
  17688. # # prevent the #update_settings action from being invoked unless
  17689. # # the 'admin_privileges' request parameter exists.
  17690. # verify :params => "admin_privileges", :only => :update_post,
  17691. # :redirect_to => { :action => "settings" }
  17692. #
  17693. # # disallow a post from being updated if there was no information
  17694. # # submitted with the post, and if there is no active post in the
  17695. # # session, and if there is no "note" key in the flash.
  17696. # verify :params => "post", :session => "post", "flash" => "note",
  17697. # :only => :update_post,
  17698. # :add_flash => { "alert" => "Failed to create your message" },
  17699. # :redirect_to => :category_url
  17700. #
  17701. module ClassMethods
  17702. # Verify the given actions so that if certain prerequisites are not met,
  17703. # the user is redirected to a different action. The +options+ parameter
  17704. # is a hash consisting of the following key/value pairs:
  17705. #
  17706. # * <tt>:params</tt>: a single key or an array of keys that must
  17707. # be in the <tt>params</tt> hash in order for the action(s) to be safely
  17708. # called.
  17709. # * <tt>:session</tt>: a single key or an array of keys that must
  17710. # be in the @session in order for the action(s) to be safely called.
  17711. # * <tt>:flash</tt>: a single key or an array of keys that must
  17712. # be in the flash in order for the action(s) to be safely called.
  17713. # * <tt>:method</tt>: a single key or an array of keys--any one of which
  17714. # must match the current request method in order for the action(s) to
  17715. # be safely called. (The key should be a symbol: <tt>:get</tt> or
  17716. # <tt>:post</tt>, for example.)
  17717. # * <tt>:xhr</tt>: true/false option to ensure that the request is coming
  17718. # from an Ajax call or not.
  17719. # * <tt>:add_flash</tt>: a hash of name/value pairs that should be merged
  17720. # into the session's flash if the prerequisites cannot be satisfied.
  17721. # * <tt>:redirect_to</tt>: the redirection parameters to be used when
  17722. # redirecting if the prerequisites cannot be satisfied.
  17723. # * <tt>:render</tt>: the render parameters to be used when
  17724. # the prerequisites cannot be satisfied.
  17725. # * <tt>:only</tt>: only apply this verification to the actions specified
  17726. # in the associated array (may also be a single value).
  17727. # * <tt>:except</tt>: do not apply this verification to the actions
  17728. # specified in the associated array (may also be a single value).
  17729. def verify(options={})
  17730. filter_opts = { :only => options[:only], :except => options[:except] }
  17731. before_filter(filter_opts) do |c|
  17732. c.send :verify_action, options
  17733. end
  17734. end
  17735. end
  17736. def verify_action(options) #:nodoc:
  17737. prereqs_invalid =
  17738. [*options[:params] ].find { |v| @params[v].nil? } ||
  17739. [*options[:session]].find { |v| @session[v].nil? } ||
  17740. [*options[:flash] ].find { |v| flash[v].nil? }
  17741. if !prereqs_invalid && options[:method]
  17742. prereqs_invalid ||=
  17743. [*options[:method]].all? { |v| @request.method != v.to_sym }
  17744. end
  17745. prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil?
  17746. if prereqs_invalid
  17747. flash.update(options[:add_flash]) if options[:add_flash]
  17748. unless performed?
  17749. render(options[:render]) if options[:render]
  17750. redirect_to(options[:redirect_to]) if options[:redirect_to]
  17751. end
  17752. return false
  17753. end
  17754. true
  17755. end
  17756. private :verify_action
  17757. end
  17758. end
  17759. #--
  17760. # Copyright (c) 2004 David Heinemeier Hansson
  17761. #
  17762. # Permission is hereby granted, free of charge, to any person obtaining
  17763. # a copy of this software and associated documentation files (the
  17764. # "Software"), to deal in the Software without restriction, including
  17765. # without limitation the rights to use, copy, modify, merge, publish,
  17766. # distribute, sublicense, and/or sell copies of the Software, and to
  17767. # permit persons to whom the Software is furnished to do so, subject to
  17768. # the following conditions:
  17769. #
  17770. # The above copyright notice and this permission notice shall be
  17771. # included in all copies or substantial portions of the Software.
  17772. #
  17773. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17774. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17775. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17776. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  17777. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  17778. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  17779. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  17780. #++
  17781. $:.unshift(File.dirname(__FILE__)) unless
  17782. $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
  17783. unless defined?(ActiveSupport)
  17784. begin
  17785. $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
  17786. require 'active_support'
  17787. rescue LoadError
  17788. require 'rubygems'
  17789. require_gem 'activesupport'
  17790. end
  17791. end
  17792. require 'action_controller/base'
  17793. require 'action_controller/deprecated_redirects'
  17794. require 'action_controller/request'
  17795. require 'action_controller/deprecated_request_methods'
  17796. require 'action_controller/rescue'
  17797. require 'action_controller/benchmarking'
  17798. require 'action_controller/flash'
  17799. require 'action_controller/filters'
  17800. require 'action_controller/layout'
  17801. require 'action_controller/dependencies'
  17802. require 'action_controller/mime_responds'
  17803. require 'action_controller/pagination'
  17804. require 'action_controller/scaffolding'
  17805. require 'action_controller/helpers'
  17806. require 'action_controller/cookies'
  17807. require 'action_controller/cgi_process'
  17808. require 'action_controller/caching'
  17809. require 'action_controller/verification'
  17810. require 'action_controller/streaming'
  17811. require 'action_controller/session_management'
  17812. require 'action_controller/components'
  17813. require 'action_controller/macros/auto_complete'
  17814. require 'action_controller/macros/in_place_editing'
  17815. require 'action_view'
  17816. ActionController::Base.template_class = ActionView::Base
  17817. ActionController::Base.class_eval do
  17818. include ActionController::Flash
  17819. include ActionController::Filters
  17820. include ActionController::Layout
  17821. include ActionController::Benchmarking
  17822. include ActionController::Rescue
  17823. include ActionController::Dependencies
  17824. include ActionController::MimeResponds
  17825. include ActionController::Pagination
  17826. include ActionController::Scaffolding
  17827. include ActionController::Helpers
  17828. include ActionController::Cookies
  17829. include ActionController::Caching
  17830. include ActionController::Verification
  17831. include ActionController::Streaming
  17832. include ActionController::SessionManagement
  17833. include ActionController::Components
  17834. include ActionController::Macros::AutoComplete
  17835. include ActionController::Macros::InPlaceEditing
  17836. end
  17837. module ActionPack #:nodoc:
  17838. module VERSION #:nodoc:
  17839. MAJOR = 1
  17840. MINOR = 12
  17841. TINY = 5
  17842. STRING = [MAJOR, MINOR, TINY].join('.')
  17843. end
  17844. end
  17845. #--
  17846. # Copyright (c) 2004 David Heinemeier Hansson
  17847. #
  17848. # Permission is hereby granted, free of charge, to any person obtaining
  17849. # a copy of this software and associated documentation files (the
  17850. # "Software"), to deal in the Software without restriction, including
  17851. # without limitation the rights to use, copy, modify, merge, publish,
  17852. # distribute, sublicense, and/or sell copies of the Software, and to
  17853. # permit persons to whom the Software is furnished to do so, subject to
  17854. # the following conditions:
  17855. #
  17856. # The above copyright notice and this permission notice shall be
  17857. # included in all copies or substantial portions of the Software.
  17858. #
  17859. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17860. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17861. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17862. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  17863. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  17864. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  17865. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  17866. #++
  17867. require 'action_pack/version'
  17868. require 'erb'
  17869. module ActionView #:nodoc:
  17870. class ActionViewError < StandardError #:nodoc:
  17871. end
  17872. # Action View templates can be written in three ways. If the template file has a +.rhtml+ extension then it uses a mixture of ERb
  17873. # (included in Ruby) and HTML. If the template file has a +.rxml+ extension then Jim Weirich's Builder::XmlMarkup library is used.
  17874. # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
  17875. #
  17876. # = ERb
  17877. #
  17878. # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
  17879. # following loop for names:
  17880. #
  17881. # <b>Names of all the people</b>
  17882. # <% for person in @people %>
  17883. # Name: <%= person.name %><br/>
  17884. # <% end %>
  17885. #
  17886. # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
  17887. # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
  17888. #
  17889. # Hi, Mr. <% puts "Frodo" %>
  17890. #
  17891. # If you absolutely must write from within a function, you can use the TextHelper#concat
  17892. #
  17893. # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
  17894. #
  17895. # == Using sub templates
  17896. #
  17897. # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
  17898. # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
  17899. #
  17900. # <%= render "shared/header" %>
  17901. # Something really specific and terrific
  17902. # <%= render "shared/footer" %>
  17903. #
  17904. # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
  17905. # result of the rendering. The output embedding writes it to the current template.
  17906. #
  17907. # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
  17908. # variables defined using the regular embedding tags. Like this:
  17909. #
  17910. # <% @page_title = "A Wonderful Hello" %>
  17911. # <%= render "shared/header" %>
  17912. #
  17913. # Now the header can pick up on the @page_title variable and use it for outputting a title tag:
  17914. #
  17915. # <title><%= @page_title %></title>
  17916. #
  17917. # == Passing local variables to sub templates
  17918. #
  17919. # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
  17920. #
  17921. # <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
  17922. #
  17923. # These can now be accessed in shared/header with:
  17924. #
  17925. # Headline: <%= headline %>
  17926. # First name: <%= person.first_name %>
  17927. #
  17928. # == Template caching
  17929. #
  17930. # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will
  17931. # check the file's modification time and recompile it.
  17932. #
  17933. # == Builder
  17934. #
  17935. # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object
  17936. # named +xml+ is automatically made available to templates with a +.rxml+ extension.
  17937. #
  17938. # Here are some basic examples:
  17939. #
  17940. # xml.em("emphasized") # => <em>emphasized</em>
  17941. # xml.em { xml.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
  17942. # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
  17943. # xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
  17944. # # NOTE: order of attributes is not specified.
  17945. #
  17946. # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
  17947. #
  17948. # xml.div {
  17949. # xml.h1(@person.name)
  17950. # xml.p(@person.bio)
  17951. # }
  17952. #
  17953. # would produce something like:
  17954. #
  17955. # <div>
  17956. # <h1>David Heinemeier Hansson</h1>
  17957. # <p>A product of Danish Design during the Winter of '79...</p>
  17958. # </div>
  17959. #
  17960. # A full-length RSS example actually used on Basecamp:
  17961. #
  17962. # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  17963. # xml.channel do
  17964. # xml.title(@feed_title)
  17965. # xml.link(@url)
  17966. # xml.description "Basecamp: Recent items"
  17967. # xml.language "en-us"
  17968. # xml.ttl "40"
  17969. #
  17970. # for item in @recent_items
  17971. # xml.item do
  17972. # xml.title(item_title(item))
  17973. # xml.description(item_description(item)) if item_description(item)
  17974. # xml.pubDate(item_pubDate(item))
  17975. # xml.guid(@person.firm.account.url + @recent_items.url(item))
  17976. # xml.link(@person.firm.account.url + @recent_items.url(item))
  17977. #
  17978. # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
  17979. # end
  17980. # end
  17981. # end
  17982. # end
  17983. #
  17984. # More builder documentation can be found at http://builder.rubyforge.org.
  17985. #
  17986. # == JavaScriptGenerator
  17987. #
  17988. # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to
  17989. # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
  17990. # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
  17991. # and make updates to the page where the request originated from.
  17992. #
  17993. # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
  17994. #
  17995. # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
  17996. #
  17997. # link_to_remote :url => {:action => 'delete'}
  17998. #
  17999. # The subsequently rendered +delete.rjs+ might look like:
  18000. #
  18001. # page.replace_html 'sidebar', :partial => 'sidebar'
  18002. # page.remove "person-#{@person.id}"
  18003. # page.visual_effect :highlight, 'user-list'
  18004. #
  18005. # This refreshes the sidebar, removes a person element and highlights the user list.
  18006. #
  18007. # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator documentation for more details.
  18008. class Base
  18009. include ERB::Util
  18010. attr_reader :first_render
  18011. attr_accessor :base_path, :assigns, :template_extension
  18012. attr_accessor :controller
  18013. attr_reader :logger, :params, :request, :response, :session, :headers, :flash
  18014. # Specify trim mode for the ERB compiler. Defaults to '-'.
  18015. # See ERB documentation for suitable values.
  18016. @@erb_trim_mode = '-'
  18017. cattr_accessor :erb_trim_mode
  18018. # Specify whether file modification times should be checked to see if a template needs recompilation
  18019. @@cache_template_loading = false
  18020. cattr_accessor :cache_template_loading
  18021. # Specify whether file extension lookup should be cached.
  18022. # Should be +false+ for development environments. Defaults to +true+.
  18023. @@cache_template_extensions = true
  18024. cattr_accessor :cache_template_extensions
  18025. # Specify whether local_assigns should be able to use string keys.
  18026. # Defaults to +true+. String keys are deprecated and will be removed
  18027. # shortly.
  18028. @@local_assigns_support_string_keys = true
  18029. cattr_accessor :local_assigns_support_string_keys
  18030. # Specify whether RJS responses should be wrapped in a try/catch block
  18031. # that alert()s the caught exception (and then re-raises it).
  18032. @@debug_rjs = false
  18033. cattr_accessor :debug_rjs
  18034. @@template_handlers = HashWithIndifferentAccess.new
  18035. module CompiledTemplates #:nodoc:
  18036. # holds compiled template code
  18037. end
  18038. include CompiledTemplates
  18039. # maps inline templates to their method names
  18040. @@method_names = {}
  18041. # map method names to their compile time
  18042. @@compile_time = {}
  18043. # map method names to the names passed in local assigns so far
  18044. @@template_args = {}
  18045. # count the number of inline templates
  18046. @@inline_template_count = 0
  18047. # maps template paths without extension to their file extension returned by pick_template_extension.
  18048. # if for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
  18049. # used by pick_template_extension determines whether ext1 or ext2 will be stored
  18050. @@cached_template_extension = {}
  18051. class ObjectWrapper < Struct.new(:value) #:nodoc:
  18052. end
  18053. def self.load_helpers(helper_dir)#:nodoc:
  18054. Dir.foreach(helper_dir) do |helper_file|
  18055. next unless helper_file =~ /^([a-z][a-z_]*_helper).rb$/
  18056. require File.join(helper_dir, $1)
  18057. helper_module_name = $1.camelize
  18058. class_eval("include ActionView::Helpers::#{helper_module_name}") if Helpers.const_defined?(helper_module_name)
  18059. end
  18060. end
  18061. # Register a class that knows how to handle template files with the given
  18062. # extension. This can be used to implement new template types.
  18063. # The constructor for the class must take the ActiveView::Base instance
  18064. # as a parameter, and the class must implement a #render method that
  18065. # takes the contents of the template to render as well as the Hash of
  18066. # local assigns available to the template. The #render method ought to
  18067. # return the rendered template as a string.
  18068. def self.register_template_handler(extension, klass)
  18069. @@template_handlers[extension] = klass
  18070. end
  18071. def initialize(base_path = nil, assigns_for_first_render = {}, controller = nil)#:nodoc:
  18072. @base_path, @assigns = base_path, assigns_for_first_render
  18073. @assigns_added = nil
  18074. @controller = controller
  18075. @logger = controller && controller.logger
  18076. end
  18077. # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
  18078. # it's relative to the template_root, otherwise it's absolute. The hash in <tt>local_assigns</tt>
  18079. # is made available as local variables.
  18080. def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
  18081. @first_render ||= template_path
  18082. if use_full_path
  18083. template_path_without_extension, template_extension = path_and_extension(template_path)
  18084. if template_extension
  18085. template_file_name = full_template_path(template_path_without_extension, template_extension)
  18086. else
  18087. template_extension = pick_template_extension(template_path).to_s
  18088. template_file_name = full_template_path(template_path, template_extension)
  18089. end
  18090. else
  18091. template_file_name = template_path
  18092. template_extension = template_path.split('.').last
  18093. end
  18094. template_source = nil # Don't read the source until we know that it is required
  18095. begin
  18096. render_template(template_extension, template_source, template_file_name, local_assigns)
  18097. rescue Exception => e
  18098. if TemplateError === e
  18099. e.sub_template_of(template_file_name)
  18100. raise e
  18101. else
  18102. raise TemplateError.new(@base_path, template_file_name, @assigns, template_source, e)
  18103. end
  18104. end
  18105. end
  18106. # Renders the template present at <tt>template_path</tt> (relative to the template_root).
  18107. # The hash in <tt>local_assigns</tt> is made available as local variables.
  18108. def render(options = {}, old_local_assigns = {}, &block) #:nodoc:
  18109. if options.is_a?(String)
  18110. render_file(options, true, old_local_assigns)
  18111. elsif options == :update
  18112. update_page(&block)
  18113. elsif options.is_a?(Hash)
  18114. options[:locals] ||= {}
  18115. options[:use_full_path] = options[:use_full_path].nil? ? true : options[:use_full_path]
  18116. if options[:file]
  18117. render_file(options[:file], options[:use_full_path], options[:locals])
  18118. elsif options[:partial] && options[:collection]
  18119. render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
  18120. elsif options[:partial]
  18121. render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
  18122. elsif options[:inline]
  18123. render_template(options[:type] || :rhtml, options[:inline], nil, options[:locals] || {})
  18124. end
  18125. end
  18126. end
  18127. # Renders the +template+ which is given as a string as either rhtml or rxml depending on <tt>template_extension</tt>.
  18128. # The hash in <tt>local_assigns</tt> is made available as local variables.
  18129. def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc:
  18130. if handler = @@template_handlers[template_extension]
  18131. template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded.
  18132. delegate_render(handler, template, local_assigns)
  18133. else
  18134. compile_and_render_template(template_extension, template, file_path, local_assigns)
  18135. end
  18136. end
  18137. # Render the provided template with the given local assigns. If the template has not been rendered with the provided
  18138. # local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method.
  18139. #
  18140. # Either, but not both, of template and file_path may be nil. If file_path is given, the template
  18141. # will only be read if it has to be compiled.
  18142. #
  18143. def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc:
  18144. # compile the given template, if necessary
  18145. if compile_template?(template, file_path, local_assigns)
  18146. template ||= read_template_file(file_path, extension)
  18147. compile_template(extension, template, file_path, local_assigns)
  18148. end
  18149. # Get the method name for this template and run it
  18150. method_name = @@method_names[file_path || template]
  18151. evaluate_assigns
  18152. local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
  18153. send(method_name, local_assigns) do |*name|
  18154. instance_variable_get "@content_for_#{name.first || 'layout'}"
  18155. end
  18156. end
  18157. def pick_template_extension(template_path)#:nodoc:
  18158. if @@cache_template_extensions
  18159. @@cached_template_extension[template_path] ||= find_template_extension_for(template_path)
  18160. else
  18161. find_template_extension_for(template_path)
  18162. end
  18163. end
  18164. def delegate_template_exists?(template_path)#:nodoc:
  18165. @@template_handlers.find { |k,| template_exists?(template_path, k) }
  18166. end
  18167. def erb_template_exists?(template_path)#:nodoc:
  18168. template_exists?(template_path, :rhtml)
  18169. end
  18170. def builder_template_exists?(template_path)#:nodoc:
  18171. template_exists?(template_path, :rxml)
  18172. end
  18173. def javascript_template_exists?(template_path)#:nodoc:
  18174. template_exists?(template_path, :rjs)
  18175. end
  18176. def file_exists?(template_path)#:nodoc:
  18177. template_file_name, template_file_extension = path_and_extension(template_path)
  18178. if template_file_extension
  18179. template_exists?(template_file_name, template_file_extension)
  18180. else
  18181. cached_template_extension(template_path) ||
  18182. %w(erb builder javascript delegate).any? do |template_type|
  18183. send("#{template_type}_template_exists?", template_path)
  18184. end
  18185. end
  18186. end
  18187. # Returns true is the file may be rendered implicitly.
  18188. def file_public?(template_path)#:nodoc:
  18189. template_path.split('/').last[0,1] != '_'
  18190. end
  18191. private
  18192. def full_template_path(template_path, extension)
  18193. "#{@base_path}/#{template_path}.#{extension}"
  18194. end
  18195. def template_exists?(template_path, extension)
  18196. file_path = full_template_path(template_path, extension)
  18197. @@method_names.has_key?(file_path) || FileTest.exists?(file_path)
  18198. end
  18199. def path_and_extension(template_path)
  18200. template_path_without_extension = template_path.sub(/\.(\w+)$/, '')
  18201. [ template_path_without_extension, $1 ]
  18202. end
  18203. def cached_template_extension(template_path)
  18204. @@cache_template_extensions && @@cached_template_extension[template_path]
  18205. end
  18206. def find_template_extension_for(template_path)
  18207. if match = delegate_template_exists?(template_path)
  18208. match.first.to_sym
  18209. elsif erb_template_exists?(template_path): :rhtml
  18210. elsif builder_template_exists?(template_path): :rxml
  18211. elsif javascript_template_exists?(template_path): :rjs
  18212. else
  18213. raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}"
  18214. end
  18215. end
  18216. # This method reads a template file.
  18217. def read_template_file(template_path, extension)
  18218. File.read(template_path)
  18219. end
  18220. def evaluate_assigns
  18221. unless @assigns_added
  18222. assign_variables_from_controller
  18223. @assigns_added = true
  18224. end
  18225. end
  18226. def delegate_render(handler, template, local_assigns)
  18227. handler.new(self).render(template, local_assigns)
  18228. end
  18229. def assign_variables_from_controller
  18230. @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
  18231. end
  18232. # Return true if the given template was compiled for a superset of the keys in local_assigns
  18233. def supports_local_assigns?(render_symbol, local_assigns)
  18234. local_assigns.empty? ||
  18235. ((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
  18236. end
  18237. # Check whether compilation is necessary.
  18238. # Compile if the inline template or file has not been compiled yet.
  18239. # Or if local_assigns has a new key, which isn't supported by the compiled code yet.
  18240. # Or if the file has changed on disk and checking file mods hasn't been disabled.
  18241. def compile_template?(template, file_name, local_assigns)
  18242. method_key = file_name || template
  18243. render_symbol = @@method_names[method_key]
  18244. if @@compile_time[render_symbol] && supports_local_assigns?(render_symbol, local_assigns)
  18245. if file_name && !@@cache_template_loading
  18246. @@compile_time[render_symbol] < File.mtime(file_name) || (File.symlink?(file_name) ?
  18247. @@compile_time[render_symbol] < File.lstat(file_name).mtime : false)
  18248. end
  18249. else
  18250. true
  18251. end
  18252. end
  18253. # Create source code for given template
  18254. def create_template_source(extension, template, render_symbol, locals)
  18255. if template_requires_setup?(extension)
  18256. body = case extension.to_sym
  18257. when :rxml
  18258. "xml = Builder::XmlMarkup.new(:indent => 2)\n" +
  18259. "@controller.headers['Content-Type'] ||= 'application/xml'\n" +
  18260. template
  18261. when :rjs
  18262. "@controller.headers['Content-Type'] ||= 'text/javascript'\n" +
  18263. "update_page do |page|\n#{template}\nend"
  18264. end
  18265. else
  18266. body = ERB.new(template, nil, @@erb_trim_mode).src
  18267. end
  18268. @@template_args[render_symbol] ||= {}
  18269. locals_keys = @@template_args[render_symbol].keys | locals
  18270. @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
  18271. locals_code = ""
  18272. locals_keys.each do |key|
  18273. locals_code << "#{key} = local_assigns[:#{key}] if local_assigns.has_key?(:#{key})\n"
  18274. end
  18275. "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
  18276. end
  18277. def template_requires_setup?(extension)
  18278. templates_requiring_setup.include? extension.to_s
  18279. end
  18280. def templates_requiring_setup
  18281. %w(rxml rjs)
  18282. end
  18283. def assign_method_name(extension, template, file_name)
  18284. method_name = '_run_'
  18285. method_name << "#{extension}_" if extension
  18286. if file_name
  18287. file_path = File.expand_path(file_name)
  18288. base_path = File.expand_path(@base_path)
  18289. i = file_path.index(base_path)
  18290. l = base_path.length
  18291. method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone
  18292. method_name_file_part.sub!(/\.r(html|xml|js)$/,'')
  18293. method_name_file_part.tr!('/:-', '_')
  18294. method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s}
  18295. method_name += method_name_file_part
  18296. else
  18297. @@inline_template_count += 1
  18298. method_name << @@inline_template_count.to_s
  18299. end
  18300. @@method_names[file_name || template] = method_name.intern
  18301. end
  18302. def compile_template(extension, template, file_name, local_assigns)
  18303. method_key = file_name || template
  18304. render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name)
  18305. render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
  18306. line_offset = @@template_args[render_symbol].size
  18307. if extension
  18308. case extension.to_sym
  18309. when :rxml, :rjs
  18310. line_offset += 2
  18311. end
  18312. end
  18313. begin
  18314. unless file_name.blank?
  18315. CompiledTemplates.module_eval(render_source, file_name, -line_offset)
  18316. else
  18317. CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)
  18318. end
  18319. rescue Object => e
  18320. if logger
  18321. logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
  18322. logger.debug "Function body: #{render_source}"
  18323. logger.debug "Backtrace: #{e.backtrace.join("\n")}"
  18324. end
  18325. raise TemplateError.new(@base_path, method_key, @assigns, template, e)
  18326. end
  18327. @@compile_time[render_symbol] = Time.now
  18328. # logger.debug "Compiled template #{method_key}\n ==> #{render_symbol}" if logger
  18329. end
  18330. end
  18331. end
  18332. require 'action_view/template_error'
  18333. module ActionView
  18334. # CompiledTemplates modules hold methods that have been compiled.
  18335. # Templates are compiled into these methods so that they do not need to be
  18336. # re-read and re-parsed each request.
  18337. #
  18338. # Each template may be compiled into one or more methods. Each method accepts a given
  18339. # set of parameters which is used to implement local assigns passing.
  18340. #
  18341. # To use a compiled template module, create a new instance and include it into the class
  18342. # in which you want the template to be rendered.
  18343. class CompiledTemplates < Module #:nodoc:
  18344. attr_reader :method_names
  18345. def initialize
  18346. @method_names = Hash.new do |hash, key|
  18347. hash[key] = "__compiled_method_#{(hash.length + 1)}"
  18348. end
  18349. @mtimes = {}
  18350. end
  18351. # Return the full key for the given identifier and argument names
  18352. def full_key(identifier, arg_names)
  18353. [identifier, arg_names]
  18354. end
  18355. # Return the selector for this method or nil if it has not been compiled
  18356. def selector(identifier, arg_names)
  18357. key = full_key(identifier, arg_names)
  18358. method_names.key?(key) ? method_names[key] : nil
  18359. end
  18360. alias :compiled? :selector
  18361. # Return the time at which the method for the given identifier and argument names was compiled.
  18362. def mtime(identifier, arg_names)
  18363. @mtimes[full_key(identifier, arg_names)]
  18364. end
  18365. # Compile the provided source code for the given argument names and with the given initial line number.
  18366. # The identifier should be unique to this source.
  18367. #
  18368. # The file_name, if provided will appear in backtraces. If not provded, the file_name defaults
  18369. # to the identifier.
  18370. #
  18371. # This method will return the selector for the compiled version of this method.
  18372. def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil)
  18373. file_name ||= identifier
  18374. name = method_names[full_key(identifier, arg_names)]
  18375. arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})"
  18376. fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now)
  18377. method_def = wrap_source(name, arg_names, source)
  18378. begin
  18379. module_eval(method_def, fake_file_name, initial_line_number)
  18380. @mtimes[full_key(identifier, arg_names)] = Time.now
  18381. rescue Object => e
  18382. e.blame_file! identifier
  18383. raise
  18384. end
  18385. name
  18386. end
  18387. # Wrap the provided source in a def ... end block.
  18388. def wrap_source(name, arg_names, source)
  18389. "def #{name}(#{arg_names * ', '})\n#{source}\nend"
  18390. end
  18391. end
  18392. end
  18393. require 'cgi'
  18394. require File.dirname(__FILE__) + '/form_helper'
  18395. module ActionView
  18396. class Base
  18397. @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }
  18398. cattr_accessor :field_error_proc
  18399. end
  18400. module Helpers
  18401. # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form
  18402. # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
  18403. # is a great of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
  18404. # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
  18405. module ActiveRecordHelper
  18406. # Returns a default input tag for the type of object returned by the method. Example
  18407. # (title is a VARCHAR column and holds "Hello World"):
  18408. # input("post", "title") =>
  18409. # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
  18410. def input(record_name, method, options = {})
  18411. InstanceTag.new(record_name, method, self).to_tag(options)
  18412. end
  18413. # Returns an entire form with input tags and everything for a specified Active Record object. Example
  18414. # (post is a new record that has a title using VARCHAR and a body using TEXT):
  18415. # form("post") =>
  18416. # <form action='/post/create' method='post'>
  18417. # <p>
  18418. # <label for="post_title">Title</label><br />
  18419. # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
  18420. # </p>
  18421. # <p>
  18422. # <label for="post_body">Body</label><br />
  18423. # <textarea cols="40" id="post_body" name="post[body]" rows="20">
  18424. # Back to the hill and over it again!
  18425. # </textarea>
  18426. # </p>
  18427. # <input type='submit' value='Create' />
  18428. # </form>
  18429. #
  18430. # It's possible to specialize the form builder by using a different action name and by supplying another
  18431. # block renderer. Example (entry is a new record that has a message attribute using VARCHAR):
  18432. #
  18433. # form("entry", :action => "sign", :input_block =>
  18434. # Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
  18435. #
  18436. # <form action='/post/sign' method='post'>
  18437. # Message:
  18438. # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /><br />
  18439. # <input type='submit' value='Sign' />
  18440. # </form>
  18441. #
  18442. # It's also possible to add additional content to the form by giving it a block, such as:
  18443. #
  18444. # form("entry", :action => "sign") do |form|
  18445. # form << content_tag("b", "Department")
  18446. # form << collection_select("department", "id", @departments, "id", "name")
  18447. # end
  18448. def form(record_name, options = {})
  18449. record = instance_variable_get("@#{record_name}")
  18450. options = options.symbolize_keys
  18451. options[:action] ||= record.new_record? ? "create" : "update"
  18452. action = url_for(:action => options[:action], :id => record)
  18453. submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
  18454. contents = ''
  18455. contents << hidden_field(record_name, :id) unless record.new_record?
  18456. contents << all_input_tags(record, record_name, options)
  18457. yield contents if block_given?
  18458. contents << submit_tag(submit_value)
  18459. content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil)
  18460. end
  18461. # Returns a string containing the error message attached to the +method+ on the +object+, if one exists.
  18462. # This error message is wrapped in a DIV tag, which can be specialized to include both a +prepend_text+ and +append_text+
  18463. # to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message
  18464. # "can't be empty" on the title attribute):
  18465. #
  18466. # <%= error_message_on "post", "title" %> =>
  18467. # <div class="formError">can't be empty</div>
  18468. #
  18469. # <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> =>
  18470. # <div class="inputError">Title simply can't be empty (or it won't work)</div>
  18471. def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
  18472. if errors = instance_variable_get("@#{object}").errors.on(method)
  18473. content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
  18474. end
  18475. end
  18476. # Returns a string with a div containing all the error messages for the object located as an instance variable by the name
  18477. # of <tt>object_name</tt>. This div can be tailored by the following options:
  18478. #
  18479. # * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
  18480. # * <tt>id</tt> - The id of the error div (default: errorExplanation)
  18481. # * <tt>class</tt> - The class of the error div (default: errorExplanation)
  18482. #
  18483. # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
  18484. # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors
  18485. # instance yourself and set it up. View the source of this method to see how easy it is.
  18486. def error_messages_for(object_name, options = {})
  18487. options = options.symbolize_keys
  18488. object = instance_variable_get("@#{object_name}")
  18489. if object && !object.errors.empty?
  18490. content_tag("div",
  18491. content_tag(
  18492. options[:header_tag] || "h2",
  18493. "#{pluralize(object.errors.count, "error")} prohibited this #{object_name.to_s.gsub("_", " ")} from being saved"
  18494. ) +
  18495. content_tag("p", "There were problems with the following fields:") +
  18496. content_tag("ul", object.errors.full_messages.collect { |msg| content_tag("li", msg) }),
  18497. "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
  18498. )
  18499. else
  18500. ""
  18501. end
  18502. end
  18503. private
  18504. def all_input_tags(record, record_name, options)
  18505. input_block = options[:input_block] || default_input_block
  18506. record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
  18507. end
  18508. def default_input_block
  18509. Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
  18510. end
  18511. end
  18512. class InstanceTag #:nodoc:
  18513. def to_tag(options = {})
  18514. case column_type
  18515. when :string
  18516. field_type = @method_name.include?("password") ? "password" : "text"
  18517. to_input_field_tag(field_type, options)
  18518. when :text
  18519. to_text_area_tag(options)
  18520. when :integer, :float
  18521. to_input_field_tag("text", options)
  18522. when :date
  18523. to_date_select_tag(options)
  18524. when :datetime, :timestamp
  18525. to_datetime_select_tag(options)
  18526. when :boolean
  18527. to_boolean_select_tag(options)
  18528. end
  18529. end
  18530. alias_method :tag_without_error_wrapping, :tag
  18531. def tag(name, options)
  18532. if object.respond_to?("errors") && object.errors.respond_to?("on")
  18533. error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
  18534. else
  18535. tag_without_error_wrapping(name, options)
  18536. end
  18537. end
  18538. alias_method :content_tag_without_error_wrapping, :content_tag
  18539. def content_tag(name, value, options)
  18540. if object.respond_to?("errors") && object.errors.respond_to?("on")
  18541. error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
  18542. else
  18543. content_tag_without_error_wrapping(name, value, options)
  18544. end
  18545. end
  18546. alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
  18547. def to_date_select_tag(options = {})
  18548. if object.respond_to?("errors") && object.errors.respond_to?("on")
  18549. error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
  18550. else
  18551. to_date_select_tag_without_error_wrapping(options)
  18552. end
  18553. end
  18554. alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
  18555. def to_datetime_select_tag(options = {})
  18556. if object.respond_to?("errors") && object.errors.respond_to?("on")
  18557. error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
  18558. else
  18559. to_datetime_select_tag_without_error_wrapping(options)
  18560. end
  18561. end
  18562. def error_wrapping(html_tag, has_error)
  18563. has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
  18564. end
  18565. def error_message
  18566. object.errors.on(@method_name)
  18567. end
  18568. def column_type
  18569. object.send("column_for_attribute", @method_name).type
  18570. end
  18571. end
  18572. end
  18573. end
  18574. require 'cgi'
  18575. require File.dirname(__FILE__) + '/url_helper'
  18576. require File.dirname(__FILE__) + '/tag_helper'
  18577. module ActionView
  18578. module Helpers
  18579. # Provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
  18580. module AssetTagHelper
  18581. # Returns a link tag that browsers and news readers can use to auto-detect a RSS or ATOM feed for this page. The +type+ can
  18582. # either be <tt>:rss</tt> (default) or <tt>:atom</tt> and the +options+ follow the url_for style of declaring a link target.
  18583. #
  18584. # Examples:
  18585. # auto_discovery_link_tag # =>
  18586. # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/action" />
  18587. # auto_discovery_link_tag(:atom) # =>
  18588. # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.curenthost.com/controller/action" />
  18589. # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
  18590. # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/feed" />
  18591. # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
  18592. # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.curenthost.com/controller/feed" />
  18593. def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
  18594. tag(
  18595. "link",
  18596. "rel" => tag_options[:rel] || "alternate",
  18597. "type" => tag_options[:type] || "application/#{type}+xml",
  18598. "title" => tag_options[:title] || type.to_s.upcase,
  18599. "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
  18600. )
  18601. end
  18602. # Returns path to a javascript asset. Example:
  18603. #
  18604. # javascript_path "xmlhr" # => /javascripts/xmlhr.js
  18605. def javascript_path(source)
  18606. compute_public_path(source, 'javascripts', 'js')
  18607. end
  18608. JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
  18609. @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
  18610. # Returns a script include tag per source given as argument. Examples:
  18611. #
  18612. # javascript_include_tag "xmlhr" # =>
  18613. # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
  18614. #
  18615. # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
  18616. # <script type="text/javascript" src="/javascripts/common.javascript"></script>
  18617. # <script type="text/javascript" src="/elsewhere/cools.js"></script>
  18618. #
  18619. # javascript_include_tag :defaults # =>
  18620. # <script type="text/javascript" src="/javascripts/prototype.js"></script>
  18621. # <script type="text/javascript" src="/javascripts/effects.js"></script>
  18622. # ...
  18623. # <script type="text/javascript" src="/javascripts/application.js"></script> *see below
  18624. #
  18625. # If there's an <tt>application.js</tt> file in your <tt>public/javascripts</tt> directory,
  18626. # <tt>javascript_include_tag :defaults</tt> will automatically include it. This file
  18627. # facilitates the inclusion of small snippets of JavaScript code, along the lines of
  18628. # <tt>controllers/application.rb</tt> and <tt>helpers/application_helper.rb</tt>.
  18629. def javascript_include_tag(*sources)
  18630. options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
  18631. if sources.include?(:defaults)
  18632. sources = sources[0..(sources.index(:defaults))] +
  18633. @@javascript_default_sources.dup +
  18634. sources[(sources.index(:defaults) + 1)..sources.length]
  18635. sources.delete(:defaults)
  18636. sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
  18637. end
  18638. sources.collect { |source|
  18639. source = javascript_path(source)
  18640. content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
  18641. }.join("\n")
  18642. end
  18643. # Register one or more additional JavaScript files to be included when
  18644. #
  18645. # javascript_include_tag :defaults
  18646. #
  18647. # is called. This method is intended to be called only from plugin initialization
  18648. # to register extra .js files the plugin installed in <tt>public/javascripts</tt>.
  18649. def self.register_javascript_include_default(*sources)
  18650. @@javascript_default_sources.concat(sources)
  18651. end
  18652. def self.reset_javascript_include_default #:nodoc:
  18653. @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
  18654. end
  18655. # Returns path to a stylesheet asset. Example:
  18656. #
  18657. # stylesheet_path "style" # => /stylesheets/style.css
  18658. def stylesheet_path(source)
  18659. compute_public_path(source, 'stylesheets', 'css')
  18660. end
  18661. # Returns a css link tag per source given as argument. Examples:
  18662. #
  18663. # stylesheet_link_tag "style" # =>
  18664. # <link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />
  18665. #
  18666. # stylesheet_link_tag "style", :media => "all" # =>
  18667. # <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
  18668. #
  18669. # stylesheet_link_tag "random.styles", "/css/stylish" # =>
  18670. # <link href="/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />
  18671. # <link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />
  18672. def stylesheet_link_tag(*sources)
  18673. options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
  18674. sources.collect { |source|
  18675. source = stylesheet_path(source)
  18676. tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
  18677. }.join("\n")
  18678. end
  18679. # Returns path to an image asset. Example:
  18680. #
  18681. # The +src+ can be supplied as a...
  18682. # * full path, like "/my_images/image.gif"
  18683. # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
  18684. # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
  18685. def image_path(source)
  18686. compute_public_path(source, 'images', 'png')
  18687. end
  18688. # Returns an image tag converting the +options+ into html options on the tag, but with these special cases:
  18689. #
  18690. # * <tt>:alt</tt> - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
  18691. # * <tt>:size</tt> - Supplied as "XxY", so "30x45" becomes width="30" and height="45"
  18692. #
  18693. # The +src+ can be supplied as a...
  18694. # * full path, like "/my_images/image.gif"
  18695. # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
  18696. # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
  18697. def image_tag(source, options = {})
  18698. options.symbolize_keys!
  18699. options[:src] = image_path(source)
  18700. options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
  18701. if options[:size]
  18702. options[:width], options[:height] = options[:size].split("x")
  18703. options.delete :size
  18704. end
  18705. tag("img", options)
  18706. end
  18707. private
  18708. def compute_public_path(source, dir, ext)
  18709. source = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
  18710. source << ".#{ext}" unless source.split("/").last.include?(".")
  18711. source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
  18712. source = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
  18713. source = ActionController::Base.asset_host + source unless source.include?(":")
  18714. source
  18715. end
  18716. def rails_asset_id(source)
  18717. ENV["RAILS_ASSET_ID"] ||
  18718. File.mtime("#{RAILS_ROOT}/public/#{source}").to_i.to_s rescue ""
  18719. end
  18720. end
  18721. end
  18722. end
  18723. require 'benchmark'
  18724. module ActionView
  18725. module Helpers
  18726. module BenchmarkHelper
  18727. # Measures the execution time of a block in a template and reports the result to the log. Example:
  18728. #
  18729. # <% benchmark "Notes section" do %>
  18730. # <%= expensive_notes_operation %>
  18731. # <% end %>
  18732. #
  18733. # Will add something like "Notes section (0.34523)" to the log.
  18734. #
  18735. # You may give an optional logger level as the second argument
  18736. # (:debug, :info, :warn, :error). The default is :info.
  18737. def benchmark(message = "Benchmarking", level = :info)
  18738. if @logger
  18739. real = Benchmark.realtime { yield }
  18740. @logger.send level, "#{message} (#{'%.5f' % real})"
  18741. end
  18742. end
  18743. end
  18744. end
  18745. end
  18746. module ActionView
  18747. module Helpers
  18748. # See ActionController::Caching::Fragments for usage instructions.
  18749. module CacheHelper
  18750. def cache(name = {}, &block)
  18751. @controller.cache_erb_fragment(block, name)
  18752. end
  18753. end
  18754. end
  18755. end
  18756. module ActionView
  18757. module Helpers
  18758. # Capture lets you extract parts of code which
  18759. # can be used in other points of the template or even layout file.
  18760. #
  18761. # == Capturing a block into an instance variable
  18762. #
  18763. # <% @script = capture do %>
  18764. # [some html...]
  18765. # <% end %>
  18766. #
  18767. # == Add javascript to header using content_for
  18768. #
  18769. # content_for("name") is a wrapper for capture which will
  18770. # make the fragment available by name to a yielding layout or template.
  18771. #
  18772. # layout.rhtml:
  18773. #
  18774. # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  18775. # <head>
  18776. # <title>layout with js</title>
  18777. # <script type="text/javascript">
  18778. # <%= yield :script %>
  18779. # </script>
  18780. # </head>
  18781. # <body>
  18782. # <%= yield %>
  18783. # </body>
  18784. # </html>
  18785. #
  18786. # view.rhtml
  18787. #
  18788. # This page shows an alert box!
  18789. #
  18790. # <% content_for("script") do %>
  18791. # alert('hello world')
  18792. # <% end %>
  18793. #
  18794. # Normal view text
  18795. module CaptureHelper
  18796. # Capture allows you to extract a part of the template into an
  18797. # instance variable. You can use this instance variable anywhere
  18798. # in your templates and even in your layout.
  18799. #
  18800. # Example of capture being used in a .rhtml page:
  18801. #
  18802. # <% @greeting = capture do %>
  18803. # Welcome To my shiny new web page!
  18804. # <% end %>
  18805. #
  18806. # Example of capture being used in a .rxml page:
  18807. #
  18808. # @greeting = capture do
  18809. # 'Welcome To my shiny new web page!'
  18810. # end
  18811. def capture(*args, &block)
  18812. # execute the block
  18813. begin
  18814. buffer = eval("_erbout", block.binding)
  18815. rescue
  18816. buffer = nil
  18817. end
  18818. if buffer.nil?
  18819. capture_block(*args, &block)
  18820. else
  18821. capture_erb_with_buffer(buffer, *args, &block)
  18822. end
  18823. end
  18824. # Calling content_for stores the block of markup for later use.
  18825. # Subsequently, you can make calls to it by name with <tt>yield</tt>
  18826. # in another template or in the layout.
  18827. #
  18828. # Example:
  18829. #
  18830. # <% content_for("header") do %>
  18831. # alert('hello world')
  18832. # <% end %>
  18833. #
  18834. # You can use yield :header anywhere in your templates.
  18835. #
  18836. # <%= yield :header %>
  18837. #
  18838. # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it
  18839. # for elements that are going to be fragment cached.
  18840. #
  18841. # The deprecated way of accessing a content_for block was to use a instance variable
  18842. # named @@content_for_#{name_of_the_content_block}@. So <tt><%= content_for('footer') %></tt>
  18843. # would be avaiable as <tt><%= @content_for_footer %></tt>. The preferred notation now is
  18844. # <tt><%= yield :footer %></tt>.
  18845. def content_for(name, &block)
  18846. eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
  18847. end
  18848. private
  18849. def capture_block(*args, &block)
  18850. block.call(*args)
  18851. end
  18852. def capture_erb(*args, &block)
  18853. buffer = eval("_erbout", block.binding)
  18854. capture_erb_with_buffer(buffer, *args, &block)
  18855. end
  18856. def capture_erb_with_buffer(buffer, *args, &block)
  18857. pos = buffer.length
  18858. block.call(*args)
  18859. # extract the block
  18860. data = buffer[pos..-1]
  18861. # replace it in the original with empty string
  18862. buffer[pos..-1] = ''
  18863. data
  18864. end
  18865. def erb_content_for(name, &block)
  18866. eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
  18867. end
  18868. def block_content_for(name, &block)
  18869. eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
  18870. end
  18871. end
  18872. end
  18873. end
  18874. require "date"
  18875. module ActionView
  18876. module Helpers
  18877. # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
  18878. # share a number of common options that are as follows:
  18879. #
  18880. # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
  18881. # birthday[month] instead of date[month] if passed to the select_month method.
  18882. # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
  18883. # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month
  18884. # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]".
  18885. module DateHelper
  18886. DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
  18887. # Reports the approximate distance in time between two Time objects or integers.
  18888. # For example, if the distance is 47 minutes, it'll return
  18889. # "about 1 hour". See the source for the complete wording list.
  18890. #
  18891. # Integers are interpreted as seconds. So,
  18892. # <tt>distance_of_time_in_words(50)</tt> returns "less than a minute".
  18893. #
  18894. # Set <tt>include_seconds</tt> to true if you want more detailed approximations if distance < 1 minute
  18895. def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
  18896. from_time = from_time.to_time if from_time.respond_to?(:to_time)
  18897. to_time = to_time.to_time if to_time.respond_to?(:to_time)
  18898. distance_in_minutes = (((to_time - from_time).abs)/60).round
  18899. distance_in_seconds = ((to_time - from_time).abs).round
  18900. case distance_in_minutes
  18901. when 0..1
  18902. return (distance_in_minutes==0) ? 'less than a minute' : '1 minute' unless include_seconds
  18903. case distance_in_seconds
  18904. when 0..5 then 'less than 5 seconds'
  18905. when 6..10 then 'less than 10 seconds'
  18906. when 11..20 then 'less than 20 seconds'
  18907. when 21..40 then 'half a minute'
  18908. when 41..59 then 'less than a minute'
  18909. else '1 minute'
  18910. end
  18911. when 2..45 then "#{distance_in_minutes} minutes"
  18912. when 46..90 then 'about 1 hour'
  18913. when 90..1440 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
  18914. when 1441..2880 then '1 day'
  18915. else "#{(distance_in_minutes / 1440).round} days"
  18916. end
  18917. end
  18918. # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
  18919. def time_ago_in_words(from_time, include_seconds = false)
  18920. distance_of_time_in_words(from_time, Time.now, include_seconds)
  18921. end
  18922. alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
  18923. # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
  18924. # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
  18925. # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of
  18926. # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll
  18927. # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
  18928. # set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in
  18929. # the desired order. Symbols may be omitted and the respective select is not included.
  18930. #
  18931. # Passing :disabled => true as part of the +options+ will make elements inaccessible for change.
  18932. #
  18933. # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
  18934. #
  18935. # Examples:
  18936. #
  18937. # date_select("post", "written_on")
  18938. # date_select("post", "written_on", :start_year => 1995)
  18939. # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
  18940. # :discard_day => true, :include_blank => true)
  18941. # date_select("post", "written_on", :order => [:day, :month, :year])
  18942. # date_select("user", "birthday", :order => [:month, :day])
  18943. #
  18944. # The selects are prepared for multi-parameter assignment to an Active Record object.
  18945. def date_select(object_name, method, options = {})
  18946. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
  18947. end
  18948. # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
  18949. # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
  18950. #
  18951. # datetime_select("post", "written_on")
  18952. # datetime_select("post", "written_on", :start_year => 1995)
  18953. #
  18954. # The selects are prepared for multi-parameter assignment to an Active Record object.
  18955. def datetime_select(object_name, method, options = {})
  18956. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
  18957. end
  18958. # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
  18959. def select_date(date = Date.today, options = {})
  18960. select_year(date, options) + select_month(date, options) + select_day(date, options)
  18961. end
  18962. # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
  18963. def select_datetime(datetime = Time.now, options = {})
  18964. select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
  18965. select_hour(datetime, options) + select_minute(datetime, options)
  18966. end
  18967. # Returns a set of html select-tags (one for hour and minute)
  18968. def select_time(datetime = Time.now, options = {})
  18969. h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
  18970. end
  18971. # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
  18972. # The <tt>second</tt> can also be substituted for a second number.
  18973. # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
  18974. def select_second(datetime, options = {})
  18975. second_options = []
  18976. 0.upto(59) do |second|
  18977. second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
  18978. %(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
  18979. %(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
  18980. )
  18981. end
  18982. select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
  18983. end
  18984. # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
  18985. # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
  18986. # The <tt>minute</tt> can also be substituted for a minute number.
  18987. # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
  18988. def select_minute(datetime, options = {})
  18989. minute_options = []
  18990. 0.step(59, options[:minute_step] || 1) do |minute|
  18991. minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
  18992. %(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
  18993. %(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
  18994. )
  18995. end
  18996. select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
  18997. end
  18998. # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
  18999. # The <tt>hour</tt> can also be substituted for a hour number.
  19000. # Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
  19001. def select_hour(datetime, options = {})
  19002. hour_options = []
  19003. 0.upto(23) do |hour|
  19004. hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
  19005. %(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
  19006. %(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
  19007. )
  19008. end
  19009. select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
  19010. end
  19011. # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
  19012. # The <tt>date</tt> can also be substituted for a hour number.
  19013. # Override the field name using the <tt>:field_name</tt> option, 'day' by default.
  19014. def select_day(date, options = {})
  19015. day_options = []
  19016. 1.upto(31) do |day|
  19017. day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
  19018. %(<option value="#{day}" selected="selected">#{day}</option>\n) :
  19019. %(<option value="#{day}">#{day}</option>\n)
  19020. )
  19021. end
  19022. select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
  19023. end
  19024. # Returns a select tag with options for each of the months January through December with the current month selected.
  19025. # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
  19026. # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
  19027. # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
  19028. # set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
  19029. #
  19030. # select_month(Date.today) # Will use keys like "January", "March"
  19031. # select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
  19032. # select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
  19033. #
  19034. # Override the field name using the <tt>:field_name</tt> option, 'month' by default.
  19035. #
  19036. # If you would prefer to show month names as abbreviations, set the
  19037. # <tt>:use_short_month</tt> key in +options+ to true.
  19038. def select_month(date, options = {})
  19039. month_options = []
  19040. month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
  19041. 1.upto(12) do |month_number|
  19042. month_name = if options[:use_month_numbers]
  19043. month_number
  19044. elsif options[:add_month_numbers]
  19045. month_number.to_s + ' - ' + month_names[month_number]
  19046. else
  19047. month_names[month_number]
  19048. end
  19049. month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
  19050. %(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
  19051. %(<option value="#{month_number}">#{month_name}</option>\n)
  19052. )
  19053. end
  19054. select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
  19055. end
  19056. # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
  19057. # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
  19058. # lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be
  19059. # substituted for a year given as a number. Example:
  19060. #
  19061. # select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values
  19062. # select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values
  19063. #
  19064. # Override the field name using the <tt>:field_name</tt> option, 'year' by default.
  19065. def select_year(date, options = {})
  19066. year_options = []
  19067. y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
  19068. start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
  19069. step_val = start_year < end_year ? 1 : -1
  19070. start_year.step(end_year, step_val) do |year|
  19071. year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
  19072. %(<option value="#{year}" selected="selected">#{year}</option>\n) :
  19073. %(<option value="#{year}">#{year}</option>\n)
  19074. )
  19075. end
  19076. select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
  19077. end
  19078. private
  19079. def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
  19080. select_html = %(<select name="#{prefix || DEFAULT_PREFIX})
  19081. select_html << "[#{type}]" unless discard_type
  19082. select_html << %(")
  19083. select_html << %( disabled="disabled") if disabled
  19084. select_html << %(>\n)
  19085. select_html << %(<option value=""></option>\n) if include_blank
  19086. select_html << options.to_s
  19087. select_html << "</select>\n"
  19088. end
  19089. def leading_zero_on_single_digits(number)
  19090. number > 9 ? number : "0#{number}"
  19091. end
  19092. end
  19093. class InstanceTag #:nodoc:
  19094. include DateHelper
  19095. def to_date_select_tag(options = {})
  19096. defaults = { :discard_type => true }
  19097. options = defaults.merge(options)
  19098. options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
  19099. date = options[:include_blank] ? (value || 0) : (value || Date.today)
  19100. date_select = ''
  19101. options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
  19102. options[:order] ||= [:year, :month, :day]
  19103. position = {:year => 1, :month => 2, :day => 3}
  19104. discard = {}
  19105. discard[:year] = true if options[:discard_year]
  19106. discard[:month] = true if options[:discard_month]
  19107. discard[:day] = true if options[:discard_day] or options[:discard_month]
  19108. options[:order].each do |param|
  19109. date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
  19110. end
  19111. date_select
  19112. end
  19113. def to_datetime_select_tag(options = {})
  19114. defaults = { :discard_type => true }
  19115. options = defaults.merge(options)
  19116. options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
  19117. datetime = options[:include_blank] ? (value || nil) : (value || Time.now)
  19118. datetime_select = select_year(datetime, options_with_prefix.call(1))
  19119. datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
  19120. datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
  19121. datetime_select << ' &mdash; ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
  19122. datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
  19123. datetime_select
  19124. end
  19125. end
  19126. class FormBuilder
  19127. def date_select(method, options = {})
  19128. @template.date_select(@object_name, method, options.merge(:object => @object))
  19129. end
  19130. def datetime_select(method, options = {})
  19131. @template.datetime_select(@object_name, method, options.merge(:object => @object))
  19132. end
  19133. end
  19134. end
  19135. end
  19136. module ActionView
  19137. module Helpers
  19138. # Provides a set of methods for making it easier to locate problems.
  19139. module DebugHelper
  19140. # Returns a <pre>-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
  19141. def debug(object)
  19142. begin
  19143. Marshal::dump(object)
  19144. "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>"
  19145. rescue Object => e
  19146. # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
  19147. "<code class='debug_dump'>#{h(object.inspect)}</code>"
  19148. end
  19149. end
  19150. end
  19151. end
  19152. endrequire 'cgi'
  19153. require File.dirname(__FILE__) + '/date_helper'
  19154. require File.dirname(__FILE__) + '/tag_helper'
  19155. module ActionView
  19156. module Helpers
  19157. # Provides a set of methods for working with forms and especially forms related to objects assigned to the template.
  19158. # The following is an example of a complete form for a person object that works for both creates and updates built
  19159. # with all the form helpers. The <tt>@person</tt> object was assigned by an action on the controller:
  19160. # <form action="save_person" method="post">
  19161. # Name:
  19162. # <%= text_field "person", "name", "size" => 20 %>
  19163. #
  19164. # Password:
  19165. # <%= password_field "person", "password", "maxsize" => 20 %>
  19166. #
  19167. # Single?:
  19168. # <%= check_box "person", "single" %>
  19169. #
  19170. # Description:
  19171. # <%= text_area "person", "description", "cols" => 20 %>
  19172. #
  19173. # <input type="submit" value="Save">
  19174. # </form>
  19175. #
  19176. # ...is compiled to:
  19177. #
  19178. # <form action="save_person" method="post">
  19179. # Name:
  19180. # <input type="text" id="person_name" name="person[name]"
  19181. # size="20" value="<%= @person.name %>" />
  19182. #
  19183. # Password:
  19184. # <input type="password" id="person_password" name="person[password]"
  19185. # size="20" maxsize="20" value="<%= @person.password %>" />
  19186. #
  19187. # Single?:
  19188. # <input type="checkbox" id="person_single" name="person[single]" value="1" />
  19189. #
  19190. # Description:
  19191. # <textarea cols="20" rows="40" id="person_description" name="person[description]">
  19192. # <%= @person.description %>
  19193. # </textarea>
  19194. #
  19195. # <input type="submit" value="Save">
  19196. # </form>
  19197. #
  19198. # If the object name contains square brackets the id for the object will be inserted. Example:
  19199. #
  19200. # <%= text_field "person[]", "name" %>
  19201. #
  19202. # ...becomes:
  19203. #
  19204. # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
  19205. #
  19206. # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
  19207. # used by render_collection_of_partials, the "index" option may come in handy. Example:
  19208. #
  19209. # <%= text_field "person", "name", "index" => 1 %>
  19210. #
  19211. # becomes
  19212. #
  19213. # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
  19214. #
  19215. # There's also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
  19216. # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
  19217. module FormHelper
  19218. # Creates a form and a scope around a specific model object, which is then used as a base for questioning about
  19219. # values for the fields. Examples:
  19220. #
  19221. # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
  19222. # First name: <%= f.text_field :first_name %>
  19223. # Last name : <%= f.text_field :last_name %>
  19224. # Biography : <%= f.text_area :biography %>
  19225. # Admin? : <%= f.check_box :admin %>
  19226. # <% end %>
  19227. #
  19228. # Worth noting is that the form_for tag is called in a ERb evaluation block, not a ERb output block. So that's <tt><% %></tt>,
  19229. # not <tt><%= %></tt>. Also worth noting is that the form_for yields a form_builder object, in this example as f, which emulates
  19230. # the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>,
  19231. # you get away with <tt>f.text_field :name</tt>.
  19232. #
  19233. # That in itself is a modest increase in comfort. The big news is that form_for allows us to more easily escape the instance
  19234. # variable convention, so while the stand-alone approach would require <tt>text_field :person, :name, :object => person</tt>
  19235. # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
  19236. # <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
  19237. #
  19238. # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
  19239. # and methods from FormTagHelper. Example:
  19240. #
  19241. # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
  19242. # First name: <%= f.text_field :first_name %>
  19243. # Last name : <%= f.text_field :last_name %>
  19244. # Biography : <%= text_area :person, :biography %>
  19245. # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
  19246. # <% end %>
  19247. #
  19248. # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
  19249. # Like collection_select and datetime_select.
  19250. #
  19251. # Html attributes for the form tag can be given as :html => {...}. Example:
  19252. #
  19253. # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
  19254. # ...
  19255. # <% end %>
  19256. #
  19257. # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
  19258. # then use your custom builder like so:
  19259. #
  19260. # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
  19261. # <%= f.text_field :first_name %>
  19262. # <%= f.text_field :last_name %>
  19263. # <%= text_area :person, :biography %>
  19264. # <%= check_box_tag "person[admin]", @person.company.admin? %>
  19265. # <% end %>
  19266. #
  19267. # In many cases you will want to wrap the above in another helper, such as:
  19268. #
  19269. # def labelled_form_for(name, object, options, &proc)
  19270. # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
  19271. # end
  19272. #
  19273. def form_for(object_name, *args, &proc)
  19274. raise ArgumentError, "Missing block" unless block_given?
  19275. options = args.last.is_a?(Hash) ? args.pop : {}
  19276. concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
  19277. fields_for(object_name, *(args << options), &proc)
  19278. concat('</form>', proc.binding)
  19279. end
  19280. # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
  19281. # fields_for suitable for specifying additional model objects in the same form. Example:
  19282. #
  19283. # <% form_for :person, @person, :url => { :action => "update" } do |person_form| %>
  19284. # First name: <%= person_form.text_field :first_name %>
  19285. # Last name : <%= person_form.text_field :last_name %>
  19286. #
  19287. # <% fields_for :permission, @person.permission do |permission_fields| %>
  19288. # Admin? : <%= permission_fields.check_box :admin %>
  19289. # <% end %>
  19290. # <% end %>
  19291. #
  19292. # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
  19293. # Like collection_select and datetime_select.
  19294. def fields_for(object_name, *args, &proc)
  19295. raise ArgumentError, "Missing block" unless block_given?
  19296. options = args.last.is_a?(Hash) ? args.pop : {}
  19297. object = args.first
  19298. yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
  19299. end
  19300. # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
  19301. # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  19302. # hash with +options+.
  19303. #
  19304. # Examples (call, result):
  19305. # text_field("post", "title", "size" => 20)
  19306. # <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
  19307. def text_field(object_name, method, options = {})
  19308. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
  19309. end
  19310. # Works just like text_field, but returns an input tag of the "password" type instead.
  19311. def password_field(object_name, method, options = {})
  19312. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
  19313. end
  19314. # Works just like text_field, but returns an input tag of the "hidden" type instead.
  19315. def hidden_field(object_name, method, options = {})
  19316. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
  19317. end
  19318. # Works just like text_field, but returns an input tag of the "file" type instead, which won't have a default value.
  19319. def file_field(object_name, method, options = {})
  19320. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
  19321. end
  19322. # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
  19323. # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
  19324. # hash with +options+.
  19325. #
  19326. # Example (call, result):
  19327. # text_area("post", "body", "cols" => 20, "rows" => 40)
  19328. # <textarea cols="20" rows="40" id="post_body" name="post[body]">
  19329. # #{@post.body}
  19330. # </textarea>
  19331. def text_area(object_name, method, options = {})
  19332. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
  19333. end
  19334. # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
  19335. # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
  19336. # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
  19337. # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
  19338. # is set to 0 which is convenient for boolean values. Usually unchecked checkboxes don't post anything.
  19339. # We work around this problem by adding a hidden value with the same name as the checkbox.
  19340. #
  19341. # Example (call, result). Imagine that @post.validated? returns 1:
  19342. # check_box("post", "validated")
  19343. # <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
  19344. # <input name="post[validated]" type="hidden" value="0" />
  19345. #
  19346. # Example (call, result). Imagine that @puppy.gooddog returns no:
  19347. # check_box("puppy", "gooddog", {}, "yes", "no")
  19348. # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
  19349. # <input name="puppy[gooddog]" type="hidden" value="no" />
  19350. def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
  19351. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
  19352. end
  19353. # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
  19354. # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
  19355. # radio button will be checked. Additional options on the input tag can be passed as a
  19356. # hash with +options+.
  19357. # Example (call, result). Imagine that @post.category returns "rails":
  19358. # radio_button("post", "category", "rails")
  19359. # radio_button("post", "category", "java")
  19360. # <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
  19361. # <input type="radio" id="post_category" name="post[category]" value="java" />
  19362. #
  19363. def radio_button(object_name, method, tag_value, options = {})
  19364. InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
  19365. end
  19366. end
  19367. class InstanceTag #:nodoc:
  19368. include Helpers::TagHelper
  19369. attr_reader :method_name, :object_name
  19370. DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
  19371. DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
  19372. DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
  19373. DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS)
  19374. def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
  19375. @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
  19376. @template_object, @local_binding = template_object, local_binding
  19377. @object = object
  19378. if @object_name.sub!(/\[\]$/,"")
  19379. @auto_index = @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}").id_before_type_cast
  19380. end
  19381. end
  19382. def to_input_field_tag(field_type, options = {})
  19383. options = options.stringify_keys
  19384. options["size"] ||= options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"]
  19385. options = DEFAULT_FIELD_OPTIONS.merge(options)
  19386. if field_type == "hidden"
  19387. options.delete("size")
  19388. end
  19389. options["type"] = field_type
  19390. options["value"] ||= value_before_type_cast unless field_type == "file"
  19391. add_default_name_and_id(options)
  19392. tag("input", options)
  19393. end
  19394. def to_radio_button_tag(tag_value, options = {})
  19395. options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
  19396. options["type"] = "radio"
  19397. options["value"] = tag_value
  19398. options["checked"] = "checked" if value.to_s == tag_value.to_s
  19399. pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
  19400. options["id"] = @auto_index ?
  19401. "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
  19402. "#{@object_name}_#{@method_name}_#{pretty_tag_value}"
  19403. add_default_name_and_id(options)
  19404. tag("input", options)
  19405. end
  19406. def to_text_area_tag(options = {})
  19407. options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
  19408. add_default_name_and_id(options)
  19409. content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast), options)
  19410. end
  19411. def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
  19412. options = options.stringify_keys
  19413. options["type"] = "checkbox"
  19414. options["value"] = checked_value
  19415. checked = case value
  19416. when TrueClass, FalseClass
  19417. value
  19418. when NilClass
  19419. false
  19420. when Integer
  19421. value != 0
  19422. when String
  19423. value == checked_value
  19424. else
  19425. value.to_i != 0
  19426. end
  19427. if checked || options["checked"] == "checked"
  19428. options["checked"] = "checked"
  19429. else
  19430. options.delete("checked")
  19431. end
  19432. add_default_name_and_id(options)
  19433. tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
  19434. end
  19435. def to_date_tag()
  19436. defaults = DEFAULT_DATE_OPTIONS.dup
  19437. date = value || Date.today
  19438. options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
  19439. html_day_select(date, options.call(3)) +
  19440. html_month_select(date, options.call(2)) +
  19441. html_year_select(date, options.call(1))
  19442. end
  19443. def to_boolean_select_tag(options = {})
  19444. options = options.stringify_keys
  19445. add_default_name_and_id(options)
  19446. tag_text = "<select"
  19447. tag_text << tag_options(options)
  19448. tag_text << "><option value=\"false\""
  19449. tag_text << " selected" if value == false
  19450. tag_text << ">False</option><option value=\"true\""
  19451. tag_text << " selected" if value
  19452. tag_text << ">True</option></select>"
  19453. end
  19454. def to_content_tag(tag_name, options = {})
  19455. content_tag(tag_name, value, options)
  19456. end
  19457. def object
  19458. @object || @template_object.instance_variable_get("@#{@object_name}")
  19459. end
  19460. def value
  19461. unless object.nil?
  19462. object.send(@method_name)
  19463. end
  19464. end
  19465. def value_before_type_cast
  19466. unless object.nil?
  19467. object.respond_to?(@method_name + "_before_type_cast") ?
  19468. object.send(@method_name + "_before_type_cast") :
  19469. object.send(@method_name)
  19470. end
  19471. end
  19472. private
  19473. def add_default_name_and_id(options)
  19474. if options.has_key?("index")
  19475. options["name"] ||= tag_name_with_index(options["index"])
  19476. options["id"] ||= tag_id_with_index(options["index"])
  19477. options.delete("index")
  19478. elsif @auto_index
  19479. options["name"] ||= tag_name_with_index(@auto_index)
  19480. options["id"] ||= tag_id_with_index(@auto_index)
  19481. else
  19482. options["name"] ||= tag_name
  19483. options["id"] ||= tag_id
  19484. end
  19485. end
  19486. def tag_name
  19487. "#{@object_name}[#{@method_name}]"
  19488. end
  19489. def tag_name_with_index(index)
  19490. "#{@object_name}[#{index}][#{@method_name}]"
  19491. end
  19492. def tag_id
  19493. "#{@object_name}_#{@method_name}"
  19494. end
  19495. def tag_id_with_index(index)
  19496. "#{@object_name}_#{index}_#{@method_name}"
  19497. end
  19498. end
  19499. class FormBuilder #:nodoc:
  19500. # The methods which wrap a form helper call.
  19501. class_inheritable_accessor :field_helpers
  19502. self.field_helpers = (FormHelper.instance_methods - ['form_for'])
  19503. attr_accessor :object_name, :object
  19504. def initialize(object_name, object, template, options, proc)
  19505. @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
  19506. end
  19507. (field_helpers - %w(check_box radio_button)).each do |selector|
  19508. src = <<-end_src
  19509. def #{selector}(method, options = {})
  19510. @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
  19511. end
  19512. end_src
  19513. class_eval src, __FILE__, __LINE__
  19514. end
  19515. def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
  19516. @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
  19517. end
  19518. def radio_button(method, tag_value, options = {})
  19519. @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
  19520. end
  19521. end
  19522. end
  19523. end
  19524. require 'cgi'
  19525. require 'erb'
  19526. require File.dirname(__FILE__) + '/form_helper'
  19527. module ActionView
  19528. module Helpers
  19529. # Provides a number of methods for turning different kinds of containers into a set of option tags.
  19530. # == Options
  19531. # The <tt>collection_select</tt>, <tt>country_select</tt>, <tt>select</tt>,
  19532. # and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter,
  19533. # a hash.
  19534. #
  19535. # * <tt>:include_blank</tt> - set to true if the first option element of the select element is a blank. Useful if there is not a default value required for the select element. For example,
  19536. #
  19537. # select("post", "category", Post::CATEGORIES, {:include_blank => true})
  19538. #
  19539. # could become:
  19540. #
  19541. # <select name="post[category]">
  19542. # <option></option>
  19543. # <option>joke</option>
  19544. # <option>poem</option>
  19545. # </select>
  19546. #
  19547. # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
  19548. #
  19549. # Another common case is a select tag for an <tt>belongs_to</tt>-associated object. For example,
  19550. #
  19551. # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] })
  19552. #
  19553. # could become:
  19554. #
  19555. # <select name="post[person_id]">
  19556. # <option value="1">David</option>
  19557. # <option value="2">Sam</option>
  19558. # <option value="3">Tobias</option>
  19559. # </select>
  19560. module FormOptionsHelper
  19561. include ERB::Util
  19562. # Create a select tag and a series of contained option tags for the provided object and method.
  19563. # The option currently held by the object will be selected, provided that the object is available.
  19564. # See options_for_select for the required format of the choices parameter.
  19565. #
  19566. # Example with @post.person_id => 1:
  19567. # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
  19568. #
  19569. # could become:
  19570. #
  19571. # <select name="post[person_id]">
  19572. # <option></option>
  19573. # <option value="1" selected="selected">David</option>
  19574. # <option value="2">Sam</option>
  19575. # <option value="3">Tobias</option>
  19576. # </select>
  19577. #
  19578. # This can be used to provide a default set of options in the standard way: before rendering the create form, a
  19579. # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
  19580. # to the database. Instead, a second model object is created when the create request is received.
  19581. # This allows the user to submit a form page more than once with the expected results of creating multiple records.
  19582. # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
  19583. #
  19584. # By default, post.person_id is the selected option. Specify :selected => value to use a different selection
  19585. # or :selected => nil to leave all options unselected.
  19586. def select(object, method, choices, options = {}, html_options = {})
  19587. InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
  19588. end
  19589. # Return select and option tags for the given object and method using options_from_collection_for_select to generate the list of option tags.
  19590. def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
  19591. InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
  19592. end
  19593. # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
  19594. def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
  19595. InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
  19596. end
  19597. # Return select and option tags for the given object and method, using
  19598. # #time_zone_options_for_select to generate the list of option tags.
  19599. #
  19600. # In addition to the <tt>:include_blank</tt> option documented above,
  19601. # this method also supports a <tt>:model</tt> option, which defaults
  19602. # to TimeZone. This may be used by users to specify a different time
  19603. # zone model object. (See #time_zone_options_for_select for more
  19604. # information.)
  19605. def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
  19606. InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
  19607. end
  19608. # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
  19609. # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
  19610. # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
  19611. # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+
  19612. # may also be an array of values to be selected when using a multiple select.
  19613. #
  19614. # Examples (call, result):
  19615. # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
  19616. # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
  19617. #
  19618. # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
  19619. # <option>VISA</option>\n<option selected="selected">MasterCard</option>
  19620. #
  19621. # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
  19622. # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
  19623. #
  19624. # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
  19625. # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
  19626. #
  19627. # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
  19628. def options_for_select(container, selected = nil)
  19629. container = container.to_a if Hash === container
  19630. options_for_select = container.inject([]) do |options, element|
  19631. if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last)
  19632. is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) )
  19633. is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) )
  19634. if is_selected
  19635. options << "<option value=\"#{html_escape(element.last.to_s)}\" selected=\"selected\">#{html_escape(element.first.to_s)}</option>"
  19636. else
  19637. options << "<option value=\"#{html_escape(element.last.to_s)}\">#{html_escape(element.first.to_s)}</option>"
  19638. end
  19639. else
  19640. is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element) : element == selected) )
  19641. is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) )
  19642. options << ((is_selected) ? "<option value=\"#{html_escape(element.to_s)}\" selected=\"selected\">#{html_escape(element.to_s)}</option>" : "<option value=\"#{html_escape(element.to_s)}\">#{html_escape(element.to_s)}</option>")
  19643. end
  19644. end
  19645. options_for_select.join("\n")
  19646. end
  19647. # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
  19648. # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
  19649. # If +selected_value+ is specified, the element returning a match on +value_method+ will get the selected option tag.
  19650. #
  19651. # Example (call, result). Imagine a loop iterating over each +person+ in <tt>@project.people</tt> to generate an input tag:
  19652. # options_from_collection_for_select(@project.people, "id", "name")
  19653. # <option value="#{person.id}">#{person.name}</option>
  19654. #
  19655. # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
  19656. def options_from_collection_for_select(collection, value_method, text_method, selected_value = nil)
  19657. options_for_select(
  19658. collection.inject([]) { |options, object| options << [ object.send(text_method), object.send(value_method) ] },
  19659. selected_value
  19660. )
  19661. end
  19662. # Returns a string of option tags, like options_from_collection_for_select, but surrounds them with <optgroup> tags.
  19663. #
  19664. # An array of group objects are passed. Each group should return an array of options when calling group_method
  19665. # Each group should return its name when calling group_label_method.
  19666. #
  19667. # html_option_groups_from_collection(@continents, "countries", "continent_name", "country_id", "country_name", @selected_country.id)
  19668. #
  19669. # Could become:
  19670. # <optgroup label="Africa">
  19671. # <select>Egypt</select>
  19672. # <select>Rwanda</select>
  19673. # ...
  19674. # </optgroup>
  19675. # <optgroup label="Asia">
  19676. # <select>China</select>
  19677. # <select>India</select>
  19678. # <select>Japan</select>
  19679. # ...
  19680. # </optgroup>
  19681. #
  19682. # with objects of the following classes:
  19683. # class Continent
  19684. # def initialize(p_name, p_countries) @continent_name = p_name; @countries = p_countries; end
  19685. # def continent_name() @continent_name; end
  19686. # def countries() @countries; end
  19687. # end
  19688. # class Country
  19689. # def initialize(id, name) @id = id; @name = name end
  19690. # def country_id() @id; end
  19691. # def country_name() @name; end
  19692. # end
  19693. #
  19694. # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
  19695. def option_groups_from_collection_for_select(collection, group_method, group_label_method,
  19696. option_key_method, option_value_method, selected_key = nil)
  19697. collection.inject("") do |options_for_select, group|
  19698. group_label_string = eval("group.#{group_label_method}")
  19699. options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
  19700. options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
  19701. options_for_select += '</optgroup>'
  19702. end
  19703. end
  19704. # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
  19705. # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
  19706. # that they will be listed above the rest of the (long) list.
  19707. #
  19708. # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
  19709. def country_options_for_select(selected = nil, priority_countries = nil)
  19710. country_options = ""
  19711. if priority_countries
  19712. country_options += options_for_select(priority_countries, selected)
  19713. country_options += "<option value=\"\">-------------</option>\n"
  19714. end
  19715. if priority_countries && priority_countries.include?(selected)
  19716. country_options += options_for_select(COUNTRIES - priority_countries, selected)
  19717. else
  19718. country_options += options_for_select(COUNTRIES, selected)
  19719. end
  19720. return country_options
  19721. end
  19722. # Returns a string of option tags for pretty much any time zone in the
  19723. # world. Supply a TimeZone name as +selected+ to have it marked as the
  19724. # selected option tag. You can also supply an array of TimeZone objects
  19725. # as +priority_zones+, so that they will be listed above the rest of the
  19726. # (long) list. (You can use TimeZone.us_zones as a convenience for
  19727. # obtaining a list of the US time zones.)
  19728. #
  19729. # The +selected+ parameter must be either +nil+, or a string that names
  19730. # a TimeZone.
  19731. #
  19732. # By default, +model+ is the TimeZone constant (which can be obtained
  19733. # in ActiveRecord as a value object). The only requirement is that the
  19734. # +model+ parameter be an object that responds to #all, and returns
  19735. # an array of objects that represent time zones.
  19736. #
  19737. # NOTE: Only the option tags are returned, you have to wrap this call in
  19738. # a regular HTML select tag.
  19739. def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
  19740. zone_options = ""
  19741. zones = model.all
  19742. convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
  19743. if priority_zones
  19744. zone_options += options_for_select(convert_zones[priority_zones], selected)
  19745. zone_options += "<option value=\"\">-------------</option>\n"
  19746. zones = zones.reject { |z| priority_zones.include?( z ) }
  19747. end
  19748. zone_options += options_for_select(convert_zones[zones], selected)
  19749. zone_options
  19750. end
  19751. private
  19752. # All the countries included in the country_options output.
  19753. COUNTRIES = [ "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla",
  19754. "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia",
  19755. "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus",
  19756. "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina",
  19757. "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory",
  19758. "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cambodia",
  19759. "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic",
  19760. "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia",
  19761. "Comoros", "Congo", "Congo, the Democratic Republic of the", "Cook Islands",
  19762. "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark",
  19763. "Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt",
  19764. "El Salvador", "England", "Equatorial Guinea", "Eritrea", "Espana", "Estonia",
  19765. "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France",
  19766. "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia",
  19767. "Georgia", "Germany", "Ghana", "Gibraltar", "Great Britain", "Greece", "Greenland",
  19768. "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana",
  19769. "Haiti", "Heard and Mc Donald Islands", "Honduras", "Hong Kong", "Hungary", "Iceland",
  19770. "India", "Indonesia", "Ireland", "Israel", "Italy", "Iran", "Iraq", "Jamaica", "Japan", "Jordan",
  19771. "Kazakhstan", "Kenya", "Kiribati", "Korea, Republic of", "Korea (South)", "Kuwait",
  19772. "Kyrgyzstan", "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho",
  19773. "Liberia", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia",
  19774. "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
  19775. "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico",
  19776. "Micronesia, Federated States of", "Moldova, Republic of", "Monaco", "Mongolia",
  19777. "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal",
  19778. "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua",
  19779. "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Ireland",
  19780. "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama",
  19781. "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland",
  19782. "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda",
  19783. "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines",
  19784. "Samoa (Independent)", "San Marino", "Sao Tome and Principe", "Saudi Arabia",
  19785. "Scotland", "Senegal", "Serbia and Montenegro", "Seychelles", "Sierra Leone", "Singapore",
  19786. "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
  19787. "South Georgia and the South Sandwich Islands", "South Korea", "Spain", "Sri Lanka",
  19788. "St. Helena", "St. Pierre and Miquelon", "Suriname", "Svalbard and Jan Mayen Islands",
  19789. "Swaziland", "Sweden", "Switzerland", "Taiwan", "Tajikistan", "Tanzania", "Thailand",
  19790. "Togo", "Tokelau", "Tonga", "Trinidad", "Trinidad and Tobago", "Tunisia", "Turkey",
  19791. "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine",
  19792. "United Arab Emirates", "United Kingdom", "United States",
  19793. "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu",
  19794. "Vatican City State (Holy See)", "Venezuela", "Viet Nam", "Virgin Islands (British)",
  19795. "Virgin Islands (U.S.)", "Wales", "Wallis and Futuna Islands", "Western Sahara",
  19796. "Yemen", "Zambia", "Zimbabwe" ] unless const_defined?("COUNTRIES")
  19797. end
  19798. class InstanceTag #:nodoc:
  19799. include FormOptionsHelper
  19800. def to_select_tag(choices, options, html_options)
  19801. html_options = html_options.stringify_keys
  19802. add_default_name_and_id(html_options)
  19803. selected_value = options.has_key?(:selected) ? options[:selected] : value
  19804. content_tag("select", add_options(options_for_select(choices, selected_value), options, value), html_options)
  19805. end
  19806. def to_collection_select_tag(collection, value_method, text_method, options, html_options)
  19807. html_options = html_options.stringify_keys
  19808. add_default_name_and_id(html_options)
  19809. content_tag(
  19810. "select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
  19811. )
  19812. end
  19813. def to_country_select_tag(priority_countries, options, html_options)
  19814. html_options = html_options.stringify_keys
  19815. add_default_name_and_id(html_options)
  19816. content_tag("select", add_options(country_options_for_select(value, priority_countries), options, value), html_options)
  19817. end
  19818. def to_time_zone_select_tag(priority_zones, options, html_options)
  19819. html_options = html_options.stringify_keys
  19820. add_default_name_and_id(html_options)
  19821. content_tag("select",
  19822. add_options(
  19823. time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone),
  19824. options, value
  19825. ), html_options
  19826. )
  19827. end
  19828. private
  19829. def add_options(option_tags, options, value = nil)
  19830. option_tags = "<option value=\"\"></option>\n" + option_tags if options[:include_blank]
  19831. if value.blank? && options[:prompt]
  19832. ("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : 'Please select'}</option>\n") + option_tags
  19833. else
  19834. option_tags
  19835. end
  19836. end
  19837. end
  19838. class FormBuilder
  19839. def select(method, choices, options = {}, html_options = {})
  19840. @template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
  19841. end
  19842. def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
  19843. @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
  19844. end
  19845. def country_select(method, priority_countries = nil, options = {}, html_options = {})
  19846. @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
  19847. end
  19848. def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
  19849. @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
  19850. end
  19851. end
  19852. end
  19853. end
  19854. require 'cgi'
  19855. require File.dirname(__FILE__) + '/tag_helper'
  19856. module ActionView
  19857. module Helpers
  19858. # Provides a number of methods for creating form tags that doesn't rely on conventions with an object assigned to the template like
  19859. # FormHelper does. With the FormTagHelper, you provide the names and values yourself.
  19860. #
  19861. # NOTE: The html options disabled, readonly, and multiple can all be treated as booleans. So specifying <tt>:disabled => true</tt>
  19862. # will give <tt>disabled="disabled"</tt>.
  19863. module FormTagHelper
  19864. # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
  19865. # ActionController::Base#url_for. The method for the form defaults to POST.
  19866. #
  19867. # Options:
  19868. # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
  19869. # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
  19870. def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc)
  19871. html_options = { "method" => "post" }.merge(options.stringify_keys)
  19872. html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
  19873. html_options["action"] = url_for(url_for_options, *parameters_for_url)
  19874. tag :form, html_options, true
  19875. end
  19876. alias_method :start_form_tag, :form_tag
  19877. # Outputs "</form>"
  19878. def end_form_tag
  19879. "</form>"
  19880. end
  19881. # Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
  19882. # choice selection box.
  19883. #
  19884. # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
  19885. # associated records.
  19886. #
  19887. # <tt>option_tags</tt> is a string containing the option tags for the select box:
  19888. # # Outputs <select id="people" name="people"><option>David</option></select>
  19889. # select_tag "people", "<option>David</option>"
  19890. #
  19891. # Options:
  19892. # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
  19893. def select_tag(name, option_tags = nil, options = {})
  19894. content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys)
  19895. end
  19896. # Creates a standard text field.
  19897. #
  19898. # Options:
  19899. # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
  19900. # * <tt>:size</tt> - The number of visible characters that will fit in the input.
  19901. # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
  19902. #
  19903. # A hash of standard HTML options for the tag.
  19904. def text_field_tag(name, value = nil, options = {})
  19905. tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
  19906. end
  19907. # Creates a hidden field.
  19908. #
  19909. # Takes the same options as text_field_tag
  19910. def hidden_field_tag(name, value = nil, options = {})
  19911. text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
  19912. end
  19913. # Creates a file upload field.
  19914. #
  19915. # If you are using file uploads then you will also need to set the multipart option for the form:
  19916. # <%= form_tag { :action => "post" }, { :multipart => true } %>
  19917. # <label for="file">File to Upload</label> <%= file_field_tag "file" %>
  19918. # <%= submit_tag %>
  19919. # <%= end_form_tag %>
  19920. #
  19921. # The specified URL will then be passed a File object containing the selected file, or if the field
  19922. # was left blank, a StringIO object.
  19923. def file_field_tag(name, options = {})
  19924. text_field_tag(name, nil, options.update("type" => "file"))
  19925. end
  19926. # Creates a password field.
  19927. #
  19928. # Takes the same options as text_field_tag
  19929. def password_field_tag(name = "password", value = nil, options = {})
  19930. text_field_tag(name, value, options.update("type" => "password"))
  19931. end
  19932. # Creates a text input area.
  19933. #
  19934. # Options:
  19935. # * <tt>:size</tt> - A string specifying the dimensions of the textarea.
  19936. # # Outputs <textarea name="body" id="body" cols="25" rows="10"></textarea>
  19937. # <%= text_area_tag "body", nil, :size => "25x10" %>
  19938. def text_area_tag(name, content = nil, options = {})
  19939. options.stringify_keys!
  19940. if size = options.delete("size")
  19941. options["cols"], options["rows"] = size.split("x")
  19942. end
  19943. content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys)
  19944. end
  19945. # Creates a check box.
  19946. def check_box_tag(name, value = "1", checked = false, options = {})
  19947. html_options = { "type" => "checkbox", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
  19948. html_options["checked"] = "checked" if checked
  19949. tag :input, html_options
  19950. end
  19951. # Creates a radio button.
  19952. def radio_button_tag(name, value, checked = false, options = {})
  19953. html_options = { "type" => "radio", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
  19954. html_options["checked"] = "checked" if checked
  19955. tag :input, html_options
  19956. end
  19957. # Creates a submit button with the text <tt>value</tt> as the caption. If options contains a pair with the key of "disable_with",
  19958. # then the value will be used to rename a disabled version of the submit button.
  19959. def submit_tag(value = "Save changes", options = {})
  19960. options.stringify_keys!
  19961. if disable_with = options.delete("disable_with")
  19962. options["onclick"] = "this.disabled=true;this.value='#{disable_with}';this.form.submit();#{options["onclick"]}"
  19963. end
  19964. tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
  19965. end
  19966. # Displays an image which when clicked will submit the form.
  19967. #
  19968. # <tt>source</tt> is passed to AssetTagHelper#image_path
  19969. def image_submit_tag(source, options = {})
  19970. tag :input, { "type" => "image", "src" => image_path(source) }.update(options.stringify_keys)
  19971. end
  19972. end
  19973. end
  19974. end
  19975. require File.dirname(__FILE__) + '/tag_helper'
  19976. module ActionView
  19977. module Helpers
  19978. # Provides a set of helpers for creating JavaScript macros that rely on and often bundle methods from JavaScriptHelper into
  19979. # larger units. These macros also rely on counterparts in the controller that provide them with their backing. The in-place
  19980. # editing relies on ActionController::Base.in_place_edit_for and the autocompletion relies on
  19981. # ActionController::Base.auto_complete_for.
  19982. module JavaScriptMacrosHelper
  19983. # Makes an HTML element specified by the DOM ID +field_id+ become an in-place
  19984. # editor of a property.
  19985. #
  19986. # A form is automatically created and displayed when the user clicks the element,
  19987. # something like this:
  19988. # <form id="myElement-in-place-edit-form" target="specified url">
  19989. # <input name="value" text="The content of myElement"/>
  19990. # <input type="submit" value="ok"/>
  19991. # <a onclick="javascript to cancel the editing">cancel</a>
  19992. # </form>
  19993. #
  19994. # The form is serialized and sent to the server using an AJAX call, the action on
  19995. # the server should process the value and return the updated value in the body of
  19996. # the reponse. The element will automatically be updated with the changed value
  19997. # (as returned from the server).
  19998. #
  19999. # Required +options+ are:
  20000. # <tt>:url</tt>:: Specifies the url where the updated value should
  20001. # be sent after the user presses "ok".
  20002. #
  20003. #
  20004. # Addtional +options+ are:
  20005. # <tt>:rows</tt>:: Number of rows (more than 1 will use a TEXTAREA)
  20006. # <tt>:cols</tt>:: Number of characters the text input should span (works for both INPUT and TEXTAREA)
  20007. # <tt>:size</tt>:: Synonym for :cols when using a single line text input.
  20008. # <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
  20009. # <tt>:save_text</tt>:: The text on the save link. (default: "ok")
  20010. # <tt>:loading_text</tt>:: The text to display when submitting to the server (default: "Saving...")
  20011. # <tt>:external_control</tt>:: The id of an external control used to enter edit mode.
  20012. # <tt>:load_text_url</tt>:: URL where initial value of editor (content) is retrieved.
  20013. # <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
  20014. # <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
  20015. # in the AJAX call, +form+ is an implicit parameter
  20016. # <tt>:script</tt>:: Instructs the in-place editor to evaluate the remote JavaScript response (default: false)
  20017. def in_place_editor(field_id, options = {})
  20018. function = "new Ajax.InPlaceEditor("
  20019. function << "'#{field_id}', "
  20020. function << "'#{url_for(options[:url])}'"
  20021. js_options = {}
  20022. js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text]
  20023. js_options['okText'] = %('#{options[:save_text]}') if options[:save_text]
  20024. js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text]
  20025. js_options['rows'] = options[:rows] if options[:rows]
  20026. js_options['cols'] = options[:cols] if options[:cols]
  20027. js_options['size'] = options[:size] if options[:size]
  20028. js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control]
  20029. js_options['loadTextURL'] = "'#{url_for(options[:load_text_url])}'" if options[:load_text_url]
  20030. js_options['ajaxOptions'] = options[:options] if options[:options]
  20031. js_options['evalScripts'] = options[:script] if options[:script]
  20032. js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with]
  20033. function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
  20034. function << ')'
  20035. javascript_tag(function)
  20036. end
  20037. # Renders the value of the specified object and method with in-place editing capabilities.
  20038. #
  20039. # See the RDoc on ActionController::InPlaceEditing to learn more about this.
  20040. def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {})
  20041. tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
  20042. tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
  20043. in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
  20044. tag.to_content_tag(tag_options.delete(:tag), tag_options) +
  20045. in_place_editor(tag_options[:id], in_place_editor_options)
  20046. end
  20047. # Adds AJAX autocomplete functionality to the text input field with the
  20048. # DOM ID specified by +field_id+.
  20049. #
  20050. # This function expects that the called action returns a HTML <ul> list,
  20051. # or nothing if no entries should be displayed for autocompletion.
  20052. #
  20053. # You'll probably want to turn the browser's built-in autocompletion off,
  20054. # so be sure to include a autocomplete="off" attribute with your text
  20055. # input field.
  20056. #
  20057. # The autocompleter object is assigned to a Javascript variable named <tt>field_id</tt>_auto_completer.
  20058. # This object is useful if you for example want to trigger the auto-complete suggestions through
  20059. # other means than user input (for that specific case, call the <tt>activate</tt> method on that object).
  20060. #
  20061. # Required +options+ are:
  20062. # <tt>:url</tt>:: URL to call for autocompletion results
  20063. # in url_for format.
  20064. #
  20065. # Addtional +options+ are:
  20066. # <tt>:update</tt>:: Specifies the DOM ID of the element whose
  20067. # innerHTML should be updated with the autocomplete
  20068. # entries returned by the AJAX request.
  20069. # Defaults to field_id + '_auto_complete'
  20070. # <tt>:with</tt>:: A JavaScript expression specifying the
  20071. # parameters for the XMLHttpRequest. This defaults
  20072. # to 'fieldname=value'.
  20073. # <tt>:frequency</tt>:: Determines the time to wait after the last keystroke
  20074. # for the AJAX request to be initiated.
  20075. # <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
  20076. # displayed while autocomplete is running.
  20077. # <tt>:tokens</tt>:: A string or an array of strings containing
  20078. # separator tokens for tokenized incremental
  20079. # autocompletion. Example: <tt>:tokens => ','</tt> would
  20080. # allow multiple autocompletion entries, separated
  20081. # by commas.
  20082. # <tt>:min_chars</tt>:: The minimum number of characters that should be
  20083. # in the input field before an Ajax call is made
  20084. # to the server.
  20085. # <tt>:on_hide</tt>:: A Javascript expression that is called when the
  20086. # autocompletion div is hidden. The expression
  20087. # should take two variables: element and update.
  20088. # Element is a DOM element for the field, update
  20089. # is a DOM element for the div from which the
  20090. # innerHTML is replaced.
  20091. # <tt>:on_show</tt>:: Like on_hide, only now the expression is called
  20092. # then the div is shown.
  20093. # <tt>:after_update_element</tt>:: A Javascript expression that is called when the
  20094. # user has selected one of the proposed values.
  20095. # The expression should take two variables: element and value.
  20096. # Element is a DOM element for the field, value
  20097. # is the value selected by the user.
  20098. # <tt>:select</tt>:: Pick the class of the element from which the value for
  20099. # insertion should be extracted. If this is not specified,
  20100. # the entire element is used.
  20101. def auto_complete_field(field_id, options = {})
  20102. function = "var #{field_id}_auto_completer = new Ajax.Autocompleter("
  20103. function << "'#{field_id}', "
  20104. function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
  20105. function << "'#{url_for(options[:url])}'"
  20106. js_options = {}
  20107. js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens]
  20108. js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
  20109. js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
  20110. js_options[:select] = "'#{options[:select]}'" if options[:select]
  20111. js_options[:frequency] = "#{options[:frequency]}" if options[:frequency]
  20112. { :after_update_element => :afterUpdateElement,
  20113. :on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v|
  20114. js_options[v] = options[k] if options[k]
  20115. end
  20116. function << (', ' + options_for_javascript(js_options) + ')')
  20117. javascript_tag(function)
  20118. end
  20119. # Use this method in your view to generate a return for the AJAX autocomplete requests.
  20120. #
  20121. # Example action:
  20122. #
  20123. # def auto_complete_for_item_title
  20124. # @items = Item.find(:all,
  20125. # :conditions => [ 'LOWER(description) LIKE ?',
  20126. # '%' + request.raw_post.downcase + '%' ])
  20127. # render :inline => '<%= auto_complete_result(@items, 'description') %>'
  20128. # end
  20129. #
  20130. # The auto_complete_result can of course also be called from a view belonging to the
  20131. # auto_complete action if you need to decorate it further.
  20132. def auto_complete_result(entries, field, phrase = nil)
  20133. return unless entries
  20134. items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) }
  20135. content_tag("ul", items.uniq)
  20136. end
  20137. # Wrapper for text_field with added AJAX autocompletion functionality.
  20138. #
  20139. # In your controller, you'll need to define an action called
  20140. # auto_complete_for_object_method to respond the AJAX calls,
  20141. #
  20142. # See the RDoc on ActionController::AutoComplete to learn more about this.
  20143. def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})
  20144. (completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
  20145. text_field(object, method, tag_options) +
  20146. content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") +
  20147. auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options))
  20148. end
  20149. private
  20150. def auto_complete_stylesheet
  20151. content_tag("style", <<-EOT
  20152. div.auto_complete {
  20153. width: 350px;
  20154. background: #fff;
  20155. }
  20156. div.auto_complete ul {
  20157. border:1px solid #888;
  20158. margin:0;
  20159. padding:0;
  20160. width:100%;
  20161. list-style-type:none;
  20162. }
  20163. div.auto_complete ul li {
  20164. margin:0;
  20165. padding:3px;
  20166. }
  20167. div.auto_complete ul li.selected {
  20168. background-color: #ffb;
  20169. }
  20170. div.auto_complete ul strong.highlight {
  20171. color: #800;
  20172. margin:0;
  20173. padding:0;
  20174. }
  20175. EOT
  20176. )
  20177. end
  20178. end
  20179. end
  20180. end
  20181. require File.dirname(__FILE__) + '/tag_helper'
  20182. module ActionView
  20183. module Helpers
  20184. # Provides functionality for working with JavaScript in your views.
  20185. #
  20186. # == Ajax, controls and visual effects
  20187. #
  20188. # * For information on using Ajax, see
  20189. # ActionView::Helpers::PrototypeHelper.
  20190. # * For information on using controls and visual effects, see
  20191. # ActionView::Helpers::ScriptaculousHelper.
  20192. #
  20193. # == Including the JavaScript libraries into your pages
  20194. #
  20195. # Rails includes the Prototype JavaScript framework and the Scriptaculous
  20196. # JavaScript controls and visual effects library. If you wish to use
  20197. # these libraries and their helpers (ActionView::Helpers::PrototypeHelper
  20198. # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
  20199. # following:
  20200. #
  20201. # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
  20202. # section of your page (recommended): This function will return
  20203. # references to the JavaScript files created by the +rails+ command in
  20204. # your <tt>public/javascripts</tt> directory. Using it is recommended as
  20205. # the browser can then cache the libraries instead of fetching all the
  20206. # functions anew on every request.
  20207. # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
  20208. # will only include the Prototype core library, which means you are able
  20209. # to use all basic AJAX functionality. For the Scriptaculous-based
  20210. # JavaScript helpers, like visual effects, autocompletion, drag and drop
  20211. # and so on, you should use the method described above.
  20212. # * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
  20213. # JavaScript support functions within a single script block. Not
  20214. # recommended.
  20215. #
  20216. # For documentation on +javascript_include_tag+ see
  20217. # ActionView::Helpers::AssetTagHelper.
  20218. module JavaScriptHelper
  20219. unless const_defined? :JAVASCRIPT_PATH
  20220. JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
  20221. end
  20222. # Returns a link that'll trigger a JavaScript +function+ using the
  20223. # onclick handler and return false after the fact.
  20224. #
  20225. # Examples:
  20226. # link_to_function "Greeting", "alert('Hello world!')"
  20227. # link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }")
  20228. def link_to_function(name, function, html_options = {})
  20229. html_options.symbolize_keys!
  20230. content_tag(
  20231. "a", name,
  20232. html_options.merge({
  20233. :href => html_options[:href] || "#",
  20234. :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
  20235. })
  20236. )
  20237. end
  20238. # Returns a link that'll trigger a JavaScript +function+ using the
  20239. # onclick handler.
  20240. #
  20241. # Examples:
  20242. # button_to_function "Greeting", "alert('Hello world!')"
  20243. # button_to_function "Delete", "if confirm('Really?'){ do_delete(); }")
  20244. def button_to_function(name, function, html_options = {})
  20245. html_options.symbolize_keys!
  20246. tag(:input, html_options.merge({
  20247. :type => "button", :value => name,
  20248. :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
  20249. }))
  20250. end
  20251. # Includes the Action Pack JavaScript libraries inside a single <script>
  20252. # tag. The function first includes prototype.js and then its core extensions,
  20253. # (determined by filenames starting with "prototype").
  20254. # Afterwards, any additional scripts will be included in undefined order.
  20255. #
  20256. # Note: The recommended approach is to copy the contents of
  20257. # lib/action_view/helpers/javascripts/ into your application's
  20258. # public/javascripts/ directory, and use +javascript_include_tag+ to
  20259. # create remote <script> links.
  20260. def define_javascript_functions
  20261. javascript = '<script type="text/javascript">'
  20262. # load prototype.js and its extensions first
  20263. prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
  20264. prototype_libs.each do |filename|
  20265. javascript << "\n" << IO.read(filename)
  20266. end
  20267. # load other librairies
  20268. (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
  20269. javascript << "\n" << IO.read(filename)
  20270. end
  20271. javascript << '</script>'
  20272. end
  20273. # Escape carrier returns and single and double quotes for JavaScript segments.
  20274. def escape_javascript(javascript)
  20275. (javascript || '').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
  20276. end
  20277. # Returns a JavaScript tag with the +content+ inside. Example:
  20278. # javascript_tag "alert('All is good')" # => <script type="text/javascript">alert('All is good')</script>
  20279. def javascript_tag(content)
  20280. content_tag("script", javascript_cdata_section(content), :type => "text/javascript")
  20281. end
  20282. def javascript_cdata_section(content) #:nodoc:
  20283. "\n//#{cdata_section("\n#{content}\n//")}\n"
  20284. end
  20285. protected
  20286. def options_for_javascript(options)
  20287. '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
  20288. end
  20289. def array_or_string_for_javascript(option)
  20290. js_option = if option.kind_of?(Array)
  20291. "['#{option.join('\',\'')}']"
  20292. elsif !option.nil?
  20293. "'#{option}'"
  20294. end
  20295. js_option
  20296. end
  20297. end
  20298. JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
  20299. end
  20300. end
  20301. module ActionView
  20302. module Helpers
  20303. # Provides methods for converting a number into a formatted string that currently represents
  20304. # one of the following forms: phone number, percentage, money, or precision level.
  20305. module NumberHelper
  20306. # Formats a +number+ into a US phone number string. The +options+ can be a hash used to customize the format of the output.
  20307. # The area code can be surrounded by parentheses by setting +:area_code+ to true; default is false
  20308. # The delimiter can be set using +:delimiter+; default is "-"
  20309. # Examples:
  20310. # number_to_phone(1235551234) => 123-555-1234
  20311. # number_to_phone(1235551234, {:area_code => true}) => (123) 555-1234
  20312. # number_to_phone(1235551234, {:delimiter => " "}) => 123 555 1234
  20313. # number_to_phone(1235551234, {:area_code => true, :extension => 555}) => (123) 555-1234 x 555
  20314. def number_to_phone(number, options = {})
  20315. options = options.stringify_keys
  20316. area_code = options.delete("area_code") { false }
  20317. delimiter = options.delete("delimiter") { "-" }
  20318. extension = options.delete("extension") { "" }
  20319. begin
  20320. str = area_code == true ? number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"(\\1) \\2#{delimiter}\\3") : number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"\\1#{delimiter}\\2#{delimiter}\\3")
  20321. extension.to_s.strip.empty? ? str : "#{str} x #{extension.to_s.strip}"
  20322. rescue
  20323. number
  20324. end
  20325. end
  20326. # Formats a +number+ into a currency string. The +options+ hash can be used to customize the format of the output.
  20327. # The +number+ can contain a level of precision using the +precision+ key; default is 2
  20328. # The currency type can be set using the +unit+ key; default is "$"
  20329. # The unit separator can be set using the +separator+ key; default is "."
  20330. # The delimiter can be set using the +delimiter+ key; default is ","
  20331. # Examples:
  20332. # number_to_currency(1234567890.50) => $1,234,567,890.50
  20333. # number_to_currency(1234567890.506) => $1,234,567,890.51
  20334. # number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}) => &pound;1234567890,50
  20335. def number_to_currency(number, options = {})
  20336. options = options.stringify_keys
  20337. precision, unit, separator, delimiter = options.delete("precision") { 2 }, options.delete("unit") { "$" }, options.delete("separator") { "." }, options.delete("delimiter") { "," }
  20338. separator = "" unless precision > 0
  20339. begin
  20340. parts = number_with_precision(number, precision).split('.')
  20341. unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
  20342. rescue
  20343. number
  20344. end
  20345. end
  20346. # Formats a +number+ as into a percentage string. The +options+ hash can be used to customize the format of the output.
  20347. # The +number+ can contain a level of precision using the +precision+ key; default is 3
  20348. # The unit separator can be set using the +separator+ key; default is "."
  20349. # Examples:
  20350. # number_to_percentage(100) => 100.000%
  20351. # number_to_percentage(100, {:precision => 0}) => 100%
  20352. # number_to_percentage(302.0574, {:precision => 2}) => 302.06%
  20353. def number_to_percentage(number, options = {})
  20354. options = options.stringify_keys
  20355. precision, separator = options.delete("precision") { 3 }, options.delete("separator") { "." }
  20356. begin
  20357. number = number_with_precision(number, precision)
  20358. parts = number.split('.')
  20359. if parts.at(1).nil?
  20360. parts[0] + "%"
  20361. else
  20362. parts[0] + separator + parts[1].to_s + "%"
  20363. end
  20364. rescue
  20365. number
  20366. end
  20367. end
  20368. # Formats a +number+ with a +delimiter+.
  20369. # Example:
  20370. # number_with_delimiter(12345678) => 12,345,678
  20371. def number_with_delimiter(number, delimiter=",")
  20372. number.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
  20373. end
  20374. # Returns a formatted-for-humans file size.
  20375. #
  20376. # Examples:
  20377. # human_size(123) => 123 Bytes
  20378. # human_size(1234) => 1.2 KB
  20379. # human_size(12345) => 12.1 KB
  20380. # human_size(1234567) => 1.2 MB
  20381. # human_size(1234567890) => 1.1 GB
  20382. def number_to_human_size(size)
  20383. case
  20384. when size < 1.kilobyte: '%d Bytes' % size
  20385. when size < 1.megabyte: '%.1f KB' % (size / 1.0.kilobyte)
  20386. when size < 1.gigabyte: '%.1f MB' % (size / 1.0.megabyte)
  20387. when size < 1.terabyte: '%.1f GB' % (size / 1.0.gigabyte)
  20388. else '%.1f TB' % (size / 1.0.terabyte)
  20389. end.sub('.0', '')
  20390. rescue
  20391. nil
  20392. end
  20393. alias_method :human_size, :number_to_human_size # deprecated alias
  20394. # Formats a +number+ with a level of +precision+.
  20395. # Example:
  20396. # number_with_precision(111.2345) => 111.235
  20397. def number_with_precision(number, precision=3)
  20398. sprintf("%01.#{precision}f", number)
  20399. end
  20400. end
  20401. end
  20402. end
  20403. module ActionView
  20404. module Helpers
  20405. # Provides methods for linking to ActionController::Pagination objects.
  20406. #
  20407. # You can also build your links manually, like in this example:
  20408. #
  20409. # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %>
  20410. #
  20411. # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %>
  20412. module PaginationHelper
  20413. unless const_defined?(:DEFAULT_OPTIONS)
  20414. DEFAULT_OPTIONS = {
  20415. :name => :page,
  20416. :window_size => 2,
  20417. :always_show_anchors => true,
  20418. :link_to_current_page => false,
  20419. :params => {}
  20420. }
  20421. end
  20422. # Creates a basic HTML link bar for the given +paginator+.
  20423. # +html_options+ are passed to +link_to+.
  20424. #
  20425. # +options+ are:
  20426. # <tt>:name</tt>:: the routing name for this paginator
  20427. # (defaults to +page+)
  20428. # <tt>:window_size</tt>:: the number of pages to show around
  20429. # the current page (defaults to +2+)
  20430. # <tt>:always_show_anchors</tt>:: whether or not the first and last
  20431. # pages should always be shown
  20432. # (defaults to +true+)
  20433. # <tt>:link_to_current_page</tt>:: whether or not the current page
  20434. # should be linked to (defaults to
  20435. # +false+)
  20436. # <tt>:params</tt>:: any additional routing parameters
  20437. # for page URLs
  20438. def pagination_links(paginator, options={}, html_options={})
  20439. name = options[:name] || DEFAULT_OPTIONS[:name]
  20440. params = (options[:params] || DEFAULT_OPTIONS[:params]).clone
  20441. pagination_links_each(paginator, options) do |n|
  20442. params[name] = n
  20443. link_to(n.to_s, params, html_options)
  20444. end
  20445. end
  20446. # Iterate through the pages of a given +paginator+, invoking a
  20447. # block for each page number that needs to be rendered as a link.
  20448. def pagination_links_each(paginator, options)
  20449. options = DEFAULT_OPTIONS.merge(options)
  20450. link_to_current_page = options[:link_to_current_page]
  20451. always_show_anchors = options[:always_show_anchors]
  20452. current_page = paginator.current_page
  20453. window_pages = current_page.window(options[:window_size]).pages
  20454. return if window_pages.length <= 1 unless link_to_current_page
  20455. first, last = paginator.first, paginator.last
  20456. html = ''
  20457. if always_show_anchors and not (wp_first = window_pages[0]).first?
  20458. html << yield(first.number)
  20459. html << ' ... ' if wp_first.number - first.number > 1
  20460. html << ' '
  20461. end
  20462. window_pages.each do |page|
  20463. if current_page == page && !link_to_current_page
  20464. html << page.number.to_s
  20465. else
  20466. html << yield(page.number)
  20467. end
  20468. html << ' '
  20469. end
  20470. if always_show_anchors and not (wp_last = window_pages[-1]).last?
  20471. html << ' ... ' if last.number - wp_last.number > 1
  20472. html << yield(last.number)
  20473. end
  20474. html
  20475. end
  20476. end # PaginationHelper
  20477. end # Helpers
  20478. end # ActionView
  20479. require File.dirname(__FILE__) + '/javascript_helper'
  20480. require 'set'
  20481. module ActionView
  20482. module Helpers
  20483. # Provides a set of helpers for calling Prototype JavaScript functions,
  20484. # including functionality to call remote methods using
  20485. # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php].
  20486. # This means that you can call actions in your controllers without
  20487. # reloading the page, but still update certain parts of it using
  20488. # injections into the DOM. The common use case is having a form that adds
  20489. # a new element to a list without reloading the page.
  20490. #
  20491. # To be able to use these helpers, you must include the Prototype
  20492. # JavaScript framework in your pages. See the documentation for
  20493. # ActionView::Helpers::JavaScriptHelper for more information on including
  20494. # the necessary JavaScript.
  20495. #
  20496. # See link_to_remote for documentation of options common to all Ajax
  20497. # helpers.
  20498. #
  20499. # See also ActionView::Helpers::ScriptaculousHelper for helpers which work
  20500. # with the Scriptaculous controls and visual effects library.
  20501. #
  20502. # See JavaScriptGenerator for information on updating multiple elements
  20503. # on the page in an Ajax response.
  20504. module PrototypeHelper
  20505. unless const_defined? :CALLBACKS
  20506. CALLBACKS = Set.new([ :uninitialized, :loading, :loaded,
  20507. :interactive, :complete, :failure, :success ] +
  20508. (100..599).to_a)
  20509. AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
  20510. :asynchronous, :method, :insertion, :position,
  20511. :form, :with, :update, :script ]).merge(CALLBACKS)
  20512. end
  20513. # Returns a link to a remote action defined by <tt>options[:url]</tt>
  20514. # (using the url_for format) that's called in the background using
  20515. # XMLHttpRequest. The result of that request can then be inserted into a
  20516. # DOM object whose id can be specified with <tt>options[:update]</tt>.
  20517. # Usually, the result would be a partial prepared by the controller with
  20518. # either render_partial or render_partial_collection.
  20519. #
  20520. # Examples:
  20521. # link_to_remote "Delete this post", :update => "posts",
  20522. # :url => { :action => "destroy", :id => post.id }
  20523. # link_to_remote(image_tag("refresh"), :update => "emails",
  20524. # :url => { :action => "list_emails" })
  20525. #
  20526. # You can also specify a hash for <tt>options[:update]</tt> to allow for
  20527. # easy redirection of output to an other DOM element if a server-side
  20528. # error occurs:
  20529. #
  20530. # Example:
  20531. # link_to_remote "Delete this post",
  20532. # :url => { :action => "destroy", :id => post.id },
  20533. # :update => { :success => "posts", :failure => "error" }
  20534. #
  20535. # Optionally, you can use the <tt>options[:position]</tt> parameter to
  20536. # influence how the target DOM element is updated. It must be one of
  20537. # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
  20538. #
  20539. # By default, these remote requests are processed asynchronous during
  20540. # which various JavaScript callbacks can be triggered (for progress
  20541. # indicators and the likes). All callbacks get access to the
  20542. # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
  20543. #
  20544. # To access the server response, use <tt>request.responseText</tt>, to
  20545. # find out the HTTP status, use <tt>request.status</tt>.
  20546. #
  20547. # Example:
  20548. # link_to_remote word,
  20549. # :url => { :action => "undo", :n => word_counter },
  20550. # :complete => "undoRequestCompleted(request)"
  20551. #
  20552. # The callbacks that may be specified are (in order):
  20553. #
  20554. # <tt>:loading</tt>:: Called when the remote document is being
  20555. # loaded with data by the browser.
  20556. # <tt>:loaded</tt>:: Called when the browser has finished loading
  20557. # the remote document.
  20558. # <tt>:interactive</tt>:: Called when the user can interact with the
  20559. # remote document, even though it has not
  20560. # finished loading.
  20561. # <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
  20562. # and the HTTP status code is in the 2XX range.
  20563. # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
  20564. # and the HTTP status code is not in the 2XX
  20565. # range.
  20566. # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
  20567. # (fires after success/failure if they are
  20568. # present).
  20569. #
  20570. # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
  20571. # adding additional callbacks for specific status codes.
  20572. #
  20573. # Example:
  20574. # link_to_remote word,
  20575. # :url => { :action => "action" },
  20576. # 404 => "alert('Not found...? Wrong URL...?')",
  20577. # :failure => "alert('HTTP Error ' + request.status + '!')"
  20578. #
  20579. # A status code callback overrides the success/failure handlers if
  20580. # present.
  20581. #
  20582. # If you for some reason or another need synchronous processing (that'll
  20583. # block the browser while the request is happening), you can specify
  20584. # <tt>options[:type] = :synchronous</tt>.
  20585. #
  20586. # You can customize further browser side call logic by passing in
  20587. # JavaScript code snippets via some optional parameters. In their order
  20588. # of use these are:
  20589. #
  20590. # <tt>:confirm</tt>:: Adds confirmation dialog.
  20591. # <tt>:condition</tt>:: Perform remote request conditionally
  20592. # by this expression. Use this to
  20593. # describe browser-side conditions when
  20594. # request should not be initiated.
  20595. # <tt>:before</tt>:: Called before request is initiated.
  20596. # <tt>:after</tt>:: Called immediately after request was
  20597. # initiated and before <tt>:loading</tt>.
  20598. # <tt>:submit</tt>:: Specifies the DOM element ID that's used
  20599. # as the parent of the form elements. By
  20600. # default this is the current form, but
  20601. # it could just as well be the ID of a
  20602. # table row or any other DOM element.
  20603. def link_to_remote(name, options = {}, html_options = {})
  20604. link_to_function(name, remote_function(options), html_options)
  20605. end
  20606. # Periodically calls the specified url (<tt>options[:url]</tt>) every
  20607. # <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
  20608. # update a specified div (<tt>options[:update]</tt>) with the results
  20609. # of the remote call. The options for specifying the target with :url
  20610. # and defining callbacks is the same as link_to_remote.
  20611. def periodically_call_remote(options = {})
  20612. frequency = options[:frequency] || 10 # every ten seconds by default
  20613. code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
  20614. javascript_tag(code)
  20615. end
  20616. # Returns a form tag that will submit using XMLHttpRequest in the
  20617. # background instead of the regular reloading POST arrangement. Even
  20618. # though it's using JavaScript to serialize the form elements, the form
  20619. # submission will work just like a regular submission as viewed by the
  20620. # receiving side (all elements available in <tt>params</tt>). The options for
  20621. # specifying the target with :url and defining callbacks is the same as
  20622. # link_to_remote.
  20623. #
  20624. # A "fall-through" target for browsers that doesn't do JavaScript can be
  20625. # specified with the :action/:method options on :html.
  20626. #
  20627. # Example:
  20628. # form_remote_tag :html => { :action =>
  20629. # url_for(:controller => "some", :action => "place") }
  20630. #
  20631. # The Hash passed to the :html key is equivalent to the options (2nd)
  20632. # argument in the FormTagHelper.form_tag method.
  20633. #
  20634. # By default the fall-through action is the same as the one specified in
  20635. # the :url (and the default method is :post).
  20636. def form_remote_tag(options = {})
  20637. options[:form] = true
  20638. options[:html] ||= {}
  20639. options[:html][:onsubmit] = "#{remote_function(options)}; return false;"
  20640. options[:html][:action] = options[:html][:action] || url_for(options[:url])
  20641. options[:html][:method] = options[:html][:method] || "post"
  20642. tag("form", options[:html], true)
  20643. end
  20644. # Works like form_remote_tag, but uses form_for semantics.
  20645. def remote_form_for(object_name, *args, &proc)
  20646. options = args.last.is_a?(Hash) ? args.pop : {}
  20647. concat(form_remote_tag(options), proc.binding)
  20648. fields_for(object_name, *(args << options), &proc)
  20649. concat('</form>', proc.binding)
  20650. end
  20651. alias_method :form_remote_for, :remote_form_for
  20652. # Returns a button input tag that will submit form using XMLHttpRequest
  20653. # in the background instead of regular reloading POST arrangement.
  20654. # <tt>options</tt> argument is the same as in <tt>form_remote_tag</tt>.
  20655. def submit_to_remote(name, value, options = {})
  20656. options[:with] ||= 'Form.serialize(this.form)'
  20657. options[:html] ||= {}
  20658. options[:html][:type] = 'button'
  20659. options[:html][:onclick] = "#{remote_function(options)}; return false;"
  20660. options[:html][:name] = name
  20661. options[:html][:value] = value
  20662. tag("input", options[:html], false)
  20663. end
  20664. # Returns a JavaScript function (or expression) that'll update a DOM
  20665. # element according to the options passed.
  20666. #
  20667. # * <tt>:content</tt>: The content to use for updating. Can be left out
  20668. # if using block, see example.
  20669. # * <tt>:action</tt>: Valid options are :update (assumed by default),
  20670. # :empty, :remove
  20671. # * <tt>:position</tt> If the :action is :update, you can optionally
  20672. # specify one of the following positions: :before, :top, :bottom,
  20673. # :after.
  20674. #
  20675. # Examples:
  20676. # <%= javascript_tag(update_element_function("products",
  20677. # :position => :bottom, :content => "<p>New product!</p>")) %>
  20678. #
  20679. # <% replacement_function = update_element_function("products") do %>
  20680. # <p>Product 1</p>
  20681. # <p>Product 2</p>
  20682. # <% end %>
  20683. # <%= javascript_tag(replacement_function) %>
  20684. #
  20685. # This method can also be used in combination with remote method call
  20686. # where the result is evaluated afterwards to cause multiple updates on
  20687. # a page. Example:
  20688. #
  20689. # # Calling view
  20690. # <%= form_remote_tag :url => { :action => "buy" },
  20691. # :complete => evaluate_remote_response %>
  20692. # all the inputs here...
  20693. #
  20694. # # Controller action
  20695. # def buy
  20696. # @product = Product.find(1)
  20697. # end
  20698. #
  20699. # # Returning view
  20700. # <%= update_element_function(
  20701. # "cart", :action => :update, :position => :bottom,
  20702. # :content => "<p>New Product: #{@product.name}</p>")) %>
  20703. # <% update_element_function("status", :binding => binding) do %>
  20704. # You've bought a new product!
  20705. # <% end %>
  20706. #
  20707. # Notice how the second call doesn't need to be in an ERb output block
  20708. # since it uses a block and passes in the binding to render directly.
  20709. # This trick will however only work in ERb (not Builder or other
  20710. # template forms).
  20711. #
  20712. # See also JavaScriptGenerator and update_page.
  20713. def update_element_function(element_id, options = {}, &block)
  20714. content = escape_javascript(options[:content] || '')
  20715. content = escape_javascript(capture(&block)) if block
  20716. javascript_function = case (options[:action] || :update)
  20717. when :update
  20718. if options[:position]
  20719. "new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
  20720. else
  20721. "$('#{element_id}').innerHTML = '#{content}'"
  20722. end
  20723. when :empty
  20724. "$('#{element_id}').innerHTML = ''"
  20725. when :remove
  20726. "Element.remove('#{element_id}')"
  20727. else
  20728. raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
  20729. end
  20730. javascript_function << ";\n"
  20731. options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
  20732. end
  20733. # Returns 'eval(request.responseText)' which is the JavaScript function
  20734. # that form_remote_tag can call in :complete to evaluate a multiple
  20735. # update return document using update_element_function calls.
  20736. def evaluate_remote_response
  20737. "eval(request.responseText)"
  20738. end
  20739. # Returns the JavaScript needed for a remote function.
  20740. # Takes the same arguments as link_to_remote.
  20741. #
  20742. # Example:
  20743. # <select id="options" onchange="<%= remote_function(:update => "options",
  20744. # :url => { :action => :update_options }) %>">
  20745. # <option value="0">Hello</option>
  20746. # <option value="1">World</option>
  20747. # </select>
  20748. def remote_function(options)
  20749. javascript_options = options_for_ajax(options)
  20750. update = ''
  20751. if options[:update] and options[:update].is_a?Hash
  20752. update = []
  20753. update << "success:'#{options[:update][:success]}'" if options[:update][:success]
  20754. update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
  20755. update = '{' + update.join(',') + '}'
  20756. elsif options[:update]
  20757. update << "'#{options[:update]}'"
  20758. end
  20759. function = update.empty? ?
  20760. "new Ajax.Request(" :
  20761. "new Ajax.Updater(#{update}, "
  20762. url_options = options[:url]
  20763. url_options = url_options.merge(:escape => false) if url_options.is_a? Hash
  20764. function << "'#{url_for(url_options)}'"
  20765. function << ", #{javascript_options})"
  20766. function = "#{options[:before]}; #{function}" if options[:before]
  20767. function = "#{function}; #{options[:after]}" if options[:after]
  20768. function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
  20769. function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
  20770. return function
  20771. end
  20772. # Observes the field with the DOM ID specified by +field_id+ and makes
  20773. # an Ajax call when its contents have changed.
  20774. #
  20775. # Required +options+ are either of:
  20776. # <tt>:url</tt>:: +url_for+-style options for the action to call
  20777. # when the field has changed.
  20778. # <tt>:function</tt>:: Instead of making a remote call to a URL, you
  20779. # can specify a function to be called instead.
  20780. #
  20781. # Additional options are:
  20782. # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
  20783. # this field will be detected. Not setting this
  20784. # option at all or to a value equal to or less than
  20785. # zero will use event based observation instead of
  20786. # time based observation.
  20787. # <tt>:update</tt>:: Specifies the DOM ID of the element whose
  20788. # innerHTML should be updated with the
  20789. # XMLHttpRequest response text.
  20790. # <tt>:with</tt>:: A JavaScript expression specifying the
  20791. # parameters for the XMLHttpRequest. This defaults
  20792. # to 'value', which in the evaluated context
  20793. # refers to the new field value. If you specify a
  20794. # string without a "=", it'll be extended to mean
  20795. # the form key that the value should be assigned to.
  20796. # So :with => "term" gives "'term'=value". If a "=" is
  20797. # present, no extension will happen.
  20798. # <tt>:on</tt>:: Specifies which event handler to observe. By default,
  20799. # it's set to "changed" for text fields and areas and
  20800. # "click" for radio buttons and checkboxes. With this,
  20801. # you can specify it instead to be "blur" or "focus" or
  20802. # any other event.
  20803. #
  20804. # Additionally, you may specify any of the options documented in
  20805. # link_to_remote.
  20806. def observe_field(field_id, options = {})
  20807. if options[:frequency] && options[:frequency] > 0
  20808. build_observer('Form.Element.Observer', field_id, options)
  20809. else
  20810. build_observer('Form.Element.EventObserver', field_id, options)
  20811. end
  20812. end
  20813. # Like +observe_field+, but operates on an entire form identified by the
  20814. # DOM ID +form_id+. +options+ are the same as +observe_field+, except
  20815. # the default value of the <tt>:with</tt> option evaluates to the
  20816. # serialized (request string) value of the form.
  20817. def observe_form(form_id, options = {})
  20818. if options[:frequency]
  20819. build_observer('Form.Observer', form_id, options)
  20820. else
  20821. build_observer('Form.EventObserver', form_id, options)
  20822. end
  20823. end
  20824. # All the methods were moved to GeneratorMethods so that
  20825. # #include_helpers_from_context has nothing to overwrite.
  20826. class JavaScriptGenerator #:nodoc:
  20827. def initialize(context, &block) #:nodoc:
  20828. @context, @lines = context, []
  20829. include_helpers_from_context
  20830. @context.instance_exec(self, &block)
  20831. end
  20832. private
  20833. def include_helpers_from_context
  20834. @context.extended_by.each do |mod|
  20835. extend mod unless mod.name =~ /^ActionView::Helpers/
  20836. end
  20837. extend GeneratorMethods
  20838. end
  20839. # JavaScriptGenerator generates blocks of JavaScript code that allow you
  20840. # to change the content and presentation of multiple DOM elements. Use
  20841. # this in your Ajax response bodies, either in a <script> tag or as plain
  20842. # JavaScript sent with a Content-type of "text/javascript".
  20843. #
  20844. # Create new instances with PrototypeHelper#update_page or with
  20845. # ActionController::Base#render, then call #insert_html, #replace_html,
  20846. # #remove, #show, #hide, #visual_effect, or any other of the built-in
  20847. # methods on the yielded generator in any order you like to modify the
  20848. # content and appearance of the current page.
  20849. #
  20850. # Example:
  20851. #
  20852. # update_page do |page|
  20853. # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
  20854. # page.visual_effect :highlight, 'list'
  20855. # page.hide 'status-indicator', 'cancel-link'
  20856. # end
  20857. #
  20858. # generates the following JavaScript:
  20859. #
  20860. # new Insertion.Bottom("list", "<li>Some item</li>");
  20861. # new Effect.Highlight("list");
  20862. # ["status-indicator", "cancel-link"].each(Element.hide);
  20863. #
  20864. # Helper methods can be used in conjunction with JavaScriptGenerator.
  20865. # When a helper method is called inside an update block on the +page+
  20866. # object, that method will also have access to a +page+ object.
  20867. #
  20868. # Example:
  20869. #
  20870. # module ApplicationHelper
  20871. # def update_time
  20872. # page.replace_html 'time', Time.now.to_s(:db)
  20873. # page.visual_effect :highlight, 'time'
  20874. # end
  20875. # end
  20876. #
  20877. # # Controller action
  20878. # def poll
  20879. # render(:update) { |page| page.update_time }
  20880. # end
  20881. #
  20882. # You can also use PrototypeHelper#update_page_tag instead of
  20883. # PrototypeHelper#update_page to wrap the generated JavaScript in a
  20884. # <script> tag.
  20885. module GeneratorMethods
  20886. def to_s #:nodoc:
  20887. returning javascript = @lines * $/ do
  20888. if ActionView::Base.debug_rjs
  20889. source = javascript.dup
  20890. javascript.replace "try {\n#{source}\n} catch (e) "
  20891. javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
  20892. end
  20893. end
  20894. end
  20895. # Returns a element reference by finding it through +id+ in the DOM. This element can then be
  20896. # used for further method calls. Examples:
  20897. #
  20898. # page['blank_slate'] # => $('blank_slate');
  20899. # page['blank_slate'].show # => $('blank_slate').show();
  20900. # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
  20901. def [](id)
  20902. JavaScriptElementProxy.new(self, id)
  20903. end
  20904. # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
  20905. # used for further method calls. Examples:
  20906. #
  20907. # page.select('p') # => $$('p');
  20908. # page.select('p.welcome b').first # => $$('p.welcome b').first();
  20909. # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
  20910. #
  20911. # You can also use prototype enumerations with the collection. Observe:
  20912. #
  20913. # page.select('#items li').each do |value|
  20914. # value.hide
  20915. # end
  20916. # # => $$('#items li').each(function(value) { value.hide(); });
  20917. #
  20918. # Though you can call the block param anything you want, they are always rendered in the
  20919. # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
  20920. #
  20921. # page.select('#items li').collect('hidden') do |item|
  20922. # item.hide
  20923. # end
  20924. # # => var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
  20925. def select(pattern)
  20926. JavaScriptElementCollectionProxy.new(self, pattern)
  20927. end
  20928. # Inserts HTML at the specified +position+ relative to the DOM element
  20929. # identified by the given +id+.
  20930. #
  20931. # +position+ may be one of:
  20932. #
  20933. # <tt>:top</tt>:: HTML is inserted inside the element, before the
  20934. # element's existing content.
  20935. # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
  20936. # element's existing content.
  20937. # <tt>:before</tt>:: HTML is inserted immediately preceeding the element.
  20938. # <tt>:after</tt>:: HTML is inserted immediately following the element.
  20939. #
  20940. # +options_for_render+ may be either a string of HTML to insert, or a hash
  20941. # of options to be passed to ActionView::Base#render. For example:
  20942. #
  20943. # # Insert the rendered 'navigation' partial just before the DOM
  20944. # # element with ID 'content'.
  20945. # insert_html :before, 'content', :partial => 'navigation'
  20946. #
  20947. # # Add a list item to the bottom of the <ul> with ID 'list'.
  20948. # insert_html :bottom, 'list', '<li>Last item</li>'
  20949. #
  20950. def insert_html(position, id, *options_for_render)
  20951. insertion = position.to_s.camelize
  20952. call "new Insertion.#{insertion}", id, render(*options_for_render)
  20953. end
  20954. # Replaces the inner HTML of the DOM element with the given +id+.
  20955. #
  20956. # +options_for_render+ may be either a string of HTML to insert, or a hash
  20957. # of options to be passed to ActionView::Base#render. For example:
  20958. #
  20959. # # Replace the HTML of the DOM element having ID 'person-45' with the
  20960. # # 'person' partial for the appropriate object.
  20961. # replace_html 'person-45', :partial => 'person', :object => @person
  20962. #
  20963. def replace_html(id, *options_for_render)
  20964. call 'Element.update', id, render(*options_for_render)
  20965. end
  20966. # Replaces the "outer HTML" (i.e., the entire element, not just its
  20967. # contents) of the DOM element with the given +id+.
  20968. #
  20969. # +options_for_render+ may be either a string of HTML to insert, or a hash
  20970. # of options to be passed to ActionView::Base#render. For example:
  20971. #
  20972. # # Replace the DOM element having ID 'person-45' with the
  20973. # # 'person' partial for the appropriate object.
  20974. # replace_html 'person-45', :partial => 'person', :object => @person
  20975. #
  20976. # This allows the same partial that is used for the +insert_html+ to
  20977. # be also used for the input to +replace+ without resorting to
  20978. # the use of wrapper elements.
  20979. #
  20980. # Examples:
  20981. #
  20982. # <div id="people">
  20983. # <%= render :partial => 'person', :collection => @people %>
  20984. # </div>
  20985. #
  20986. # # Insert a new person
  20987. # page.insert_html :bottom, :partial => 'person', :object => @person
  20988. #
  20989. # # Replace an existing person
  20990. # page.replace 'person_45', :partial => 'person', :object => @person
  20991. #
  20992. def replace(id, *options_for_render)
  20993. call 'Element.replace', id, render(*options_for_render)
  20994. end
  20995. # Removes the DOM elements with the given +ids+ from the page.
  20996. def remove(*ids)
  20997. record "#{javascript_object_for(ids)}.each(Element.remove)"
  20998. end
  20999. # Shows hidden DOM elements with the given +ids+.
  21000. def show(*ids)
  21001. call 'Element.show', *ids
  21002. end
  21003. # Hides the visible DOM elements with the given +ids+.
  21004. def hide(*ids)
  21005. call 'Element.hide', *ids
  21006. end
  21007. # Toggles the visibility of the DOM elements with the given +ids+.
  21008. def toggle(*ids)
  21009. call 'Element.toggle', *ids
  21010. end
  21011. # Displays an alert dialog with the given +message+.
  21012. def alert(message)
  21013. call 'alert', message
  21014. end
  21015. # Redirects the browser to the given +location+, in the same form as
  21016. # +url_for+.
  21017. def redirect_to(location)
  21018. assign 'window.location.href', @context.url_for(location)
  21019. end
  21020. # Calls the JavaScript +function+, optionally with the given
  21021. # +arguments+.
  21022. def call(function, *arguments)
  21023. record "#{function}(#{arguments_for_call(arguments)})"
  21024. end
  21025. # Assigns the JavaScript +variable+ the given +value+.
  21026. def assign(variable, value)
  21027. record "#{variable} = #{javascript_object_for(value)}"
  21028. end
  21029. # Writes raw JavaScript to the page.
  21030. def <<(javascript)
  21031. @lines << javascript
  21032. end
  21033. # Executes the content of the block after a delay of +seconds+. Example:
  21034. #
  21035. # page.delay(20) do
  21036. # page.visual_effect :fade, 'notice'
  21037. # end
  21038. def delay(seconds = 1)
  21039. record "setTimeout(function() {\n\n"
  21040. yield
  21041. record "}, #{(seconds * 1000).to_i})"
  21042. end
  21043. # Starts a script.aculo.us visual effect. See
  21044. # ActionView::Helpers::ScriptaculousHelper for more information.
  21045. def visual_effect(name, id = nil, options = {})
  21046. record @context.send(:visual_effect, name, id, options)
  21047. end
  21048. # Creates a script.aculo.us sortable element. Useful
  21049. # to recreate sortable elements after items get added
  21050. # or deleted.
  21051. # See ActionView::Helpers::ScriptaculousHelper for more information.
  21052. def sortable(id, options = {})
  21053. record @context.send(:sortable_element_js, id, options)
  21054. end
  21055. # Creates a script.aculo.us draggable element.
  21056. # See ActionView::Helpers::ScriptaculousHelper for more information.
  21057. def draggable(id, options = {})
  21058. record @context.send(:draggable_element_js, id, options)
  21059. end
  21060. # Creates a script.aculo.us drop receiving element.
  21061. # See ActionView::Helpers::ScriptaculousHelper for more information.
  21062. def drop_receiving(id, options = {})
  21063. record @context.send(:drop_receiving_element_js, id, options)
  21064. end
  21065. private
  21066. def page
  21067. self
  21068. end
  21069. def record(line)
  21070. returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do
  21071. self << line
  21072. end
  21073. end
  21074. def render(*options_for_render)
  21075. Hash === options_for_render.first ?
  21076. @context.render(*options_for_render) :
  21077. options_for_render.first.to_s
  21078. end
  21079. def javascript_object_for(object)
  21080. object.respond_to?(:to_json) ? object.to_json : object.inspect
  21081. end
  21082. def arguments_for_call(arguments)
  21083. arguments.map { |argument| javascript_object_for(argument) }.join ', '
  21084. end
  21085. def method_missing(method, *arguments)
  21086. JavaScriptProxy.new(self, method.to_s.camelize)
  21087. end
  21088. end
  21089. end
  21090. # Yields a JavaScriptGenerator and returns the generated JavaScript code.
  21091. # Use this to update multiple elements on a page in an Ajax response.
  21092. # See JavaScriptGenerator for more information.
  21093. def update_page(&block)
  21094. JavaScriptGenerator.new(@template, &block).to_s
  21095. end
  21096. # Works like update_page but wraps the generated JavaScript in a <script>
  21097. # tag. Use this to include generated JavaScript in an ERb template.
  21098. # See JavaScriptGenerator for more information.
  21099. def update_page_tag(&block)
  21100. javascript_tag update_page(&block)
  21101. end
  21102. protected
  21103. def options_for_ajax(options)
  21104. js_options = build_callbacks(options)
  21105. js_options['asynchronous'] = options[:type] != :synchronous
  21106. js_options['method'] = method_option_to_s(options[:method]) if options[:method]
  21107. js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
  21108. js_options['evalScripts'] = options[:script].nil? || options[:script]
  21109. if options[:form]
  21110. js_options['parameters'] = 'Form.serialize(this)'
  21111. elsif options[:submit]
  21112. js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
  21113. elsif options[:with]
  21114. js_options['parameters'] = options[:with]
  21115. end
  21116. options_for_javascript(js_options)
  21117. end
  21118. def method_option_to_s(method)
  21119. (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
  21120. end
  21121. def build_observer(klass, name, options = {})
  21122. if options[:with] && !options[:with].include?("=")
  21123. options[:with] = "'#{options[:with]}=' + value"
  21124. else
  21125. options[:with] ||= 'value' if options[:update]
  21126. end
  21127. callback = options[:function] || remote_function(options)
  21128. javascript = "new #{klass}('#{name}', "
  21129. javascript << "#{options[:frequency]}, " if options[:frequency]
  21130. javascript << "function(element, value) {"
  21131. javascript << "#{callback}}"
  21132. javascript << ", '#{options[:on]}'" if options[:on]
  21133. javascript << ")"
  21134. javascript_tag(javascript)
  21135. end
  21136. def build_callbacks(options)
  21137. callbacks = {}
  21138. options.each do |callback, code|
  21139. if CALLBACKS.include?(callback)
  21140. name = 'on' + callback.to_s.capitalize
  21141. callbacks[name] = "function(request){#{code}}"
  21142. end
  21143. end
  21144. callbacks
  21145. end
  21146. end
  21147. # Converts chained method calls on DOM proxy elements into JavaScript chains
  21148. class JavaScriptProxy < Builder::BlankSlate #:nodoc:
  21149. def initialize(generator, root = nil)
  21150. @generator = generator
  21151. @generator << root if root
  21152. end
  21153. private
  21154. def method_missing(method, *arguments)
  21155. if method.to_s =~ /(.*)=$/
  21156. assign($1, arguments.first)
  21157. else
  21158. call("#{method.to_s.camelize(:lower)}", *arguments)
  21159. end
  21160. end
  21161. def call(function, *arguments)
  21162. append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments)})")
  21163. self
  21164. end
  21165. def assign(variable, value)
  21166. append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
  21167. end
  21168. def function_chain
  21169. @function_chain ||= @generator.instance_variable_get("@lines")
  21170. end
  21171. def append_to_function_chain!(call)
  21172. function_chain[-1].chomp!(';')
  21173. function_chain[-1] += ".#{call};"
  21174. end
  21175. end
  21176. class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
  21177. def initialize(generator, id)
  21178. @id = id
  21179. super(generator, "$(#{id.to_json})")
  21180. end
  21181. def replace_html(*options_for_render)
  21182. call 'update', @generator.send(:render, *options_for_render)
  21183. end
  21184. def replace(*options_for_render)
  21185. call 'replace', @generator.send(:render, *options_for_render)
  21186. end
  21187. def reload
  21188. replace :partial => @id.to_s
  21189. end
  21190. end
  21191. class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
  21192. def initialize(generator, variable)
  21193. @variable = variable
  21194. @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
  21195. super(generator)
  21196. end
  21197. # The JSON Encoder calls this to check for the #to_json method
  21198. # Since it's a blank slate object, I suppose it responds to anything.
  21199. def respond_to?(method)
  21200. true
  21201. end
  21202. def to_json
  21203. @variable
  21204. end
  21205. private
  21206. def append_to_function_chain!(call)
  21207. @generator << @variable if @empty
  21208. @empty = false
  21209. super
  21210. end
  21211. end
  21212. class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
  21213. ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by]
  21214. ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each]
  21215. attr_reader :generator
  21216. delegate :arguments_for_call, :to => :generator
  21217. def initialize(generator, pattern)
  21218. super(generator, @pattern = pattern)
  21219. end
  21220. def grep(variable, pattern, &block)
  21221. enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
  21222. end
  21223. def inject(variable, memo, &block)
  21224. enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
  21225. end
  21226. def pluck(variable, property)
  21227. add_variable_assignment!(variable)
  21228. append_enumerable_function!("pluck(#{property.to_json});")
  21229. end
  21230. def zip(variable, *arguments, &block)
  21231. add_variable_assignment!(variable)
  21232. append_enumerable_function!("zip(#{arguments_for_call arguments}")
  21233. if block
  21234. function_chain[-1] += ", function(array) {"
  21235. yield ActiveSupport::JSON::Variable.new('array')
  21236. add_return_statement!
  21237. @generator << '});'
  21238. else
  21239. function_chain[-1] += ');'
  21240. end
  21241. end
  21242. private
  21243. def method_missing(method, *arguments, &block)
  21244. if ENUMERABLE_METHODS.include?(method)
  21245. returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
  21246. variable = arguments.first if returnable
  21247. enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
  21248. else
  21249. super
  21250. end
  21251. end
  21252. # Options
  21253. # * variable - name of the variable to set the result of the enumeration to
  21254. # * method_args - array of the javascript enumeration method args that occur before the function
  21255. # * yield_args - array of the javascript yield args
  21256. # * return - true if the enumeration should return the last statement
  21257. def enumerate(enumerable, options = {}, &block)
  21258. options[:method_args] ||= []
  21259. options[:yield_args] ||= []
  21260. yield_args = options[:yield_args] * ', '
  21261. method_args = arguments_for_call options[:method_args] # foo, bar, function
  21262. method_args << ', ' unless method_args.blank?
  21263. add_variable_assignment!(options[:variable]) if options[:variable]
  21264. append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
  21265. # only yield as many params as were passed in the block
  21266. yield *options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1]
  21267. add_return_statement! if options[:return]
  21268. @generator << '});'
  21269. end
  21270. def add_variable_assignment!(variable)
  21271. function_chain.push("var #{variable} = #{function_chain.pop}")
  21272. end
  21273. def add_return_statement!
  21274. unless function_chain.last =~ /return/
  21275. function_chain.push("return #{function_chain.pop.chomp(';')};")
  21276. end
  21277. end
  21278. def append_enumerable_function!(call)
  21279. function_chain[-1].chomp!(';')
  21280. function_chain[-1] += ".#{call}"
  21281. end
  21282. end
  21283. class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
  21284. def initialize(generator, pattern)
  21285. super(generator, "$$(#{pattern.to_json})")
  21286. end
  21287. end
  21288. end
  21289. end
  21290. require File.dirname(__FILE__) + '/javascript_helper'
  21291. module ActionView
  21292. module Helpers
  21293. # Provides a set of helpers for calling Scriptaculous JavaScript
  21294. # functions, including those which create Ajax controls and visual effects.
  21295. #
  21296. # To be able to use these helpers, you must include the Prototype
  21297. # JavaScript framework and the Scriptaculous JavaScript library in your
  21298. # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
  21299. # for more information on including the necessary JavaScript.
  21300. #
  21301. # The Scriptaculous helpers' behavior can be tweaked with various options.
  21302. # See the documentation at http://script.aculo.us for more information on
  21303. # using these helpers in your application.
  21304. module ScriptaculousHelper
  21305. unless const_defined? :TOGGLE_EFFECTS
  21306. TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
  21307. end
  21308. # Returns a JavaScript snippet to be used on the Ajax callbacks for
  21309. # starting visual effects.
  21310. #
  21311. # Example:
  21312. # <%= link_to_remote "Reload", :update => "posts",
  21313. # :url => { :action => "reload" },
  21314. # :complete => visual_effect(:highlight, "posts", :duration => 0.5)
  21315. #
  21316. # If no element_id is given, it assumes "element" which should be a local
  21317. # variable in the generated JavaScript execution context. This can be
  21318. # used for example with drop_receiving_element:
  21319. #
  21320. # <%= drop_receving_element (...), :loading => visual_effect(:fade) %>
  21321. #
  21322. # This would fade the element that was dropped on the drop receiving
  21323. # element.
  21324. #
  21325. # For toggling visual effects, you can use :toggle_appear, :toggle_slide, and
  21326. # :toggle_blind which will alternate between appear/fade, slidedown/slideup, and
  21327. # blinddown/blindup respectively.
  21328. #
  21329. # You can change the behaviour with various options, see
  21330. # http://script.aculo.us for more documentation.
  21331. def visual_effect(name, element_id = false, js_options = {})
  21332. element = element_id ? element_id.to_json : "element"
  21333. js_options[:queue] = if js_options[:queue].is_a?(Hash)
  21334. '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
  21335. elsif js_options[:queue]
  21336. "'#{js_options[:queue]}'"
  21337. end if js_options[:queue]
  21338. if TOGGLE_EFFECTS.include? name.to_sym
  21339. "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
  21340. else
  21341. "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
  21342. end
  21343. end
  21344. # Makes the element with the DOM ID specified by +element_id+ sortable
  21345. # by drag-and-drop and make an Ajax call whenever the sort order has
  21346. # changed. By default, the action called gets the serialized sortable
  21347. # element as parameters.
  21348. #
  21349. # Example:
  21350. # <%= sortable_element("my_list", :url => { :action => "order" }) %>
  21351. #
  21352. # In the example, the action gets a "my_list" array parameter
  21353. # containing the values of the ids of elements the sortable consists
  21354. # of, in the current order.
  21355. #
  21356. # You can change the behaviour with various options, see
  21357. # http://script.aculo.us for more documentation.
  21358. def sortable_element(element_id, options = {})
  21359. javascript_tag(sortable_element_js(element_id, options).chop!)
  21360. end
  21361. def sortable_element_js(element_id, options = {}) #:nodoc:
  21362. options[:with] ||= "Sortable.serialize(#{element_id.to_json})"
  21363. options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
  21364. options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
  21365. [:tag, :overlap, :constraint, :handle].each do |option|
  21366. options[option] = "'#{options[option]}'" if options[option]
  21367. end
  21368. options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
  21369. options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
  21370. %(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
  21371. end
  21372. # Makes the element with the DOM ID specified by +element_id+ draggable.
  21373. #
  21374. # Example:
  21375. # <%= draggable_element("my_image", :revert => true)
  21376. #
  21377. # You can change the behaviour with various options, see
  21378. # http://script.aculo.us for more documentation.
  21379. def draggable_element(element_id, options = {})
  21380. javascript_tag(draggable_element_js(element_id, options).chop!)
  21381. end
  21382. def draggable_element_js(element_id, options = {}) #:nodoc:
  21383. %(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
  21384. end
  21385. # Makes the element with the DOM ID specified by +element_id+ receive
  21386. # dropped draggable elements (created by draggable_element).
  21387. # and make an AJAX call By default, the action called gets the DOM ID
  21388. # of the element as parameter.
  21389. #
  21390. # Example:
  21391. # <%= drop_receiving_element("my_cart", :url =>
  21392. # { :controller => "cart", :action => "add" }) %>
  21393. #
  21394. # You can change the behaviour with various options, see
  21395. # http://script.aculo.us for more documentation.
  21396. def drop_receiving_element(element_id, options = {})
  21397. javascript_tag(drop_receiving_element_js(element_id, options).chop!)
  21398. end
  21399. def drop_receiving_element_js(element_id, options = {}) #:nodoc:
  21400. options[:with] ||= "'id=' + encodeURIComponent(element.id)"
  21401. options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
  21402. options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
  21403. options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
  21404. options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
  21405. %(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
  21406. end
  21407. end
  21408. end
  21409. end
  21410. require 'cgi'
  21411. require 'erb'
  21412. module ActionView
  21413. module Helpers
  21414. # This is poor man's Builder for the rare cases where you need to programmatically make tags but can't use Builder.
  21415. module TagHelper
  21416. include ERB::Util
  21417. # Examples:
  21418. # * <tt>tag("br") => <br /></tt>
  21419. # * <tt>tag("input", { "type" => "text"}) => <input type="text" /></tt>
  21420. def tag(name, options = nil, open = false)
  21421. "<#{name}#{tag_options(options.stringify_keys) if options}" + (open ? ">" : " />")
  21422. end
  21423. # Examples:
  21424. # * <tt>content_tag("p", "Hello world!") => <p>Hello world!</p></tt>
  21425. # * <tt>content_tag("div", content_tag("p", "Hello world!"), "class" => "strong") => </tt>
  21426. # <tt><div class="strong"><p>Hello world!</p></div></tt>
  21427. def content_tag(name, content, options = nil)
  21428. "<#{name}#{tag_options(options.stringify_keys) if options}>#{content}</#{name}>"
  21429. end
  21430. # Returns a CDATA section for the given +content+. CDATA sections
  21431. # are used to escape blocks of text containing characters which would
  21432. # otherwise be recognized as markup. CDATA sections begin with the string
  21433. # <tt>&lt;![CDATA[</tt> and end with (and may not contain) the string
  21434. # <tt>]]></tt>.
  21435. def cdata_section(content)
  21436. "<![CDATA[#{content}]]>"
  21437. end
  21438. private
  21439. def tag_options(options)
  21440. cleaned_options = convert_booleans(options.stringify_keys.reject {|key, value| value.nil?})
  21441. ' ' + cleaned_options.map {|key, value| %(#{key}="#{html_escape(value.to_s)}")}.sort * ' ' unless cleaned_options.empty?
  21442. end
  21443. def convert_booleans(options)
  21444. %w( disabled readonly multiple ).each { |a| boolean_attribute(options, a) }
  21445. options
  21446. end
  21447. def boolean_attribute(options, attribute)
  21448. options[attribute] ? options[attribute] = attribute : options.delete(attribute)
  21449. end
  21450. end
  21451. end
  21452. end
  21453. require File.dirname(__FILE__) + '/tag_helper'
  21454. module ActionView
  21455. module Helpers #:nodoc:
  21456. # Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
  21457. # templates. In the example below we iterate over a collection of posts provided to the template and print each title
  21458. # after making sure it doesn't run longer than 20 characters:
  21459. # <% for post in @posts %>
  21460. # Title: <%= truncate(post.title, 20) %>
  21461. # <% end %>
  21462. module TextHelper
  21463. # The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello".
  21464. # If you absolutely must use a method-based output, you can use concat. It's used like this: <% concat "hello", binding %>. Notice that
  21465. # it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello.
  21466. def concat(string, binding)
  21467. eval("_erbout", binding).concat(string)
  21468. end
  21469. # Truncates +text+ to the length of +length+ and replaces the last three characters with the +truncate_string+
  21470. # if the +text+ is longer than +length+.
  21471. def truncate(text, length = 30, truncate_string = "...")
  21472. if text.nil? then return end
  21473. l = length - truncate_string.length
  21474. if $KCODE == "NONE"
  21475. text.length > length ? text[0...l] + truncate_string : text
  21476. else
  21477. chars = text.split(//)
  21478. chars.length > length ? chars[0...l].join + truncate_string : text
  21479. end
  21480. end
  21481. # Highlights the +phrase+ where it is found in the +text+ by surrounding it like
  21482. # <strong class="highlight">I'm a highlight phrase</strong>. The highlighter can be specialized by
  21483. # passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted.
  21484. # N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
  21485. def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
  21486. if phrase.blank? then return text end
  21487. text.gsub(/(#{Regexp.escape(phrase)})/i, highlighter) unless text.nil?
  21488. end
  21489. # Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined
  21490. # by +radius+. If the phrase isn't found, nil is returned. Ex:
  21491. # excerpt("hello my world", "my", 3) => "...lo my wo..."
  21492. def excerpt(text, phrase, radius = 100, excerpt_string = "...")
  21493. if text.nil? || phrase.nil? then return end
  21494. phrase = Regexp.escape(phrase)
  21495. if found_pos = text =~ /(#{phrase})/i
  21496. start_pos = [ found_pos - radius, 0 ].max
  21497. end_pos = [ found_pos + phrase.length + radius, text.length ].min
  21498. prefix = start_pos > 0 ? excerpt_string : ""
  21499. postfix = end_pos < text.length ? excerpt_string : ""
  21500. prefix + text[start_pos..end_pos].strip + postfix
  21501. else
  21502. nil
  21503. end
  21504. end
  21505. # Attempts to pluralize the +singular+ word unless +count+ is 1. See source for pluralization rules.
  21506. def pluralize(count, singular, plural = nil)
  21507. "#{count} " + if count == 1
  21508. singular
  21509. elsif plural
  21510. plural
  21511. elsif Object.const_defined?("Inflector")
  21512. Inflector.pluralize(singular)
  21513. else
  21514. singular + "s"
  21515. end
  21516. end
  21517. # Word wrap long lines to line_width.
  21518. def word_wrap(text, line_width = 80)
  21519. text.gsub(/\n/, "\n\n").gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
  21520. end
  21521. begin
  21522. require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth)
  21523. # Returns the text with all the Textile codes turned into HTML-tags.
  21524. # <i>This method is only available if RedCloth can be required</i>.
  21525. def textilize(text)
  21526. if text.blank?
  21527. ""
  21528. else
  21529. textilized = RedCloth.new(text, [ :hard_breaks ])
  21530. textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
  21531. textilized.to_html
  21532. end
  21533. end
  21534. # Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
  21535. # <i>This method is only available if RedCloth can be required</i>.
  21536. def textilize_without_paragraph(text)
  21537. textiled = textilize(text)
  21538. if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
  21539. if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end
  21540. return textiled
  21541. end
  21542. rescue LoadError
  21543. # We can't really help what's not there
  21544. end
  21545. begin
  21546. require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth)
  21547. # Returns the text with all the Markdown codes turned into HTML-tags.
  21548. # <i>This method is only available if BlueCloth can be required</i>.
  21549. def markdown(text)
  21550. text.blank? ? "" : BlueCloth.new(text).to_html
  21551. end
  21552. rescue LoadError
  21553. # We can't really help what's not there
  21554. end
  21555. # Returns +text+ transformed into HTML using very simple formatting rules
  21556. # Surrounds paragraphs with <tt><p></tt> tags, and converts line breaks into <tt><br/></tt>
  21557. # Two consecutive newlines(<tt>\n\n</tt>) are considered as a paragraph, one newline (<tt>\n</tt>) is
  21558. # considered a linebreak, three or more consecutive newlines are turned into two newlines
  21559. def simple_format(text)
  21560. text.gsub!(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
  21561. text.gsub!(/\n\n+/, "\n\n") # zap dupes
  21562. text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
  21563. text.gsub!(/([^\n])(\n)([^\n])/, '\1\2<br />\3') # turn single newline into <br />
  21564. content_tag("p", text)
  21565. end
  21566. # Turns all urls and email addresses into clickable links. The +link+ parameter can limit what should be linked.
  21567. # Options are <tt>:all</tt> (default), <tt>:email_addresses</tt>, and <tt>:urls</tt>.
  21568. #
  21569. # Example:
  21570. # auto_link("Go to http://www.rubyonrails.com and say hello to david@loudthinking.com") =>
  21571. # Go to <a href="http://www.rubyonrails.com">http://www.rubyonrails.com</a> and
  21572. # say hello to <a href="mailto:david@loudthinking.com">david@loudthinking.com</a>
  21573. #
  21574. # If a block is given, each url and email address is yielded and the
  21575. # result is used as the link text. Example:
  21576. # auto_link(post.body, :all, :target => '_blank') do |text|
  21577. # truncate(text, 15)
  21578. # end
  21579. def auto_link(text, link = :all, href_options = {}, &block)
  21580. return '' if text.blank?
  21581. case link
  21582. when :all then auto_link_urls(auto_link_email_addresses(text, &block), href_options, &block)
  21583. when :email_addresses then auto_link_email_addresses(text, &block)
  21584. when :urls then auto_link_urls(text, href_options, &block)
  21585. end
  21586. end
  21587. # Turns all links into words, like "<a href="something">else</a>" to "else".
  21588. def strip_links(text)
  21589. text.gsub(/<a.*>(.*)<\/a>/m, '\1')
  21590. end
  21591. # Try to require the html-scanner library
  21592. begin
  21593. require 'html/tokenizer'
  21594. require 'html/node'
  21595. rescue LoadError
  21596. # if there isn't a copy installed, use the vendor version in
  21597. # action controller
  21598. $:.unshift File.join(File.dirname(__FILE__), "..", "..",
  21599. "action_controller", "vendor", "html-scanner")
  21600. require 'html/tokenizer'
  21601. require 'html/node'
  21602. end
  21603. VERBOTEN_TAGS = %w(form script) unless defined?(VERBOTEN_TAGS)
  21604. VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS)
  21605. # Sanitizes the given HTML by making form and script tags into regular
  21606. # text, and removing all "onxxx" attributes (so that arbitrary Javascript
  21607. # cannot be executed). Also removes href attributes that start with
  21608. # "javascript:".
  21609. #
  21610. # Returns the sanitized text.
  21611. def sanitize(html)
  21612. # only do this if absolutely necessary
  21613. if html.index("<")
  21614. tokenizer = HTML::Tokenizer.new(html)
  21615. new_text = ""
  21616. while token = tokenizer.next
  21617. node = HTML::Node.parse(nil, 0, 0, token, false)
  21618. new_text << case node
  21619. when HTML::Tag
  21620. if VERBOTEN_TAGS.include?(node.name)
  21621. node.to_s.gsub(/</, "&lt;")
  21622. else
  21623. if node.closing != :close
  21624. node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS }
  21625. if node.attributes["href"] =~ /^javascript:/i
  21626. node.attributes.delete "href"
  21627. end
  21628. end
  21629. node.to_s
  21630. end
  21631. else
  21632. node.to_s.gsub(/</, "&lt;")
  21633. end
  21634. end
  21635. html = new_text
  21636. end
  21637. html
  21638. end
  21639. # Strips all HTML tags from the input, including comments. This uses the html-scanner
  21640. # tokenizer and so it's HTML parsing ability is limited by that of html-scanner.
  21641. #
  21642. # Returns the tag free text.
  21643. def strip_tags(html)
  21644. if html.index("<")
  21645. text = ""
  21646. tokenizer = HTML::Tokenizer.new(html)
  21647. while token = tokenizer.next
  21648. node = HTML::Node.parse(nil, 0, 0, token, false)
  21649. # result is only the content of any Text nodes
  21650. text << node.to_s if node.class == HTML::Text
  21651. end
  21652. # strip any comments, and if they have a newline at the end (ie. line with
  21653. # only a comment) strip that too
  21654. text.gsub(/<!--(.*?)-->[\n]?/m, "")
  21655. else
  21656. html # already plain text
  21657. end
  21658. end
  21659. # Returns a Cycle object whose to_s value cycles through items of an
  21660. # array every time it is called. This can be used to alternate classes
  21661. # for table rows:
  21662. #
  21663. # <%- for item in @items do -%>
  21664. # <tr class="<%= cycle("even", "odd") %>">
  21665. # ... use item ...
  21666. # </tr>
  21667. # <%- end -%>
  21668. #
  21669. # You can use named cycles to prevent clashes in nested loops. You'll
  21670. # have to reset the inner cycle, manually:
  21671. #
  21672. # <%- for item in @items do -%>
  21673. # <tr class="<%= cycle("even", "odd", :name => "row_class")
  21674. # <td>
  21675. # <%- for value in item.values do -%>
  21676. # <span style="color:'<%= cycle("red", "green", "blue"
  21677. # :name => "colors") %>'">
  21678. # item
  21679. # </span>
  21680. # <%- end -%>
  21681. # <%- reset_cycle("colors") -%>
  21682. # </td>
  21683. # </tr>
  21684. # <%- end -%>
  21685. def cycle(first_value, *values)
  21686. if (values.last.instance_of? Hash)
  21687. params = values.pop
  21688. name = params[:name]
  21689. else
  21690. name = "default"
  21691. end
  21692. values.unshift(first_value)
  21693. cycle = get_cycle(name)
  21694. if (cycle.nil? || cycle.values != values)
  21695. cycle = set_cycle(name, Cycle.new(*values))
  21696. end
  21697. return cycle.to_s
  21698. end
  21699. # Resets a cycle so that it starts from the first element in the array
  21700. # the next time it is used.
  21701. def reset_cycle(name = "default")
  21702. cycle = get_cycle(name)
  21703. return if cycle.nil?
  21704. cycle.reset
  21705. end
  21706. class Cycle #:nodoc:
  21707. attr_reader :values
  21708. def initialize(first_value, *values)
  21709. @values = values.unshift(first_value)
  21710. reset
  21711. end
  21712. def reset
  21713. @index = 0
  21714. end
  21715. def to_s
  21716. value = @values[@index].to_s
  21717. @index = (@index + 1) % @values.size
  21718. return value
  21719. end
  21720. end
  21721. private
  21722. # The cycle helpers need to store the cycles in a place that is
  21723. # guaranteed to be reset every time a page is rendered, so it
  21724. # uses an instance variable of ActionView::Base.
  21725. def get_cycle(name)
  21726. @_cycles = Hash.new if @_cycles.nil?
  21727. return @_cycles[name]
  21728. end
  21729. def set_cycle(name, cycle_object)
  21730. @_cycles = Hash.new if @_cycles.nil?
  21731. @_cycles[name] = cycle_object
  21732. end
  21733. AUTO_LINK_RE = /
  21734. ( # leading text
  21735. <\w+.*?>| # leading HTML tag, or
  21736. [^=!:'"\/]| # leading punctuation, or
  21737. ^ # beginning of line
  21738. )
  21739. (
  21740. (?:http[s]?:\/\/)| # protocol spec, or
  21741. (?:www\.) # www.*
  21742. )
  21743. (
  21744. ([\w]+:?[=?&\/.-]?)* # url segment
  21745. \w+[\/]? # url tail
  21746. (?:\#\w*)? # trailing anchor
  21747. )
  21748. ([[:punct:]]|\s|<|$) # trailing text
  21749. /x unless const_defined?(:AUTO_LINK_RE)
  21750. # Turns all urls into clickable links. If a block is given, each url
  21751. # is yielded and the result is used as the link text. Example:
  21752. # auto_link_urls(post.body, :all, :target => '_blank') do |text|
  21753. # truncate(text, 15)
  21754. # end
  21755. def auto_link_urls(text, href_options = {})
  21756. extra_options = tag_options(href_options.stringify_keys) || ""
  21757. text.gsub(AUTO_LINK_RE) do
  21758. all, a, b, c, d = $&, $1, $2, $3, $5
  21759. if a =~ /<a\s/i # don't replace URL's that are already linked
  21760. all
  21761. else
  21762. text = b + c
  21763. text = yield(text) if block_given?
  21764. %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{text}</a>#{d})
  21765. })
  21766. end
  21767. end
  21768. end
  21769. # Turns all email addresses into clickable links. If a block is given,
  21770. # each email is yielded and the result is used as the link text.
  21771. # Example:
  21772. # auto_link_email_addresses(post.body) do |text|
  21773. # truncate(text, 15)
  21774. # end
  21775. def auto_link_email_addresses(text)
  21776. text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
  21777. text = $1
  21778. text = yield(text) if block_given?
  21779. %{<a href="mailto:#{$1}">#{text}</a>}
  21780. end
  21781. end
  21782. end
  21783. end
  21784. end
  21785. require File.dirname(__FILE__) + '/javascript_helper'
  21786. module ActionView
  21787. module Helpers
  21788. # Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
  21789. # you can use the same format for links in the views that you do in the controller. The different methods are even named
  21790. # synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
  21791. # redirection in redirect_to.
  21792. module UrlHelper
  21793. include JavaScriptHelper
  21794. # Returns the URL for the set of +options+ provided. This takes the same options
  21795. # as url_for. For a list, see the documentation for ActionController::Base#url_for.
  21796. # Note that it'll set :only_path => true so you'll get /controller/action instead of the
  21797. # http://example.com/controller/action part (makes it harder to parse httpd log files)
  21798. #
  21799. # When called from a view, url_for returns an HTML escaped url. If you need an unescaped
  21800. # url, pass :escape => false to url_for.
  21801. #
  21802. def url_for(options = {}, *parameters_for_method_reference)
  21803. if options.kind_of? Hash
  21804. options = { :only_path => true }.update(options.symbolize_keys)
  21805. escape = options.key?(:escape) ? options.delete(:escape) : true
  21806. else
  21807. escape = true
  21808. end
  21809. url = @controller.send(:url_for, options, *parameters_for_method_reference)
  21810. escape ? html_escape(url) : url
  21811. end
  21812. # Creates a link tag of the given +name+ using an URL created by the set of +options+. See the valid options in
  21813. # the documentation for ActionController::Base#url_for. It's also possible to pass a string instead of an options hash to
  21814. # get a link tag that just points without consideration. If nil is passed as a name, the link itself will become the name.
  21815. #
  21816. # The html_options has three special features. One for creating javascript confirm alerts where if you pass :confirm => 'Are you sure?',
  21817. # the link will be guarded with a JS popup asking that question. If the user accepts, the link is processed, otherwise not.
  21818. #
  21819. # Another for creating a popup window, which is done by either passing :popup with true or the options of the window in
  21820. # Javascript form.
  21821. #
  21822. # And a third for making the link do a POST request (instead of the regular GET) through a dynamically added form element that
  21823. # is instantly submitted. Note that if the user has turned off Javascript, the request will fall back on the GET. So its
  21824. # your responsibility to determine what the action should be once it arrives at the controller. The POST form is turned on by
  21825. # passing :post as true. Note, it's not possible to use POST requests and popup targets at the same time (an exception will be thrown).
  21826. #
  21827. # Examples:
  21828. # link_to "Delete this page", { :action => "destroy", :id => @page.id }, :confirm => "Are you sure?"
  21829. # link_to "Help", { :action => "help" }, :popup => true
  21830. # link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
  21831. # link_to "Destroy account", { :action => "destroy" }, :confirm => "Are you sure?", :post => true
  21832. def link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)
  21833. if html_options
  21834. html_options = html_options.stringify_keys
  21835. convert_options_to_javascript!(html_options)
  21836. tag_options = tag_options(html_options)
  21837. else
  21838. tag_options = nil
  21839. end
  21840. url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
  21841. "<a href=\"#{url}\"#{tag_options}>#{name || url}</a>"
  21842. end
  21843. # Generates a form containing a sole button that submits to the
  21844. # URL given by _options_. Use this method instead of +link_to+
  21845. # for actions that do not have the safe HTTP GET semantics
  21846. # implied by using a hypertext link.
  21847. #
  21848. # The parameters are the same as for +link_to+. Any _html_options_
  21849. # that you pass will be applied to the inner +input+ element.
  21850. # In particular, pass
  21851. #
  21852. # :disabled => true/false
  21853. #
  21854. # as part of _html_options_ to control whether the button is
  21855. # disabled. The generated form element is given the class
  21856. # 'button-to', to which you can attach CSS styles for display
  21857. # purposes.
  21858. #
  21859. # Example 1:
  21860. #
  21861. # # inside of controller for "feeds"
  21862. # button_to "Edit", :action => 'edit', :id => 3
  21863. #
  21864. # Generates the following HTML (sans formatting):
  21865. #
  21866. # <form method="post" action="/feeds/edit/3" class="button-to">
  21867. # <div><input value="Edit" type="submit" /></div>
  21868. # </form>
  21869. #
  21870. # Example 2:
  21871. #
  21872. # button_to "Destroy", { :action => 'destroy', :id => 3 },
  21873. # :confirm => "Are you sure?"
  21874. #
  21875. # Generates the following HTML (sans formatting):
  21876. #
  21877. # <form method="post" action="/feeds/destroy/3" class="button-to">
  21878. # <div><input onclick="return confirm('Are you sure?');"
  21879. # value="Destroy" type="submit" />
  21880. # </div>
  21881. # </form>
  21882. #
  21883. # *NOTE*: This method generates HTML code that represents a form.
  21884. # Forms are "block" content, which means that you should not try to
  21885. # insert them into your HTML where only inline content is expected.
  21886. # For example, you can legally insert a form inside of a +div+ or
  21887. # +td+ element or in between +p+ elements, but not in the middle of
  21888. # a run of text, nor can you place a form within another form.
  21889. # (Bottom line: Always validate your HTML before going public.)
  21890. def button_to(name, options = {}, html_options = nil)
  21891. html_options = (html_options || {}).stringify_keys
  21892. convert_boolean_attributes!(html_options, %w( disabled ))
  21893. if confirm = html_options.delete("confirm")
  21894. html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
  21895. end
  21896. url = options.is_a?(String) ? options : url_for(options)
  21897. name ||= url
  21898. html_options.merge!("type" => "submit", "value" => name)
  21899. "<form method=\"post\" action=\"#{h url}\" class=\"button-to\"><div>" +
  21900. tag("input", html_options) + "</div></form>"
  21901. end
  21902. # This tag is deprecated. Combine the link_to and AssetTagHelper::image_tag yourself instead, like:
  21903. # link_to(image_tag("rss", :size => "30x45", :border => 0), "http://www.example.com")
  21904. def link_image_to(src, options = {}, html_options = {}, *parameters_for_method_reference)
  21905. image_options = { "src" => src.include?("/") ? src : "/images/#{src}" }
  21906. image_options["src"] += ".png" unless image_options["src"].include?(".")
  21907. html_options = html_options.stringify_keys
  21908. if html_options["alt"]
  21909. image_options["alt"] = html_options["alt"]
  21910. html_options.delete "alt"
  21911. else
  21912. image_options["alt"] = src.split("/").last.split(".").first.capitalize
  21913. end
  21914. if html_options["size"]
  21915. image_options["width"], image_options["height"] = html_options["size"].split("x")
  21916. html_options.delete "size"
  21917. end
  21918. if html_options["border"]
  21919. image_options["border"] = html_options["border"]
  21920. html_options.delete "border"
  21921. end
  21922. if html_options["align"]
  21923. image_options["align"] = html_options["align"]
  21924. html_options.delete "align"
  21925. end
  21926. link_to(tag("img", image_options), options, html_options, *parameters_for_method_reference)
  21927. end
  21928. alias_method :link_to_image, :link_image_to # deprecated name
  21929. # Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
  21930. # request uri is the same as the link's, in which case only the name is returned (or the
  21931. # given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
  21932. # to the page currently being viewed.
  21933. def link_to_unless_current(name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
  21934. link_to_unless current_page?(options), name, options, html_options, *parameters_for_method_reference, &block
  21935. end
  21936. # Create a link tag of the given +name+ using an URL created by the set of +options+, unless +condition+
  21937. # is true, in which case only the name is returned (or the given block is yielded, if one exists).
  21938. def link_to_unless(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
  21939. if condition
  21940. if block_given?
  21941. block.arity <= 1 ? yield(name) : yield(name, options, html_options, *parameters_for_method_reference)
  21942. else
  21943. name
  21944. end
  21945. else
  21946. link_to(name, options, html_options, *parameters_for_method_reference)
  21947. end
  21948. end
  21949. # Create a link tag of the given +name+ using an URL created by the set of +options+, if +condition+
  21950. # is true, in which case only the name is returned (or the given block is yielded, if one exists).
  21951. def link_to_if(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
  21952. link_to_unless !condition, name, options, html_options, *parameters_for_method_reference, &block
  21953. end
  21954. # Creates a link tag for starting an email to the specified <tt>email_address</tt>, which is also used as the name of the
  21955. # link unless +name+ is specified. Additional HTML options, such as class or id, can be passed in the <tt>html_options</tt> hash.
  21956. #
  21957. # You can also make it difficult for spiders to harvest email address by obfuscating them.
  21958. # Examples:
  21959. # mail_to "me@domain.com", "My email", :encode => "javascript" # =>
  21960. # <script type="text/javascript" language="javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>
  21961. #
  21962. # mail_to "me@domain.com", "My email", :encode => "hex" # =>
  21963. # <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
  21964. #
  21965. # You can also specify the cc address, bcc address, subject, and body parts of the message header to create a complex e-mail using the
  21966. # corresponding +cc+, +bcc+, +subject+, and +body+ <tt>html_options</tt> keys. Each of these options are URI escaped and then appended to
  21967. # the <tt>email_address</tt> before being output. <b>Be aware that javascript keywords will not be escaped and may break this feature
  21968. # when encoding with javascript.</b>
  21969. # Examples:
  21970. # mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", :bcc => "bccaddress@domain.com", :subject => "This is an example email", :body => "This is the body of the message." # =>
  21971. # <a href="mailto:me@domain.com?cc="ccaddress@domain.com"&bcc="bccaddress@domain.com"&body="This%20is%20the%20body%20of%20the%20message."&subject="This%20is%20an%20example%20email">My email</a>
  21972. def mail_to(email_address, name = nil, html_options = {})
  21973. html_options = html_options.stringify_keys
  21974. encode = html_options.delete("encode")
  21975. cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
  21976. string = ''
  21977. extras = ''
  21978. extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
  21979. extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
  21980. extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
  21981. extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
  21982. extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
  21983. email_address_obfuscated = email_address.dup
  21984. email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
  21985. email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
  21986. if encode == 'javascript'
  21987. tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address.to_s+extras }))}');"
  21988. for i in 0...tmp.length
  21989. string << sprintf("%%%x",tmp[i])
  21990. end
  21991. "<script type=\"text/javascript\">eval(unescape('#{string}'))</script>"
  21992. elsif encode == 'hex'
  21993. for i in 0...email_address.length
  21994. if email_address[i,1] =~ /\w/
  21995. string << sprintf("%%%x",email_address[i])
  21996. else
  21997. string << email_address[i,1]
  21998. end
  21999. end
  22000. content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{string}#{extras}" })
  22001. else
  22002. content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
  22003. end
  22004. end
  22005. # Returns true if the current page uri is generated by the options passed (in url_for format).
  22006. def current_page?(options)
  22007. CGI.escapeHTML(url_for(options)) == @controller.request.request_uri
  22008. end
  22009. private
  22010. def convert_options_to_javascript!(html_options)
  22011. confirm, popup, post = html_options.delete("confirm"), html_options.delete("popup"), html_options.delete("post")
  22012. html_options["onclick"] = case
  22013. when popup && post
  22014. raise ActionView::ActionViewError, "You can't use :popup and :post in the same link"
  22015. when confirm && popup
  22016. "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
  22017. when confirm && post
  22018. "if (#{confirm_javascript_function(confirm)}) { #{post_javascript_function} };return false;"
  22019. when confirm
  22020. "return #{confirm_javascript_function(confirm)};"
  22021. when post
  22022. "#{post_javascript_function}return false;"
  22023. when popup
  22024. popup_javascript_function(popup) + 'return false;'
  22025. else
  22026. html_options["onclick"]
  22027. end
  22028. end
  22029. def confirm_javascript_function(confirm)
  22030. "confirm('#{escape_javascript(confirm)}')"
  22031. end
  22032. def popup_javascript_function(popup)
  22033. popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
  22034. end
  22035. def post_javascript_function
  22036. "var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();"
  22037. end
  22038. # Processes the _html_options_ hash, converting the boolean
  22039. # attributes from true/false form into the form required by
  22040. # HTML/XHTML. (An attribute is considered to be boolean if
  22041. # its name is listed in the given _bool_attrs_ array.)
  22042. #
  22043. # More specifically, for each boolean attribute in _html_options_
  22044. # given as:
  22045. #
  22046. # "attr" => bool_value
  22047. #
  22048. # if the associated _bool_value_ evaluates to true, it is
  22049. # replaced with the attribute's name; otherwise the attribute is
  22050. # removed from the _html_options_ hash. (See the XHTML 1.0 spec,
  22051. # section 4.5 "Attribute Minimization" for more:
  22052. # http://www.w3.org/TR/xhtml1/#h-4.5)
  22053. #
  22054. # Returns the updated _html_options_ hash, which is also modified
  22055. # in place.
  22056. #
  22057. # Example:
  22058. #
  22059. # convert_boolean_attributes!( html_options,
  22060. # %w( checked disabled readonly ) )
  22061. def convert_boolean_attributes!(html_options, bool_attrs)
  22062. bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
  22063. html_options
  22064. end
  22065. end
  22066. end
  22067. end
  22068. module ActionView
  22069. # There's also a convenience method for rendering sub templates within the current controller that depends on a single object
  22070. # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being
  22071. # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own.
  22072. #
  22073. # In a template for Advertiser#account:
  22074. #
  22075. # <%= render :partial => "account" %>
  22076. #
  22077. # This would render "advertiser/_account.rhtml" and pass the instance variable @account in as a local variable +account+ to
  22078. # the template for display.
  22079. #
  22080. # In another template for Advertiser#buy, we could have:
  22081. #
  22082. # <%= render :partial => "account", :locals => { :account => @buyer } %>
  22083. #
  22084. # <% for ad in @advertisements %>
  22085. # <%= render :partial => "ad", :locals => { :ad => ad } %>
  22086. # <% end %>
  22087. #
  22088. # This would first render "advertiser/_account.rhtml" with @buyer passed in as the local variable +account+, then render
  22089. # "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display.
  22090. #
  22091. # == Rendering a collection of partials
  22092. #
  22093. # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub
  22094. # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders
  22095. # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten
  22096. # with a single line:
  22097. #
  22098. # <%= render :partial => "ad", :collection => @advertisements %>
  22099. #
  22100. # This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter
  22101. # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
  22102. # example above, the template would be fed +ad_counter+.
  22103. #
  22104. # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects,
  22105. # like Active Records, in there.
  22106. #
  22107. # == Rendering shared partials
  22108. #
  22109. # Two controllers can share a set of partials and render them like this:
  22110. #
  22111. # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
  22112. #
  22113. # This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
  22114. module Partials
  22115. # Deprecated, use render :partial
  22116. def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc:
  22117. path, partial_name = partial_pieces(partial_path)
  22118. object = extracting_object(partial_name, local_assigns, deprecated_local_assigns)
  22119. local_assigns = extract_local_assigns(local_assigns, deprecated_local_assigns)
  22120. local_assigns = local_assigns ? local_assigns.clone : {}
  22121. add_counter_to_local_assigns!(partial_name, local_assigns)
  22122. add_object_to_local_assigns!(partial_name, local_assigns, object)
  22123. if logger
  22124. ActionController::Base.benchmark("Rendered #{path}/_#{partial_name}", Logger::DEBUG, false) do
  22125. render("#{path}/_#{partial_name}", local_assigns)
  22126. end
  22127. else
  22128. render("#{path}/_#{partial_name}", local_assigns)
  22129. end
  22130. end
  22131. # Deprecated, use render :partial, :collection
  22132. def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil) #:nodoc:
  22133. collection_of_partials = Array.new
  22134. counter_name = partial_counter_name(partial_name)
  22135. local_assigns = local_assigns ? local_assigns.clone : {}
  22136. collection.each_with_index do |element, counter|
  22137. local_assigns[counter_name] = counter
  22138. collection_of_partials.push(render_partial(partial_name, element, local_assigns))
  22139. end
  22140. return " " if collection_of_partials.empty?
  22141. if partial_spacer_template
  22142. spacer_path, spacer_name = partial_pieces(partial_spacer_template)
  22143. collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}"))
  22144. else
  22145. collection_of_partials.join
  22146. end
  22147. end
  22148. alias_method :render_collection_of_partials, :render_partial_collection
  22149. private
  22150. def partial_pieces(partial_path)
  22151. if partial_path.include?('/')
  22152. return File.dirname(partial_path), File.basename(partial_path)
  22153. else
  22154. return controller.class.controller_path, partial_path
  22155. end
  22156. end
  22157. def partial_counter_name(partial_name)
  22158. "#{partial_name.split('/').last}_counter".intern
  22159. end
  22160. def extracting_object(partial_name, local_assigns, deprecated_local_assigns)
  22161. if local_assigns.is_a?(Hash) || local_assigns.nil?
  22162. controller.instance_variable_get("@#{partial_name}")
  22163. else
  22164. # deprecated form where object could be passed in as second parameter
  22165. local_assigns
  22166. end
  22167. end
  22168. def extract_local_assigns(local_assigns, deprecated_local_assigns)
  22169. local_assigns.is_a?(Hash) ? local_assigns : deprecated_local_assigns
  22170. end
  22171. def add_counter_to_local_assigns!(partial_name, local_assigns)
  22172. counter_name = partial_counter_name(partial_name)
  22173. local_assigns[counter_name] = 1 unless local_assigns.has_key?(counter_name)
  22174. end
  22175. def add_object_to_local_assigns!(partial_name, local_assigns, object)
  22176. local_assigns[partial_name.intern] ||=
  22177. if object.is_a?(ActionView::Base::ObjectWrapper)
  22178. object.value
  22179. else
  22180. object
  22181. end || controller.instance_variable_get("@#{partial_name}")
  22182. end
  22183. end
  22184. end
  22185. module ActionView
  22186. # The TemplateError exception is raised when the compilation of the template fails. This exception then gathers a
  22187. # bunch of intimate details and uses it to report a very precise exception message.
  22188. class TemplateError < ActionViewError #:nodoc:
  22189. SOURCE_CODE_RADIUS = 3
  22190. attr_reader :original_exception
  22191. def initialize(base_path, file_name, assigns, source, original_exception)
  22192. @base_path, @assigns, @source, @original_exception =
  22193. base_path, assigns, source, original_exception
  22194. @file_name = file_name
  22195. end
  22196. def message
  22197. original_exception.message
  22198. end
  22199. def sub_template_message
  22200. if @sub_templates
  22201. "Trace of template inclusion: " +
  22202. @sub_templates.collect { |template| strip_base_path(template) }.join(", ")
  22203. else
  22204. ""
  22205. end
  22206. end
  22207. def source_extract(indention = 0)
  22208. source_code = IO.readlines(@file_name)
  22209. start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max
  22210. end_on_line = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min
  22211. line_counter = start_on_line
  22212. extract = source_code[start_on_line..end_on_line].collect do |line|
  22213. line_counter += 1
  22214. "#{' ' * indention}#{line_counter}: " + line
  22215. end
  22216. extract.join
  22217. end
  22218. def sub_template_of(file_name)
  22219. @sub_templates ||= []
  22220. @sub_templates << file_name
  22221. end
  22222. def line_number
  22223. if file_name
  22224. regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
  22225. [@original_exception.message, @original_exception.clean_backtrace].flatten.each do |line|
  22226. return $1.to_i if regexp =~ line
  22227. end
  22228. end
  22229. 0
  22230. end
  22231. def file_name
  22232. stripped = strip_base_path(@file_name)
  22233. stripped[0] == ?/ ? stripped[1..-1] : stripped
  22234. end
  22235. def to_s
  22236. "\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" +
  22237. source_extract + "\n " +
  22238. original_exception.clean_backtrace.join("\n ") +
  22239. "\n\n"
  22240. end
  22241. def backtrace
  22242. [
  22243. "On line ##{line_number} of #{file_name}\n\n#{source_extract(4)}\n " +
  22244. original_exception.clean_backtrace.join("\n ")
  22245. ]
  22246. end
  22247. private
  22248. def strip_base_path(file_name)
  22249. file_name = File.expand_path(file_name).gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '')
  22250. file_name.gsub(@base_path, "")
  22251. end
  22252. end
  22253. end
  22254. Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] if defined?(Exception::TraceSubstitutions)
  22255. Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
  22256. #--
  22257. # Copyright (c) 2004 David Heinemeier Hansson
  22258. #
  22259. # Permission is hereby granted, free of charge, to any person obtaining
  22260. # a copy of this software and associated documentation files (the
  22261. # "Software"), to deal in the Software without restriction, including
  22262. # without limitation the rights to use, copy, modify, merge, publish,
  22263. # distribute, sublicense, and/or sell copies of the Software, and to
  22264. # permit persons to whom the Software is furnished to do so, subject to
  22265. # the following conditions:
  22266. #
  22267. # The above copyright notice and this permission notice shall be
  22268. # included in all copies or substantial portions of the Software.
  22269. #
  22270. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  22271. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22272. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  22273. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  22274. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  22275. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22276. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22277. #++
  22278. $:.unshift(File.dirname(__FILE__) + "/action_view/vendor")
  22279. require 'action_view/base'
  22280. require 'action_view/partials'
  22281. ActionView::Base.class_eval do
  22282. include ActionView::Partials
  22283. end
  22284. ActionView::Base.load_helpers(File.dirname(__FILE__) + "/action_view/helpers/")
  22285. $:.unshift(File.dirname(__FILE__) + '/../lib')
  22286. $:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib/active_support')
  22287. $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
  22288. require 'yaml'
  22289. require 'test/unit'
  22290. require 'action_controller'
  22291. require 'breakpoint'
  22292. require 'action_controller/test_process'
  22293. ActionController::Base.logger = nil
  22294. ActionController::Base.ignore_missing_templates = false
  22295. ActionController::Routing::Routes.reload rescue nilrequire File.dirname(__FILE__) + '/abstract_unit'
  22296. # Define the essentials
  22297. class ActiveRecordTestConnector
  22298. cattr_accessor :able_to_connect
  22299. cattr_accessor :connected
  22300. # Set our defaults
  22301. self.connected = false
  22302. self.able_to_connect = true
  22303. end
  22304. # Try to grab AR
  22305. begin
  22306. PATH_TO_AR = File.dirname(__FILE__) + '/../../activerecord'
  22307. require "#{PATH_TO_AR}/lib/active_record" unless Object.const_defined?(:ActiveRecord)
  22308. require "#{PATH_TO_AR}/lib/active_record/fixtures" unless Object.const_defined?(:Fixtures)
  22309. rescue Object => e
  22310. $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
  22311. ActiveRecordTestConnector.able_to_connect = false
  22312. end
  22313. # Define the rest of the connector
  22314. class ActiveRecordTestConnector
  22315. def self.setup
  22316. unless self.connected || !self.able_to_connect
  22317. setup_connection
  22318. load_schema
  22319. self.connected = true
  22320. end
  22321. rescue Object => e
  22322. $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
  22323. #$stderr.puts " #{e.backtrace.join("\n ")}\n"
  22324. self.able_to_connect = false
  22325. end
  22326. private
  22327. def self.setup_connection
  22328. if Object.const_defined?(:ActiveRecord)
  22329. begin
  22330. ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
  22331. ActiveRecord::Base.connection
  22332. rescue Object
  22333. $stderr.puts 'SQLite 3 unavailable; falling to SQLite 2.'
  22334. ActiveRecord::Base.establish_connection(:adapter => 'sqlite', :dbfile => ':memory:')
  22335. ActiveRecord::Base.connection
  22336. end
  22337. Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
  22338. else
  22339. raise "Couldn't locate ActiveRecord."
  22340. end
  22341. end
  22342. # Load actionpack sqlite tables
  22343. def self.load_schema
  22344. File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql|
  22345. ActiveRecord::Base.connection.execute(sql) unless sql.blank?
  22346. end
  22347. end
  22348. end
  22349. # Test case for inheiritance
  22350. class ActiveRecordTestCase < Test::Unit::TestCase
  22351. # Set our fixture path
  22352. self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
  22353. def setup
  22354. abort_tests unless ActiveRecordTestConnector.connected = true
  22355. end
  22356. # Default so Test::Unit::TestCase doesn't complain
  22357. def test_truth
  22358. end
  22359. private
  22360. # If things go wrong, we don't want to run our test cases. We'll just define them to test nothing.
  22361. def abort_tests
  22362. self.class.public_instance_methods.grep(/^test./).each do |method|
  22363. self.class.class_eval { define_method(method.to_sym){} }
  22364. end
  22365. end
  22366. end
  22367. ActiveRecordTestConnector.setuprequire "#{File.dirname(__FILE__)}/../active_record_unit"
  22368. require 'fixtures/company'
  22369. class ActiveRecordAssertionsController < ActionController::Base
  22370. self.template_root = "#{File.dirname(__FILE__)}/../fixtures/"
  22371. # fail with 1 bad column
  22372. def nasty_columns_1
  22373. @company = Company.new
  22374. @company.name = "B"
  22375. @company.rating = 2
  22376. render :inline => "snicker...."
  22377. end
  22378. # fail with 2 bad columns
  22379. def nasty_columns_2
  22380. @company = Company.new
  22381. @company.name = ""
  22382. @company.rating = 2
  22383. render :inline => "double snicker...."
  22384. end
  22385. # this will pass validation
  22386. def good_company
  22387. @company = Company.new
  22388. @company.name = "A"
  22389. @company.rating = 69
  22390. render :inline => "Goodness Gracious!"
  22391. end
  22392. # this will fail validation
  22393. def bad_company
  22394. @company = Company.new
  22395. render :inline => "Who's Bad?"
  22396. end
  22397. # the safety dance......
  22398. def rescue_action(e) raise; end
  22399. end
  22400. class ActiveRecordAssertionsControllerTest < ActiveRecordTestCase
  22401. fixtures :companies
  22402. def setup
  22403. @request = ActionController::TestRequest.new
  22404. @response = ActionController::TestResponse.new
  22405. @controller = ActiveRecordAssertionsController.new
  22406. super
  22407. end
  22408. # test for 1 bad apple column
  22409. def test_some_invalid_columns
  22410. process :nasty_columns_1
  22411. assert_success
  22412. assert_invalid_record 'company'
  22413. assert_invalid_column_on_record 'company', 'rating'
  22414. assert_valid_column_on_record 'company', 'name'
  22415. assert_valid_column_on_record 'company', %w(name id)
  22416. end
  22417. # test for 2 bad apples columns
  22418. def test_all_invalid_columns
  22419. process :nasty_columns_2
  22420. assert_success
  22421. assert_invalid_record 'company'
  22422. assert_invalid_column_on_record 'company', 'rating'
  22423. assert_invalid_column_on_record 'company', 'name'
  22424. assert_invalid_column_on_record 'company', %w(name rating)
  22425. end
  22426. # ensure we have no problems with an ActiveRecord
  22427. def test_valid_record
  22428. process :good_company
  22429. assert_success
  22430. assert_valid_record 'company'
  22431. end
  22432. # ensure we have problems with an ActiveRecord
  22433. def test_invalid_record
  22434. process :bad_company
  22435. assert_success
  22436. assert_invalid_record 'company'
  22437. end
  22438. end# Unfurl the safety net.
  22439. path_to_ar = File.dirname(__FILE__) + '/../../../activerecord'
  22440. if Object.const_defined?(:ActiveRecord) or File.exist?(path_to_ar)
  22441. begin
  22442. # These tests exercise CGI::Session::ActiveRecordStore, so you're going to
  22443. # need AR in a sibling directory to AP and have SQLite installed.
  22444. unless Object.const_defined?(:ActiveRecord)
  22445. require File.join(path_to_ar, 'lib', 'active_record')
  22446. end
  22447. require File.dirname(__FILE__) + '/../abstract_unit'
  22448. require 'action_controller/session/active_record_store'
  22449. #ActiveRecord::Base.logger = Logger.new($stdout)
  22450. begin
  22451. CGI::Session::ActiveRecordStore::Session.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
  22452. CGI::Session::ActiveRecordStore::Session.connection
  22453. rescue Object
  22454. $stderr.puts 'SQLite 3 unavailable; falling back to SQLite 2.'
  22455. begin
  22456. CGI::Session::ActiveRecordStore::Session.establish_connection(:adapter => 'sqlite', :database => ':memory:')
  22457. CGI::Session::ActiveRecordStore::Session.connection
  22458. rescue Object
  22459. $stderr.puts 'SQLite 2 unavailable; skipping ActiveRecordStore test suite.'
  22460. raise SystemExit
  22461. end
  22462. end
  22463. module CommonActiveRecordStoreTests
  22464. def test_basics
  22465. s = session_class.new(:session_id => '1234', :data => { 'foo' => 'bar' })
  22466. assert_equal 'bar', s.data['foo']
  22467. assert s.save
  22468. assert_equal 'bar', s.data['foo']
  22469. assert_not_nil t = session_class.find_by_session_id('1234')
  22470. assert_not_nil t.data
  22471. assert_equal 'bar', t.data['foo']
  22472. end
  22473. def test_reload_same_session
  22474. @new_session.update
  22475. reloaded = CGI::Session.new(CGI.new, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore)
  22476. assert_equal 'bar', reloaded['foo']
  22477. end
  22478. def test_tolerates_close_close
  22479. assert_nothing_raised do
  22480. @new_session.close
  22481. @new_session.close
  22482. end
  22483. end
  22484. end
  22485. class ActiveRecordStoreTest < Test::Unit::TestCase
  22486. include CommonActiveRecordStoreTests
  22487. def session_class
  22488. CGI::Session::ActiveRecordStore::Session
  22489. end
  22490. def session_id_column
  22491. "session_id"
  22492. end
  22493. def setup
  22494. session_class.create_table!
  22495. ENV['REQUEST_METHOD'] = 'GET'
  22496. CGI::Session::ActiveRecordStore.session_class = session_class
  22497. @cgi = CGI.new
  22498. @new_session = CGI::Session.new(@cgi, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true)
  22499. @new_session['foo'] = 'bar'
  22500. end
  22501. # this test only applies for eager sesssion saving
  22502. # def test_another_instance
  22503. # @another = CGI::Session.new(@cgi, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore)
  22504. # assert_equal @new_session.session_id, @another.session_id
  22505. # end
  22506. def test_model_attribute
  22507. assert_kind_of CGI::Session::ActiveRecordStore::Session, @new_session.model
  22508. assert_equal({ 'foo' => 'bar' }, @new_session.model.data)
  22509. end
  22510. def test_save_unloaded_session
  22511. c = session_class.connection
  22512. bogus_class = c.quote(Base64.encode64("\004\010o:\vBlammo\000"))
  22513. c.insert("INSERT INTO #{session_class.table_name} ('#{session_id_column}', 'data') VALUES ('abcdefghijklmnop', #{bogus_class})")
  22514. sess = session_class.find_by_session_id('abcdefghijklmnop')
  22515. assert_not_nil sess
  22516. assert !sess.loaded?
  22517. # because the session is not loaded, the save should be a no-op. If it
  22518. # isn't, this'll try and unmarshall the bogus class, and should get an error.
  22519. assert_nothing_raised { sess.save }
  22520. end
  22521. def teardown
  22522. session_class.drop_table!
  22523. end
  22524. end
  22525. class ColumnLimitTest < Test::Unit::TestCase
  22526. def setup
  22527. @session_class = CGI::Session::ActiveRecordStore::Session
  22528. @session_class.create_table!
  22529. end
  22530. def teardown
  22531. @session_class.drop_table!
  22532. end
  22533. def test_protection_from_data_larger_than_column
  22534. # Can't test this unless there is a limit
  22535. return unless limit = @session_class.data_column_size_limit
  22536. too_big = ':(' * limit
  22537. s = @session_class.new(:session_id => '666', :data => {'foo' => too_big})
  22538. s.data
  22539. assert_raise(ActionController::SessionOverflowError) { s.save }
  22540. end
  22541. end
  22542. class DeprecatedActiveRecordStoreTest < ActiveRecordStoreTest
  22543. def session_id_column
  22544. "sessid"
  22545. end
  22546. def setup
  22547. session_class.connection.execute 'create table old_sessions (id integer primary key, sessid text unique, data text)'
  22548. session_class.table_name = 'old_sessions'
  22549. session_class.send :setup_sessid_compatibility!
  22550. ENV['REQUEST_METHOD'] = 'GET'
  22551. CGI::Session::ActiveRecordStore.session_class = session_class
  22552. @new_session = CGI::Session.new(CGI.new, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true)
  22553. @new_session['foo'] = 'bar'
  22554. end
  22555. def teardown
  22556. session_class.connection.execute 'drop table old_sessions'
  22557. session_class.table_name = 'sessions'
  22558. end
  22559. end
  22560. class SqlBypassActiveRecordStoreTest < ActiveRecordStoreTest
  22561. def session_class
  22562. unless @session_class
  22563. @session_class = CGI::Session::ActiveRecordStore::SqlBypass
  22564. @session_class.connection = CGI::Session::ActiveRecordStore::Session.connection
  22565. end
  22566. @session_class
  22567. end
  22568. def test_model_attribute
  22569. assert_kind_of CGI::Session::ActiveRecordStore::SqlBypass, @new_session.model
  22570. assert_equal({ 'foo' => 'bar' }, @new_session.model.data)
  22571. end
  22572. end
  22573. # End of safety net.
  22574. rescue Object => e
  22575. $stderr.puts "Skipping CGI::Session::ActiveRecordStore tests: #{e}"
  22576. #$stderr.puts " #{e.backtrace.join("\n ")}"
  22577. end
  22578. end
  22579. require File.dirname(__FILE__) + '/../active_record_unit'
  22580. require 'fixtures/topic'
  22581. require 'fixtures/reply'
  22582. require 'fixtures/developer'
  22583. require 'fixtures/project'
  22584. class PaginationTest < ActiveRecordTestCase
  22585. fixtures :topics, :replies, :developers, :projects, :developers_projects
  22586. class PaginationController < ActionController::Base
  22587. self.template_root = "#{File.dirname(__FILE__)}/../fixtures/"
  22588. def simple_paginate
  22589. @topic_pages, @topics = paginate(:topics)
  22590. render :nothing => true
  22591. end
  22592. def paginate_with_per_page
  22593. @topic_pages, @topics = paginate(:topics, :per_page => 1)
  22594. render :nothing => true
  22595. end
  22596. def paginate_with_order
  22597. @topic_pages, @topics = paginate(:topics, :order => 'created_at asc')
  22598. render :nothing => true
  22599. end
  22600. def paginate_with_order_by
  22601. @topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc')
  22602. render :nothing => true
  22603. end
  22604. def paginate_with_include_and_order
  22605. @topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc')
  22606. render :nothing => true
  22607. end
  22608. def paginate_with_conditions
  22609. @topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago])
  22610. render :nothing => true
  22611. end
  22612. def paginate_with_class_name
  22613. @developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR")
  22614. render :nothing => true
  22615. end
  22616. def paginate_with_singular_name
  22617. @developer_pages, @developers = paginate()
  22618. render :nothing => true
  22619. end
  22620. def paginate_with_joins
  22621. @developer_pages, @developers = paginate(:developers,
  22622. :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
  22623. :conditions => 'project_id=1')
  22624. render :nothing => true
  22625. end
  22626. def paginate_with_join
  22627. @developer_pages, @developers = paginate(:developers,
  22628. :join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
  22629. :conditions => 'project_id=1')
  22630. render :nothing => true
  22631. end
  22632. def paginate_with_join_and_count
  22633. @developer_pages, @developers = paginate(:developers,
  22634. :join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id',
  22635. :conditions => 'project_id=1',
  22636. :count => "d.id")
  22637. render :nothing => true
  22638. end
  22639. def rescue_errors(e) raise e end
  22640. def rescue_action(e) raise end
  22641. end
  22642. def setup
  22643. @controller = PaginationController.new
  22644. @request = ActionController::TestRequest.new
  22645. @response = ActionController::TestResponse.new
  22646. super
  22647. end
  22648. # Single Action Pagination Tests
  22649. def test_simple_paginate
  22650. get :simple_paginate
  22651. assert_equal 1, assigns(:topic_pages).page_count
  22652. assert_equal 3, assigns(:topics).size
  22653. end
  22654. def test_paginate_with_per_page
  22655. get :paginate_with_per_page
  22656. assert_equal 1, assigns(:topics).size
  22657. assert_equal 3, assigns(:topic_pages).page_count
  22658. end
  22659. def test_paginate_with_order
  22660. get :paginate_with_order
  22661. expected = [topics(:futurama),
  22662. topics(:harvey_birdman),
  22663. topics(:rails)]
  22664. assert_equal expected, assigns(:topics)
  22665. assert_equal 1, assigns(:topic_pages).page_count
  22666. end
  22667. def test_paginate_with_order_by
  22668. get :paginate_with_order
  22669. expected = assigns(:topics)
  22670. get :paginate_with_order_by
  22671. assert_equal expected, assigns(:topics)
  22672. assert_equal 1, assigns(:topic_pages).page_count
  22673. end
  22674. def test_paginate_with_conditions
  22675. get :paginate_with_conditions
  22676. expected = [topics(:rails)]
  22677. assert_equal expected, assigns(:topics)
  22678. assert_equal 1, assigns(:topic_pages).page_count
  22679. end
  22680. def test_paginate_with_class_name
  22681. get :paginate_with_class_name
  22682. assert assigns(:developers).size > 0
  22683. assert_equal DeVeLoPeR, assigns(:developers).first.class
  22684. end
  22685. def test_paginate_with_joins
  22686. get :paginate_with_joins
  22687. assert_equal 2, assigns(:developers).size
  22688. developer_names = assigns(:developers).map { |d| d.name }
  22689. assert developer_names.include?('David')
  22690. assert developer_names.include?('Jamis')
  22691. end
  22692. def test_paginate_with_join_and_conditions
  22693. get :paginate_with_joins
  22694. expected = assigns(:developers)
  22695. get :paginate_with_join
  22696. assert_equal expected, assigns(:developers)
  22697. end
  22698. def test_paginate_with_join_and_count
  22699. get :paginate_with_joins
  22700. expected = assigns(:developers)
  22701. get :paginate_with_join_and_count
  22702. assert_equal expected, assigns(:developers)
  22703. end
  22704. def test_paginate_with_include_and_order
  22705. get :paginate_with_include_and_order
  22706. expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10)
  22707. assert_equal expected, assigns(:topics)
  22708. end
  22709. end
  22710. require File.dirname(__FILE__) + '/../abstract_unit'
  22711. # a controller class to facilitate the tests
  22712. class ActionPackAssertionsController < ActionController::Base
  22713. # this does absolutely nothing
  22714. def nothing() render_text ""; end
  22715. # a standard template
  22716. def hello_world() render "test/hello_world"; end
  22717. # a standard template
  22718. def hello_xml_world() render "test/hello_xml_world"; end
  22719. # a redirect to an internal location
  22720. def redirect_internal() redirect_to "/nothing"; end
  22721. def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end
  22722. def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end
  22723. def redirect_to_path() redirect_to '/some/path' end
  22724. def redirect_to_named_route() redirect_to route_one_url end
  22725. # a redirect to an external location
  22726. def redirect_external() redirect_to_url "http://www.rubyonrails.org"; end
  22727. # a 404
  22728. def response404() render_text "", "404 AWOL"; end
  22729. # a 500
  22730. def response500() render_text "", "500 Sorry"; end
  22731. # a fictional 599
  22732. def response599() render_text "", "599 Whoah!"; end
  22733. # putting stuff in the flash
  22734. def flash_me
  22735. flash['hello'] = 'my name is inigo montoya...'
  22736. render_text "Inconceivable!"
  22737. end
  22738. # we have a flash, but nothing is in it
  22739. def flash_me_naked
  22740. flash.clear
  22741. render_text "wow!"
  22742. end
  22743. # assign some template instance variables
  22744. def assign_this
  22745. @howdy = "ho"
  22746. render :inline => "Mr. Henke"
  22747. end
  22748. def render_based_on_parameters
  22749. render_text "Mr. #{@params["name"]}"
  22750. end
  22751. def render_url
  22752. render_text "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
  22753. end
  22754. # puts something in the session
  22755. def session_stuffing
  22756. session['xmas'] = 'turkey'
  22757. render_text "ho ho ho"
  22758. end
  22759. # raises exception on get requests
  22760. def raise_on_get
  22761. raise "get" if @request.get?
  22762. render_text "request method: #{@request.env['REQUEST_METHOD']}"
  22763. end
  22764. # raises exception on post requests
  22765. def raise_on_post
  22766. raise "post" if @request.post?
  22767. render_text "request method: #{@request.env['REQUEST_METHOD']}"
  22768. end
  22769. def get_valid_record
  22770. @record = Class.new do
  22771. def valid?
  22772. true
  22773. end
  22774. def errors
  22775. Class.new do
  22776. def full_messages; []; end
  22777. end.new
  22778. end
  22779. end.new
  22780. render :nothing => true
  22781. end
  22782. def get_invalid_record
  22783. @record = Class.new do
  22784. def valid?
  22785. false
  22786. end
  22787. def errors
  22788. Class.new do
  22789. def full_messages; ['...stuff...']; end
  22790. end.new
  22791. end
  22792. end.new
  22793. render :nothing => true
  22794. end
  22795. # 911
  22796. def rescue_action(e) raise; end
  22797. end
  22798. module Admin
  22799. class InnerModuleController < ActionController::Base
  22800. def redirect_to_absolute_controller
  22801. redirect_to :controller => '/content'
  22802. end
  22803. def redirect_to_fellow_controller
  22804. redirect_to :controller => 'user'
  22805. end
  22806. end
  22807. end
  22808. # ---------------------------------------------------------------------------
  22809. # tell the controller where to find its templates but start from parent
  22810. # directory of test_request_response to simulate the behaviour of a
  22811. # production environment
  22812. ActionPackAssertionsController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  22813. # a test case to exercise the new capabilities TestRequest & TestResponse
  22814. class ActionPackAssertionsControllerTest < Test::Unit::TestCase
  22815. # let's get this party started
  22816. def setup
  22817. @controller = ActionPackAssertionsController.new
  22818. @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
  22819. end
  22820. # -- assertion-based testing ------------------------------------------------
  22821. def test_assert_tag_and_url_for
  22822. get :render_url
  22823. assert_tag :content => "/action_pack_assertions/flash_me"
  22824. end
  22825. # test the session assertion to make sure something is there.
  22826. def test_assert_session_has
  22827. process :session_stuffing
  22828. assert_session_has 'xmas'
  22829. assert_session_has_no 'halloween'
  22830. end
  22831. # test the get method, make sure the request really was a get
  22832. def test_get
  22833. assert_raise(RuntimeError) { get :raise_on_get }
  22834. get :raise_on_post
  22835. assert_equal @response.body, 'request method: GET'
  22836. end
  22837. # test the get method, make sure the request really was a get
  22838. def test_post
  22839. assert_raise(RuntimeError) { post :raise_on_post }
  22840. post :raise_on_get
  22841. assert_equal @response.body, 'request method: POST'
  22842. end
  22843. # the following test fails because the request_method is now cached on the request instance
  22844. # test the get/post switch within one test action
  22845. # def test_get_post_switch
  22846. # post :raise_on_get
  22847. # assert_equal @response.body, 'request method: POST'
  22848. # get :raise_on_post
  22849. # assert_equal @response.body, 'request method: GET'
  22850. # post :raise_on_get
  22851. # assert_equal @response.body, 'request method: POST'
  22852. # get :raise_on_post
  22853. # assert_equal @response.body, 'request method: GET'
  22854. # end
  22855. # test the assertion of goodies in the template
  22856. def test_assert_template_has
  22857. process :assign_this
  22858. assert_template_has 'howdy'
  22859. end
  22860. # test the assertion for goodies that shouldn't exist in the template
  22861. def test_assert_template_has_no
  22862. process :nothing
  22863. assert_template_has_no 'maple syrup'
  22864. assert_template_has_no 'howdy'
  22865. end
  22866. # test the redirection assertions
  22867. def test_assert_redirect
  22868. process :redirect_internal
  22869. assert_redirect
  22870. end
  22871. # test the redirect url string
  22872. def test_assert_redirect_url
  22873. process :redirect_external
  22874. assert_redirect_url 'http://www.rubyonrails.org'
  22875. end
  22876. # test the redirection pattern matching on a string
  22877. def test_assert_redirect_url_match_string
  22878. process :redirect_external
  22879. assert_redirect_url_match 'rails.org'
  22880. end
  22881. # test the redirection pattern matching on a pattern
  22882. def test_assert_redirect_url_match_pattern
  22883. process :redirect_external
  22884. assert_redirect_url_match /ruby/
  22885. end
  22886. # test the redirection to a named route
  22887. def test_assert_redirect_to_named_route
  22888. process :redirect_to_named_route
  22889. assert_raise(Test::Unit::AssertionFailedError) do
  22890. assert_redirected_to 'http://test.host/route_two'
  22891. end
  22892. end
  22893. # test the flash-based assertions with something is in the flash
  22894. def test_flash_assertions_full
  22895. process :flash_me
  22896. assert @response.has_flash_with_contents?
  22897. assert_flash_exists
  22898. assert_flash_not_empty
  22899. assert_flash_has 'hello'
  22900. assert_flash_has_no 'stds'
  22901. end
  22902. # test the flash-based assertions with no flash at all
  22903. def test_flash_assertions_negative
  22904. process :nothing
  22905. assert_flash_empty
  22906. assert_flash_has_no 'hello'
  22907. assert_flash_has_no 'qwerty'
  22908. end
  22909. # test the assert_rendered_file
  22910. def test_assert_rendered_file
  22911. process :hello_world
  22912. assert_rendered_file 'test/hello_world'
  22913. assert_rendered_file 'hello_world'
  22914. end
  22915. # test the assert_success assertion
  22916. def test_assert_success
  22917. process :nothing
  22918. assert_success
  22919. assert_rendered_file
  22920. end
  22921. # -- standard request/response object testing --------------------------------
  22922. # ensure our session is working properly
  22923. def test_session_objects
  22924. process :session_stuffing
  22925. assert @response.has_session_object?('xmas')
  22926. assert_session_equal 'turkey', 'xmas'
  22927. assert !@response.has_session_object?('easter')
  22928. end
  22929. # make sure that the template objects exist
  22930. def test_template_objects_alive
  22931. process :assign_this
  22932. assert !@response.has_template_object?('hi')
  22933. assert @response.has_template_object?('howdy')
  22934. end
  22935. # make sure we don't have template objects when we shouldn't
  22936. def test_template_object_missing
  22937. process :nothing
  22938. assert_nil @response.template_objects['howdy']
  22939. end
  22940. def test_assigned_equal
  22941. process :assign_this
  22942. assert_assigned_equal "ho", :howdy
  22943. end
  22944. # check the empty flashing
  22945. def test_flash_me_naked
  22946. process :flash_me_naked
  22947. assert !@response.has_flash?
  22948. assert !@response.has_flash_with_contents?
  22949. end
  22950. # check if we have flash objects
  22951. def test_flash_haves
  22952. process :flash_me
  22953. assert @response.has_flash?
  22954. assert @response.has_flash_with_contents?
  22955. assert @response.has_flash_object?('hello')
  22956. end
  22957. # ensure we don't have flash objects
  22958. def test_flash_have_nots
  22959. process :nothing
  22960. assert !@response.has_flash?
  22961. assert !@response.has_flash_with_contents?
  22962. assert_nil @response.flash['hello']
  22963. end
  22964. # examine that the flash objects are what we expect
  22965. def test_flash_equals
  22966. process :flash_me
  22967. assert_flash_equal 'my name is inigo montoya...', 'hello'
  22968. end
  22969. # check if we were rendered by a file-based template?
  22970. def test_rendered_action
  22971. process :nothing
  22972. assert !@response.rendered_with_file?
  22973. process :hello_world
  22974. assert @response.rendered_with_file?
  22975. assert 'hello_world', @response.rendered_file
  22976. end
  22977. # check the redirection location
  22978. def test_redirection_location
  22979. process :redirect_internal
  22980. assert_equal 'http://test.host/nothing', @response.redirect_url
  22981. process :redirect_external
  22982. assert_equal 'http://www.rubyonrails.org', @response.redirect_url
  22983. process :nothing
  22984. assert_nil @response.redirect_url
  22985. end
  22986. # check server errors
  22987. def test_server_error_response_code
  22988. process :response500
  22989. assert @response.server_error?
  22990. process :response599
  22991. assert @response.server_error?
  22992. process :response404
  22993. assert !@response.server_error?
  22994. end
  22995. # check a 404 response code
  22996. def test_missing_response_code
  22997. process :response404
  22998. assert @response.missing?
  22999. end
  23000. # check to see if our redirection matches a pattern
  23001. def test_redirect_url_match
  23002. process :redirect_external
  23003. assert @response.redirect?
  23004. assert @response.redirect_url_match?("rubyonrails")
  23005. assert @response.redirect_url_match?(/rubyonrails/)
  23006. assert !@response.redirect_url_match?("phpoffrails")
  23007. assert !@response.redirect_url_match?(/perloffrails/)
  23008. end
  23009. # check for a redirection
  23010. def test_redirection
  23011. process :redirect_internal
  23012. assert @response.redirect?
  23013. process :redirect_external
  23014. assert @response.redirect?
  23015. process :nothing
  23016. assert !@response.redirect?
  23017. end
  23018. # check a successful response code
  23019. def test_successful_response_code
  23020. process :nothing
  23021. assert @response.success?
  23022. end
  23023. # a basic check to make sure we have a TestResponse object
  23024. def test_has_response
  23025. process :nothing
  23026. assert_kind_of ActionController::TestResponse, @response
  23027. end
  23028. def test_render_based_on_parameters
  23029. process :render_based_on_parameters, "name" => "David"
  23030. assert_equal "Mr. David", @response.body
  23031. end
  23032. def test_assert_template_xpath_match_no_matches
  23033. process :hello_xml_world
  23034. assert_raises Test::Unit::AssertionFailedError do
  23035. assert_template_xpath_match('/no/such/node/in/document')
  23036. end
  23037. end
  23038. def test_simple_one_element_xpath_match
  23039. process :hello_xml_world
  23040. assert_template_xpath_match('//title', "Hello World")
  23041. end
  23042. def test_array_of_elements_in_xpath_match
  23043. process :hello_xml_world
  23044. assert_template_xpath_match('//p', %w( abes monks wiseguys ))
  23045. end
  23046. def test_follow_redirect
  23047. process :redirect_to_action
  23048. assert_redirected_to :action => "flash_me"
  23049. follow_redirect
  23050. assert_equal 1, @request.parameters["id"].to_i
  23051. assert "Inconceivable!", @response.body
  23052. end
  23053. def test_follow_redirect_outside_current_action
  23054. process :redirect_to_controller
  23055. assert_redirected_to :controller => "elsewhere", :action => "flash_me"
  23056. assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect }
  23057. end
  23058. def test_redirected_to_url_leadling_slash
  23059. process :redirect_to_path
  23060. assert_redirected_to '/some/path'
  23061. end
  23062. def test_redirected_to_url_no_leadling_slash
  23063. process :redirect_to_path
  23064. assert_redirected_to 'some/path'
  23065. end
  23066. def test_redirected_to_url_full_url
  23067. process :redirect_to_path
  23068. assert_redirected_to 'http://test.host/some/path'
  23069. end
  23070. def test_redirected_to_with_nested_controller
  23071. @controller = Admin::InnerModuleController.new
  23072. get :redirect_to_absolute_controller
  23073. assert_redirected_to :controller => 'content'
  23074. get :redirect_to_fellow_controller
  23075. assert_redirected_to :controller => 'admin/user'
  23076. end
  23077. def test_assert_valid
  23078. get :get_valid_record
  23079. assert_valid assigns('record')
  23080. end
  23081. def test_assert_valid_failing
  23082. get :get_invalid_record
  23083. begin
  23084. assert_valid assigns('record')
  23085. assert false
  23086. rescue Test::Unit::AssertionFailedError => e
  23087. end
  23088. end
  23089. end
  23090. class ActionPackHeaderTest < Test::Unit::TestCase
  23091. def setup
  23092. @controller = ActionPackAssertionsController.new
  23093. @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
  23094. end
  23095. def test_rendering_xml_sets_content_type
  23096. process :hello_xml_world
  23097. assert_equal('application/xml', @controller.headers['Content-Type'])
  23098. end
  23099. def test_rendering_xml_respects_content_type
  23100. @response.headers['Content-Type'] = 'application/pdf'
  23101. process :hello_xml_world
  23102. assert_equal('application/pdf', @controller.headers['Content-Type'])
  23103. end
  23104. end
  23105. require File.dirname(__FILE__) + '/../abstract_unit'
  23106. class Address
  23107. def Address.count(conditions = nil, join = nil)
  23108. nil
  23109. end
  23110. def Address.find_all(arg1, arg2, arg3, arg4)
  23111. []
  23112. end
  23113. def self.find(*args)
  23114. []
  23115. end
  23116. end
  23117. class AddressesTestController < ActionController::Base
  23118. scaffold :address
  23119. def self.controller_name; "addresses"; end
  23120. def self.controller_path; "addresses"; end
  23121. end
  23122. AddressesTestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  23123. class AddressesTest < Test::Unit::TestCase
  23124. def setup
  23125. @controller = AddressesTestController.new
  23126. # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
  23127. # a more accurate simulation of what happens in "real life".
  23128. @controller.logger = Logger.new(nil)
  23129. @request = ActionController::TestRequest.new
  23130. @response = ActionController::TestResponse.new
  23131. @request.host = "www.nextangle.com"
  23132. end
  23133. def test_list
  23134. get :list
  23135. assert_equal "We only need to get this far!", @response.body.chomp
  23136. end
  23137. end
  23138. require File.dirname(__FILE__) + '/../abstract_unit'
  23139. require 'test/unit'
  23140. require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
  23141. # Provide some controller to run the tests on.
  23142. module Submodule
  23143. class ContainedEmptyController < ActionController::Base
  23144. end
  23145. class ContainedNonEmptyController < ActionController::Base
  23146. def public_action
  23147. end
  23148. hide_action :hidden_action
  23149. def hidden_action
  23150. end
  23151. def another_hidden_action
  23152. end
  23153. hide_action :another_hidden_action
  23154. end
  23155. class SubclassedController < ContainedNonEmptyController
  23156. hide_action :public_action # Hiding it here should not affect the superclass.
  23157. end
  23158. end
  23159. class EmptyController < ActionController::Base
  23160. include ActionController::Caching
  23161. end
  23162. class NonEmptyController < ActionController::Base
  23163. def public_action
  23164. end
  23165. hide_action :hidden_action
  23166. def hidden_action
  23167. end
  23168. end
  23169. class ControllerClassTests < Test::Unit::TestCase
  23170. def test_controller_path
  23171. assert_equal 'empty', EmptyController.controller_path
  23172. assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
  23173. end
  23174. def test_controller_name
  23175. assert_equal 'empty', EmptyController.controller_name
  23176. assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
  23177. end
  23178. end
  23179. class ControllerInstanceTests < Test::Unit::TestCase
  23180. def setup
  23181. @empty = EmptyController.new
  23182. @contained = Submodule::ContainedEmptyController.new
  23183. @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
  23184. @non_empty_controllers = [NonEmptyController.new,
  23185. Submodule::ContainedNonEmptyController.new]
  23186. end
  23187. def test_action_methods
  23188. @empty_controllers.each do |c|
  23189. assert_equal Set.new, c.send(:action_methods), "#{c.class.controller_path} should be empty!"
  23190. end
  23191. @non_empty_controllers.each do |c|
  23192. assert_equal Set.new('public_action'), c.send(:action_methods), "#{c.class.controller_path} should not be empty!"
  23193. end
  23194. end
  23195. end
  23196. require File.dirname(__FILE__) + '/../abstract_unit'
  23197. require 'test/unit'
  23198. # Provide some static controllers.
  23199. class BenchmarkedController < ActionController::Base
  23200. def public_action
  23201. render :nothing => true
  23202. end
  23203. def rescue_action(e)
  23204. raise e
  23205. end
  23206. end
  23207. class BenchmarkTest < Test::Unit::TestCase
  23208. class MockLogger
  23209. def method_missing(*args)
  23210. end
  23211. end
  23212. def setup
  23213. @controller = BenchmarkedController.new
  23214. # benchmark doesn't do anything unless a logger is set
  23215. @controller.logger = MockLogger.new
  23216. @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
  23217. @request.host = "test.actioncontroller.i"
  23218. end
  23219. def test_with_http_1_0_request
  23220. @request.host = nil
  23221. assert_nothing_raised { get :public_action }
  23222. end
  23223. end
  23224. require 'fileutils'
  23225. require File.dirname(__FILE__) + '/../abstract_unit'
  23226. class TestLogDevice < Logger::LogDevice
  23227. attr :last_message, true
  23228. def initialize
  23229. @last_message=String.new
  23230. end
  23231. def write(message)
  23232. @last_message << message
  23233. end
  23234. def clear
  23235. @last_message = String.new
  23236. end
  23237. end
  23238. #setup our really sophisticated logger
  23239. TestLog = TestLogDevice.new
  23240. RAILS_DEFAULT_LOGGER = Logger.new(TestLog)
  23241. ActionController::Base.logger = RAILS_DEFAULT_LOGGER
  23242. def use_store
  23243. #generate a random key to ensure the cache is always in a different location
  23244. RANDOM_KEY = rand(99999999).to_s
  23245. FILE_STORE_PATH = File.dirname(__FILE__) + '/../temp/' + RANDOM_KEY
  23246. ActionController::Base.perform_caching = true
  23247. ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH
  23248. end
  23249. class TestController < ActionController::Base
  23250. caches_action :render_to_cache, :index
  23251. def render_to_cache
  23252. render_text "Render Cached"
  23253. end
  23254. alias :index :render_to_cache
  23255. end
  23256. class FileStoreTest < Test::Unit::TestCase
  23257. def setup
  23258. @request = ActionController::TestRequest.new
  23259. @response = ActionController::TestResponse.new
  23260. @controller = TestController.new
  23261. @request.host = "hostname.com"
  23262. end
  23263. def teardown
  23264. FileUtils.rm_rf(FILE_STORE_PATH)
  23265. end
  23266. def test_render_cached
  23267. assert_fragment_cached { get :render_to_cache }
  23268. assert_fragment_hit { get :render_to_cache }
  23269. end
  23270. private
  23271. def assert_fragment_cached
  23272. yield
  23273. assert(TestLog.last_message.include?("Cached fragment:"), "--ERROR-- FileStore write failed ----")
  23274. assert(!TestLog.last_message.include?("Couldn't create cache directory:"), "--ERROR-- FileStore create directory failed ----")
  23275. TestLog.clear
  23276. end
  23277. def assert_fragment_hit
  23278. yield
  23279. assert(TestLog.last_message.include?("Fragment read:"), "--ERROR-- Fragment not found in FileStore ----")
  23280. assert(!TestLog.last_message.include?("Cached fragment:"), "--ERROR-- Did cache ----")
  23281. TestLog.clear
  23282. end
  23283. endrequire File.dirname(__FILE__) + '/../abstract_unit'
  23284. class CaptureController < ActionController::Base
  23285. def self.controller_name; "test"; end
  23286. def self.controller_path; "test"; end
  23287. def content_for
  23288. render :layout => "talk_from_action"
  23289. end
  23290. def erb_content_for
  23291. render :layout => "talk_from_action"
  23292. end
  23293. def block_content_for
  23294. render :layout => "talk_from_action"
  23295. end
  23296. def non_erb_block_content_for
  23297. render :layout => "talk_from_action"
  23298. end
  23299. def rescue_action(e) raise end
  23300. end
  23301. CaptureController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  23302. class CaptureTest < Test::Unit::TestCase
  23303. def setup
  23304. @controller = CaptureController.new
  23305. # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
  23306. # a more accurate simulation of what happens in "real life".
  23307. @controller.logger = Logger.new(nil)
  23308. @request = ActionController::TestRequest.new
  23309. @response = ActionController::TestResponse.new
  23310. @request.host = "www.nextangle.com"
  23311. end
  23312. def test_simple_capture
  23313. get :capturing
  23314. assert_equal "Dreamy days", @response.body.strip
  23315. end
  23316. def test_content_for
  23317. get :content_for
  23318. assert_equal expected_content_for_output, @response.body
  23319. end
  23320. def test_erb_content_for
  23321. get :content_for
  23322. assert_equal expected_content_for_output, @response.body
  23323. end
  23324. def test_block_content_for
  23325. get :block_content_for
  23326. assert_equal expected_content_for_output, @response.body
  23327. end
  23328. def test_non_erb_block_content_for
  23329. get :non_erb_block_content_for
  23330. assert_equal expected_content_for_output, @response.body
  23331. end
  23332. def test_update_element_with_capture
  23333. get :update_element_with_capture
  23334. assert_equal(
  23335. "<script type=\"text/javascript\">\n//<![CDATA[\n$('products').innerHTML = '\\n <p>Product 1</p>\\n <p>Product 2</p>\\n';\n\n//]]>\n</script>" +
  23336. "\n\n$('status').innerHTML = '\\n <b>You bought something!</b>\\n';",
  23337. @response.body.strip
  23338. )
  23339. end
  23340. private
  23341. def expected_content_for_output
  23342. "<title>Putting stuff in the title!</title>\n\nGreat stuff!"
  23343. end
  23344. end
  23345. require File.dirname(__FILE__) + '/../abstract_unit'
  23346. require 'action_controller/cgi_process'
  23347. require 'action_controller/cgi_ext/cgi_ext'
  23348. require 'stringio'
  23349. class CGITest < Test::Unit::TestCase
  23350. def setup
  23351. @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
  23352. @query_string_with_nil = "action=create_customer&full_name="
  23353. @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
  23354. @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
  23355. @query_string_with_multiple_of_same_name =
  23356. "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
  23357. @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
  23358. @query_string_without_equal = "action"
  23359. @query_string_with_many_ampersands =
  23360. "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
  23361. end
  23362. def test_query_string
  23363. assert_equal(
  23364. { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
  23365. CGIMethods.parse_query_parameters(@query_string)
  23366. )
  23367. end
  23368. def test_deep_query_string
  23369. assert_equal({'x' => {'y' => {'z' => '10'}}}, CGIMethods.parse_query_parameters('x[y][z]=10'))
  23370. end
  23371. def test_deep_query_string_with_array
  23372. assert_equal({'x' => {'y' => {'z' => ['10']}}}, CGIMethods.parse_query_parameters('x[y][z][]=10'))
  23373. assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, CGIMethods.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
  23374. end
  23375. def test_query_string_with_nil
  23376. assert_equal(
  23377. { "action" => "create_customer", "full_name" => nil},
  23378. CGIMethods.parse_query_parameters(@query_string_with_nil)
  23379. )
  23380. end
  23381. def test_query_string_with_array
  23382. assert_equal(
  23383. { "action" => "create_customer", "selected" => ["1", "2", "3"]},
  23384. CGIMethods.parse_query_parameters(@query_string_with_array)
  23385. )
  23386. end
  23387. def test_query_string_with_amps
  23388. assert_equal(
  23389. { "action" => "create_customer", "name" => "Don't & Does"},
  23390. CGIMethods.parse_query_parameters(@query_string_with_amps)
  23391. )
  23392. end
  23393. def test_query_string_with_many_equal
  23394. assert_equal(
  23395. { "action" => "create_customer", "full_name" => "abc=def=ghi"},
  23396. CGIMethods.parse_query_parameters(@query_string_with_many_equal)
  23397. )
  23398. end
  23399. def test_query_string_without_equal
  23400. assert_equal(
  23401. { "action" => nil },
  23402. CGIMethods.parse_query_parameters(@query_string_without_equal)
  23403. )
  23404. end
  23405. def test_query_string_with_many_ampersands
  23406. assert_equal(
  23407. { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
  23408. CGIMethods.parse_query_parameters(@query_string_with_many_ampersands)
  23409. )
  23410. end
  23411. def test_parse_params
  23412. input = {
  23413. "customers[boston][first][name]" => [ "David" ],
  23414. "customers[boston][first][url]" => [ "http://David" ],
  23415. "customers[boston][second][name]" => [ "Allan" ],
  23416. "customers[boston][second][url]" => [ "http://Allan" ],
  23417. "something_else" => [ "blah" ],
  23418. "something_nil" => [ nil ],
  23419. "something_empty" => [ "" ],
  23420. "products[first]" => [ "Apple Computer" ],
  23421. "products[second]" => [ "Pc" ]
  23422. }
  23423. expected_output = {
  23424. "customers" => {
  23425. "boston" => {
  23426. "first" => {
  23427. "name" => "David",
  23428. "url" => "http://David"
  23429. },
  23430. "second" => {
  23431. "name" => "Allan",
  23432. "url" => "http://Allan"
  23433. }
  23434. }
  23435. },
  23436. "something_else" => "blah",
  23437. "something_empty" => "",
  23438. "something_nil" => "",
  23439. "products" => {
  23440. "first" => "Apple Computer",
  23441. "second" => "Pc"
  23442. }
  23443. }
  23444. assert_equal expected_output, CGIMethods.parse_request_parameters(input)
  23445. end
  23446. def test_parse_params_from_multipart_upload
  23447. mockup = Struct.new(:content_type, :original_filename)
  23448. file = mockup.new('img/jpeg', 'foo.jpg')
  23449. ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
  23450. input = {
  23451. "something" => [ StringIO.new("") ],
  23452. "array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]],
  23453. "mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]],
  23454. "mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]],
  23455. "ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]],
  23456. "products[string]" => [ StringIO.new("Apple Computer") ],
  23457. "products[file]" => [ file ],
  23458. "ie_products[string]" => [ StringIO.new("Microsoft") ],
  23459. "ie_products[file]" => [ ie_file ]
  23460. }
  23461. expected_output = {
  23462. "something" => "",
  23463. "array_of_stringios" => ["One", "Two"],
  23464. "mixed_types_array" => [ "Three", "NotStringIO" ],
  23465. "mixed_types_as_checkboxes" => {
  23466. "strings" => {
  23467. "nested" => [ file, "String", "StringIO" ]
  23468. },
  23469. },
  23470. "ie_mixed_types_as_checkboxes" => {
  23471. "strings" => {
  23472. "nested" => [ ie_file, "String", "StringIO" ]
  23473. },
  23474. },
  23475. "products" => {
  23476. "string" => "Apple Computer",
  23477. "file" => file
  23478. },
  23479. "ie_products" => {
  23480. "string" => "Microsoft",
  23481. "file" => ie_file
  23482. }
  23483. }
  23484. params = CGIMethods.parse_request_parameters(input)
  23485. assert_equal expected_output, params
  23486. # Lone filenames are preserved.
  23487. assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
  23488. assert_equal 'foo.jpg', params['products']['file'].original_filename
  23489. # But full Windows paths are reduced to their basename.
  23490. assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
  23491. assert_equal 'bar.jpg', params['ie_products']['file'].original_filename
  23492. end
  23493. def test_parse_params_with_file
  23494. input = {
  23495. "customers[boston][first][name]" => [ "David" ],
  23496. "something_else" => [ "blah" ],
  23497. "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
  23498. }
  23499. expected_output = {
  23500. "customers" => {
  23501. "boston" => {
  23502. "first" => {
  23503. "name" => "David"
  23504. }
  23505. }
  23506. },
  23507. "something_else" => "blah",
  23508. "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
  23509. }
  23510. assert_equal expected_output, CGIMethods.parse_request_parameters(input)
  23511. end
  23512. def test_parse_params_with_array
  23513. input = { "selected[]" => [ "1", "2", "3" ] }
  23514. expected_output = { "selected" => [ "1", "2", "3" ] }
  23515. assert_equal expected_output, CGIMethods.parse_request_parameters(input)
  23516. end
  23517. def test_parse_params_with_non_alphanumeric_name
  23518. input = { "a/b[c]" => %w(d) }
  23519. expected = { "a/b" => { "c" => "d" }}
  23520. assert_equal expected, CGIMethods.parse_request_parameters(input)
  23521. end
  23522. def test_parse_params_with_single_brackets_in_middle
  23523. input = { "a/b[c]d" => %w(e) }
  23524. expected = { "a/b[c]d" => "e" }
  23525. assert_equal expected, CGIMethods.parse_request_parameters(input)
  23526. end
  23527. def test_parse_params_with_separated_brackets
  23528. input = { "a/b@[c]d[e]" => %w(f) }
  23529. expected = { "a/b@" => { "c]d[e" => "f" }}
  23530. assert_equal expected, CGIMethods.parse_request_parameters(input)
  23531. end
  23532. def test_parse_params_with_separated_brackets_and_array
  23533. input = { "a/b@[c]d[e][]" => %w(f) }
  23534. expected = { "a/b@" => { "c]d[e" => ["f"] }}
  23535. assert_equal expected , CGIMethods.parse_request_parameters(input)
  23536. end
  23537. def test_parse_params_with_unmatched_brackets_and_array
  23538. input = { "a/b@[c][d[e][]" => %w(f) }
  23539. expected = { "a/b@" => { "c" => { "d[e" => ["f"] }}}
  23540. assert_equal expected, CGIMethods.parse_request_parameters(input)
  23541. end
  23542. end
  23543. class MultipartCGITest < Test::Unit::TestCase
  23544. FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart'
  23545. def setup
  23546. ENV['REQUEST_METHOD'] = 'POST'
  23547. ENV['CONTENT_LENGTH'] = '0'
  23548. ENV['CONTENT_TYPE'] = 'multipart/form-data, boundary=AaB03x'
  23549. end
  23550. def test_single_parameter
  23551. params = process('single_parameter')
  23552. assert_equal({ 'foo' => 'bar' }, params)
  23553. end
  23554. def test_text_file
  23555. params = process('text_file')
  23556. assert_equal %w(file foo), params.keys.sort
  23557. assert_equal 'bar', params['foo']
  23558. file = params['file']
  23559. assert_kind_of StringIO, file
  23560. assert_equal 'file.txt', file.original_filename
  23561. assert_equal "text/plain\r", file.content_type
  23562. assert_equal 'contents', file.read
  23563. end
  23564. def test_large_text_file
  23565. params = process('large_text_file')
  23566. assert_equal %w(file foo), params.keys.sort
  23567. assert_equal 'bar', params['foo']
  23568. file = params['file']
  23569. assert_kind_of Tempfile, file
  23570. assert_equal 'file.txt', file.original_filename
  23571. assert_equal "text/plain\r", file.content_type
  23572. assert ('a' * 20480) == file.read
  23573. end
  23574. def test_binary_file
  23575. params = process('binary_file')
  23576. assert_equal %w(file flowers foo), params.keys.sort
  23577. assert_equal 'bar', params['foo']
  23578. file = params['file']
  23579. assert_kind_of StringIO, file
  23580. assert_equal 'file.txt', file.original_filename
  23581. assert_equal "text/plain\r", file.content_type
  23582. assert_equal 'contents', file.read
  23583. file = params['flowers']
  23584. assert_kind_of StringIO, file
  23585. assert_equal 'flowers.jpg', file.original_filename
  23586. assert_equal "image/jpeg\r", file.content_type
  23587. assert_equal 19512, file.size
  23588. #assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read
  23589. end
  23590. def test_mixed_files
  23591. params = process('mixed_files')
  23592. assert_equal %w(files foo), params.keys.sort
  23593. assert_equal 'bar', params['foo']
  23594. # Ruby CGI doesn't handle multipart/mixed for us.
  23595. assert_kind_of StringIO, params['files']
  23596. assert_equal 19756, params['files'].size
  23597. end
  23598. private
  23599. def process(name)
  23600. old_stdin = $stdin
  23601. File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
  23602. ENV['CONTENT_LENGTH'] = file.stat.size.to_s
  23603. $stdin = file
  23604. CGIMethods.parse_request_parameters CGI.new.params
  23605. end
  23606. ensure
  23607. $stdin = old_stdin
  23608. end
  23609. end
  23610. class CGIRequestTest < Test::Unit::TestCase
  23611. def setup
  23612. @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
  23613. # cookie as returned by some Nokia phone browsers (no space after semicolon separator)
  23614. @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2;is_admin=yes"}
  23615. @fake_cgi = Struct.new(:env_table).new(@request_hash)
  23616. @request = ActionController::CgiRequest.new(@fake_cgi)
  23617. end
  23618. def test_proxy_request
  23619. assert_equal 'glu.ttono.us', @request.host_with_port
  23620. end
  23621. def test_http_host
  23622. @request_hash.delete "HTTP_X_FORWARDED_HOST"
  23623. @request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
  23624. assert_equal "rubyonrails.org:8080", @request.host_with_port
  23625. @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
  23626. assert_equal "www.secondhost.org", @request.host
  23627. end
  23628. def test_http_host_with_default_port_overrides_server_port
  23629. @request_hash.delete "HTTP_X_FORWARDED_HOST"
  23630. @request_hash['HTTP_HOST'] = "rubyonrails.org"
  23631. assert_equal "rubyonrails.org", @request.host_with_port
  23632. end
  23633. def test_host_with_port_defaults_to_server_name_if_no_host_headers
  23634. @request_hash.delete "HTTP_X_FORWARDED_HOST"
  23635. @request_hash.delete "HTTP_HOST"
  23636. assert_equal "glu.ttono.us:8007", @request.host_with_port
  23637. end
  23638. def test_host_with_port_falls_back_to_server_addr_if_necessary
  23639. @request_hash.delete "HTTP_X_FORWARDED_HOST"
  23640. @request_hash.delete "HTTP_HOST"
  23641. @request_hash.delete "SERVER_NAME"
  23642. assert_equal "207.7.108.53:8007", @request.host_with_port
  23643. end
  23644. def test_cookie_syntax_resilience
  23645. cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
  23646. assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"]
  23647. assert_equal ["yes"], cookies["is_admin"]
  23648. alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
  23649. assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"]
  23650. assert_equal ["yes"], alt_cookies["is_admin"]
  23651. end
  23652. end
  23653. require File.dirname(__FILE__) + '/../abstract_unit'
  23654. class CallerController < ActionController::Base
  23655. def calling_from_controller
  23656. render_component(:controller => "callee", :action => "being_called")
  23657. end
  23658. def calling_from_controller_with_params
  23659. render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" })
  23660. end
  23661. def calling_from_controller_with_different_status_code
  23662. render_component(:controller => "callee", :action => "blowing_up")
  23663. end
  23664. def calling_from_template
  23665. render_template "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>"
  23666. end
  23667. def internal_caller
  23668. render_template "Are you there? <%= render_component(:action => 'internal_callee') %>"
  23669. end
  23670. def internal_callee
  23671. render_text "Yes, ma'am"
  23672. end
  23673. def set_flash
  23674. render_component(:controller => "callee", :action => "set_flash")
  23675. end
  23676. def use_flash
  23677. render_component(:controller => "callee", :action => "use_flash")
  23678. end
  23679. def calling_redirected
  23680. render_component(:controller => "callee", :action => "redirected")
  23681. end
  23682. def calling_redirected_as_string
  23683. render_template "<%= render_component(:controller => 'callee', :action => 'redirected') %>"
  23684. end
  23685. def rescue_action(e) raise end
  23686. end
  23687. class CalleeController < ActionController::Base
  23688. def being_called
  23689. render_text "#{@params["name"] || "Lady"} of the House, speaking"
  23690. end
  23691. def blowing_up
  23692. render_text "It's game over, man, just game over, man!", "500 Internal Server Error"
  23693. end
  23694. def set_flash
  23695. flash[:notice] = 'My stoney baby'
  23696. render :text => 'flash is set'
  23697. end
  23698. def use_flash
  23699. render :text => flash[:notice] || 'no flash'
  23700. end
  23701. def redirected
  23702. redirect_to :controller => "callee", :action => "being_called"
  23703. end
  23704. def rescue_action(e) raise end
  23705. end
  23706. class ComponentsTest < Test::Unit::TestCase
  23707. def setup
  23708. @controller = CallerController.new
  23709. @request = ActionController::TestRequest.new
  23710. @response = ActionController::TestResponse.new
  23711. end
  23712. def test_calling_from_controller
  23713. get :calling_from_controller
  23714. assert_equal "Lady of the House, speaking", @response.body
  23715. end
  23716. def test_calling_from_controller_with_params
  23717. get :calling_from_controller_with_params
  23718. assert_equal "David of the House, speaking", @response.body
  23719. end
  23720. def test_calling_from_controller_with_different_status_code
  23721. get :calling_from_controller_with_different_status_code
  23722. assert_equal 500, @response.response_code
  23723. end
  23724. def test_calling_from_template
  23725. get :calling_from_template
  23726. assert_equal "Ring, ring: Lady of the House, speaking", @response.body
  23727. end
  23728. def test_internal_calling
  23729. get :internal_caller
  23730. assert_equal "Are you there? Yes, ma'am", @response.body
  23731. end
  23732. def test_flash
  23733. get :set_flash
  23734. assert_equal 'My stoney baby', flash[:notice]
  23735. get :use_flash
  23736. assert_equal 'My stoney baby', @response.body
  23737. get :use_flash
  23738. assert_equal 'no flash', @response.body
  23739. end
  23740. def test_component_redirect_redirects
  23741. get :calling_redirected
  23742. assert_redirected_to :action => "being_called"
  23743. end
  23744. def test_component_multiple_redirect_redirects
  23745. test_component_redirect_redirects
  23746. test_internal_calling
  23747. end
  23748. def test_component_as_string_redirect_renders_redirecte_action
  23749. get :calling_redirected_as_string
  23750. assert_equal "Lady of the House, speaking", @response.body
  23751. end
  23752. endrequire File.dirname(__FILE__) + '/../abstract_unit'
  23753. class CookieTest < Test::Unit::TestCase
  23754. class TestController < ActionController::Base
  23755. def authenticate_with_deprecated_writer
  23756. cookie "name" => "user_name", "value" => "david"
  23757. render_text "hello world"
  23758. end
  23759. def authenticate
  23760. cookies["user_name"] = "david"
  23761. render_text "hello world"
  23762. end
  23763. def authenticate_for_fourten_days
  23764. cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
  23765. render_text "hello world"
  23766. end
  23767. def authenticate_for_fourten_days_with_symbols
  23768. cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) }
  23769. render_text "hello world"
  23770. end
  23771. def set_multiple_cookies
  23772. cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
  23773. cookies["login"] = "XJ-122"
  23774. render_text "hello world"
  23775. end
  23776. def access_frozen_cookies
  23777. @cookies["will"] = "work"
  23778. render_text "hello world"
  23779. end
  23780. def rescue_action(e) raise end
  23781. end
  23782. def setup
  23783. @request = ActionController::TestRequest.new
  23784. @response = ActionController::TestResponse.new
  23785. @request.host = "www.nextangle.com"
  23786. end
  23787. def test_setting_cookie_with_deprecated_writer
  23788. @request.action = "authenticate_with_deprecated_writer"
  23789. assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
  23790. end
  23791. def test_setting_cookie
  23792. @request.action = "authenticate"
  23793. assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
  23794. end
  23795. def test_setting_cookie_for_fourteen_days
  23796. @request.action = "authenticate_for_fourten_days"
  23797. assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"]
  23798. end
  23799. def test_setting_cookie_for_fourteen_days_with_symbols
  23800. @request.action = "authenticate_for_fourten_days"
  23801. assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"]
  23802. end
  23803. def test_multiple_cookies
  23804. @request.action = "set_multiple_cookies"
  23805. assert_equal 2, process_request.headers["cookie"].size
  23806. end
  23807. def test_setting_test_cookie
  23808. @request.action = "access_frozen_cookies"
  23809. assert_nothing_raised { process_request }
  23810. end
  23811. private
  23812. def process_request
  23813. TestController.process(@request, @response)
  23814. end
  23815. end
  23816. require File.dirname(__FILE__) + '/../abstract_unit'
  23817. class CustomHandler
  23818. def initialize( view )
  23819. @view = view
  23820. end
  23821. def render( template, local_assigns )
  23822. [ template,
  23823. local_assigns,
  23824. @view ]
  23825. end
  23826. end
  23827. class CustomHandlerTest < Test::Unit::TestCase
  23828. def setup
  23829. ActionView::Base.register_template_handler "foo", CustomHandler
  23830. ActionView::Base.register_template_handler :foo2, CustomHandler
  23831. @view = ActionView::Base.new
  23832. end
  23833. def test_custom_render
  23834. result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" )
  23835. assert_equal(
  23836. [ "hello <%= one %>", { :one => "two" }, @view ],
  23837. result )
  23838. end
  23839. def test_custom_render2
  23840. result = @view.render_template( "foo2", "hello <%= one %>", nil, :one => "two" )
  23841. assert_equal(
  23842. [ "hello <%= one %>", { :one => "two" }, @view ],
  23843. result )
  23844. end
  23845. def test_unhandled_extension
  23846. # uses the ERb handler by default if the extension isn't recognized
  23847. result = @view.render_template( "bar", "hello <%= one %>", nil, :one => "two" )
  23848. assert_equal "hello two", result
  23849. end
  23850. end
  23851. class << Object; alias_method :const_available?, :const_defined?; end
  23852. class ContentController < Class.new(ActionController::Base)
  23853. end
  23854. class NotAController
  23855. end
  23856. module Admin
  23857. class << self; alias_method :const_available?, :const_defined?; end
  23858. SomeConstant = 10
  23859. class UserController < Class.new(ActionController::Base); end
  23860. class NewsFeedController < Class.new(ActionController::Base); end
  23861. end
  23862. ActionController::Routing::Routes.draw do |map|
  23863. map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me'
  23864. map.connect ':controller/:action/:id'
  23865. end
  23866. require File.dirname(__FILE__) + '/../abstract_unit'
  23867. class FilterParamController < ActionController::Base
  23868. end
  23869. class FilterParamTest < Test::Unit::TestCase
  23870. def setup
  23871. @controller = FilterParamController.new
  23872. end
  23873. def test_filter_parameters
  23874. assert FilterParamController.respond_to?(:filter_parameter_logging)
  23875. assert !@controller.respond_to?(:filter_parameters)
  23876. FilterParamController.filter_parameter_logging
  23877. assert @controller.respond_to?(:filter_parameters)
  23878. test_hashes = [[{},{},[]],
  23879. [{'foo'=>'bar'},{'foo'=>'bar'},[]],
  23880. [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
  23881. [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
  23882. [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
  23883. [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
  23884. [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
  23885. [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']]
  23886. test_hashes.each do |before_filter, after_filter, filter_words|
  23887. FilterParamController.filter_parameter_logging(*filter_words)
  23888. assert_equal after_filter, @controller.filter_parameters(before_filter)
  23889. filter_words.push('blah')
  23890. FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
  23891. value.reverse! if key =~ /bargain/
  23892. end
  23893. before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
  23894. after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
  23895. assert_equal after_filter, @controller.filter_parameters(before_filter)
  23896. end
  23897. end
  23898. end
  23899. require File.dirname(__FILE__) + '/../abstract_unit'
  23900. class FilterTest < Test::Unit::TestCase
  23901. class TestController < ActionController::Base
  23902. before_filter :ensure_login
  23903. after_filter :clean_up
  23904. def show
  23905. render :inline => "ran action"
  23906. end
  23907. private
  23908. def ensure_login
  23909. @ran_filter ||= []
  23910. @ran_filter << "ensure_login"
  23911. end
  23912. def clean_up
  23913. @ran_after_filter ||= []
  23914. @ran_after_filter << "clean_up"
  23915. end
  23916. end
  23917. class RenderingController < ActionController::Base
  23918. before_filter :render_something_else
  23919. def show
  23920. @ran_action = true
  23921. render :inline => "ran action"
  23922. end
  23923. private
  23924. def render_something_else
  23925. render :inline => "something else"
  23926. end
  23927. end
  23928. class ConditionalFilterController < ActionController::Base
  23929. def show
  23930. render :inline => "ran action"
  23931. end
  23932. def another_action
  23933. render :inline => "ran action"
  23934. end
  23935. def show_without_filter
  23936. render :inline => "ran action without filter"
  23937. end
  23938. private
  23939. def ensure_login
  23940. @ran_filter ||= []
  23941. @ran_filter << "ensure_login"
  23942. end
  23943. def clean_up_tmp
  23944. @ran_filter ||= []
  23945. @ran_filter << "clean_up_tmp"
  23946. end
  23947. def rescue_action(e) raise(e) end
  23948. end
  23949. class ConditionalCollectionFilterController < ConditionalFilterController
  23950. before_filter :ensure_login, :except => [ :show_without_filter, :another_action ]
  23951. end
  23952. class OnlyConditionSymController < ConditionalFilterController
  23953. before_filter :ensure_login, :only => :show
  23954. end
  23955. class ExceptConditionSymController < ConditionalFilterController
  23956. before_filter :ensure_login, :except => :show_without_filter
  23957. end
  23958. class BeforeAndAfterConditionController < ConditionalFilterController
  23959. before_filter :ensure_login, :only => :show
  23960. after_filter :clean_up_tmp, :only => :show
  23961. end
  23962. class OnlyConditionProcController < ConditionalFilterController
  23963. before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true }
  23964. end
  23965. class ExceptConditionProcController < ConditionalFilterController
  23966. before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true }
  23967. end
  23968. class ConditionalClassFilter
  23969. def self.filter(controller) controller.assigns["ran_class_filter"] = true end
  23970. end
  23971. class OnlyConditionClassController < ConditionalFilterController
  23972. before_filter ConditionalClassFilter, :only => :show
  23973. end
  23974. class ExceptConditionClassController < ConditionalFilterController
  23975. before_filter ConditionalClassFilter, :except => :show_without_filter
  23976. end
  23977. class AnomolousYetValidConditionController < ConditionalFilterController
  23978. before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true}
  23979. end
  23980. class PrependingController < TestController
  23981. prepend_before_filter :wonderful_life
  23982. # skip_before_filter :fire_flash
  23983. private
  23984. def wonderful_life
  23985. @ran_filter ||= []
  23986. @ran_filter << "wonderful_life"
  23987. end
  23988. end
  23989. class ConditionalSkippingController < TestController
  23990. skip_before_filter :ensure_login, :only => [ :login ]
  23991. skip_after_filter :clean_up, :only => [ :login ]
  23992. before_filter :find_user, :only => [ :change_password ]
  23993. def login
  23994. render :inline => "ran action"
  23995. end
  23996. def change_password
  23997. render :inline => "ran action"
  23998. end
  23999. protected
  24000. def find_user
  24001. @ran_filter ||= []
  24002. @ran_filter << "find_user"
  24003. end
  24004. end
  24005. class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
  24006. before_filter :conditional_in_parent, :only => [:show, :another_action]
  24007. after_filter :conditional_in_parent, :only => [:show, :another_action]
  24008. private
  24009. def conditional_in_parent
  24010. @ran_filter ||= []
  24011. @ran_filter << 'conditional_in_parent'
  24012. end
  24013. end
  24014. class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
  24015. skip_before_filter :conditional_in_parent, :only => :another_action
  24016. skip_after_filter :conditional_in_parent, :only => :another_action
  24017. end
  24018. class ProcController < PrependingController
  24019. before_filter(proc { |c| c.assigns["ran_proc_filter"] = true })
  24020. end
  24021. class ImplicitProcController < PrependingController
  24022. before_filter { |c| c.assigns["ran_proc_filter"] = true }
  24023. end
  24024. class AuditFilter
  24025. def self.filter(controller)
  24026. controller.assigns["was_audited"] = true
  24027. end
  24028. end
  24029. class AroundFilter
  24030. def before(controller)
  24031. @execution_log = "before"
  24032. controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
  24033. controller.assigns["before_ran"] = true
  24034. end
  24035. def after(controller)
  24036. controller.assigns["execution_log"] = @execution_log + " and after"
  24037. controller.assigns["after_ran"] = true
  24038. controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
  24039. end
  24040. end
  24041. class AppendedAroundFilter
  24042. def before(controller)
  24043. controller.class.execution_log << " before appended aroundfilter "
  24044. end
  24045. def after(controller)
  24046. controller.class.execution_log << " after appended aroundfilter "
  24047. end
  24048. end
  24049. class AuditController < ActionController::Base
  24050. before_filter(AuditFilter)
  24051. def show
  24052. render_text "hello"
  24053. end
  24054. end
  24055. class BadFilterController < ActionController::Base
  24056. before_filter 2
  24057. def show() "show" end
  24058. protected
  24059. def rescue_action(e) raise(e) end
  24060. end
  24061. class AroundFilterController < PrependingController
  24062. around_filter AroundFilter.new
  24063. end
  24064. class MixedFilterController < PrependingController
  24065. cattr_accessor :execution_log
  24066. def initialize
  24067. @@execution_log = ""
  24068. end
  24069. before_filter { |c| c.class.execution_log << " before procfilter " }
  24070. prepend_around_filter AroundFilter.new
  24071. after_filter { |c| c.class.execution_log << " after procfilter " }
  24072. append_around_filter AppendedAroundFilter.new
  24073. end
  24074. class MixedSpecializationController < ActionController::Base
  24075. class OutOfOrder < StandardError; end
  24076. before_filter :first
  24077. before_filter :second, :only => :foo
  24078. def foo
  24079. render_text 'foo'
  24080. end
  24081. def bar
  24082. render_text 'bar'
  24083. end
  24084. protected
  24085. def first
  24086. @first = true
  24087. end
  24088. def second
  24089. raise OutOfOrder unless @first
  24090. end
  24091. end
  24092. class DynamicDispatchController < ActionController::Base
  24093. before_filter :choose
  24094. %w(foo bar baz).each do |action|
  24095. define_method(action) { render :text => action }
  24096. end
  24097. private
  24098. def choose
  24099. self.action_name = params[:choose]
  24100. end
  24101. end
  24102. def test_added_filter_to_inheritance_graph
  24103. assert_equal [ :ensure_login ], TestController.before_filters
  24104. end
  24105. def test_base_class_in_isolation
  24106. assert_equal [ ], ActionController::Base.before_filters
  24107. end
  24108. def test_prepending_filter
  24109. assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters
  24110. end
  24111. def test_running_filters
  24112. assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"]
  24113. end
  24114. def test_running_filters_with_proc
  24115. assert test_process(ProcController).template.assigns["ran_proc_filter"]
  24116. end
  24117. def test_running_filters_with_implicit_proc
  24118. assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"]
  24119. end
  24120. def test_running_filters_with_class
  24121. assert test_process(AuditController).template.assigns["was_audited"]
  24122. end
  24123. def test_running_anomolous_yet_valid_condition_filters
  24124. response = test_process(AnomolousYetValidConditionController)
  24125. assert_equal %w( ensure_login ), response.template.assigns["ran_filter"]
  24126. assert response.template.assigns["ran_class_filter"]
  24127. assert response.template.assigns["ran_proc_filter1"]
  24128. assert response.template.assigns["ran_proc_filter2"]
  24129. response = test_process(AnomolousYetValidConditionController, "show_without_filter")
  24130. assert_equal nil, response.template.assigns["ran_filter"]
  24131. assert !response.template.assigns["ran_class_filter"]
  24132. assert !response.template.assigns["ran_proc_filter1"]
  24133. assert !response.template.assigns["ran_proc_filter2"]
  24134. end
  24135. def test_running_collection_condition_filters
  24136. assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"]
  24137. assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"]
  24138. assert_equal nil, test_process(ConditionalCollectionFilterController, "another_action").template.assigns["ran_filter"]
  24139. end
  24140. def test_running_only_condition_filters
  24141. assert_equal %w( ensure_login ), test_process(OnlyConditionSymController).template.assigns["ran_filter"]
  24142. assert_equal nil, test_process(OnlyConditionSymController, "show_without_filter").template.assigns["ran_filter"]
  24143. assert test_process(OnlyConditionProcController).template.assigns["ran_proc_filter"]
  24144. assert !test_process(OnlyConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]
  24145. assert test_process(OnlyConditionClassController).template.assigns["ran_class_filter"]
  24146. assert !test_process(OnlyConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
  24147. end
  24148. def test_running_except_condition_filters
  24149. assert_equal %w( ensure_login ), test_process(ExceptConditionSymController).template.assigns["ran_filter"]
  24150. assert_equal nil, test_process(ExceptConditionSymController, "show_without_filter").template.assigns["ran_filter"]
  24151. assert test_process(ExceptConditionProcController).template.assigns["ran_proc_filter"]
  24152. assert !test_process(ExceptConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]
  24153. assert test_process(ExceptConditionClassController).template.assigns["ran_class_filter"]
  24154. assert !test_process(ExceptConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
  24155. end
  24156. def test_running_before_and_after_condition_filters
  24157. assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"]
  24158. assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"]
  24159. end
  24160. def test_bad_filter
  24161. assert_raises(ActionController::ActionControllerError) {
  24162. test_process(BadFilterController)
  24163. }
  24164. end
  24165. def test_around_filter
  24166. controller = test_process(AroundFilterController)
  24167. assert controller.template.assigns["before_ran"]
  24168. assert controller.template.assigns["after_ran"]
  24169. end
  24170. def test_having_properties_in_around_filter
  24171. controller = test_process(AroundFilterController)
  24172. assert_equal "before and after", controller.template.assigns["execution_log"]
  24173. end
  24174. def test_prepending_and_appending_around_filter
  24175. controller = test_process(MixedFilterController)
  24176. assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
  24177. " after appended aroundfilter after aroundfilter after procfilter ",
  24178. MixedFilterController.execution_log
  24179. end
  24180. def test_rendering_breaks_filtering_chain
  24181. response = test_process(RenderingController)
  24182. assert_equal "something else", response.body
  24183. assert !response.template.assigns["ran_action"]
  24184. end
  24185. def test_filters_with_mixed_specialization_run_in_order
  24186. assert_nothing_raised do
  24187. response = test_process(MixedSpecializationController, 'bar')
  24188. assert_equal 'bar', response.body
  24189. end
  24190. assert_nothing_raised do
  24191. response = test_process(MixedSpecializationController, 'foo')
  24192. assert_equal 'foo', response.body
  24193. end
  24194. end
  24195. def test_dynamic_dispatch
  24196. %w(foo bar baz).each do |action|
  24197. request = ActionController::TestRequest.new
  24198. request.query_parameters[:choose] = action
  24199. response = DynamicDispatchController.process(request, ActionController::TestResponse.new)
  24200. assert_equal action, response.body
  24201. end
  24202. end
  24203. def test_conditional_skipping_of_filters
  24204. assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"]
  24205. assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"]
  24206. assert_nil test_process(ConditionalSkippingController, "login").template.controller.instance_variable_get("@ran_after_filter")
  24207. assert_equal %w( clean_up ), test_process(ConditionalSkippingController, "change_password").template.controller.instance_variable_get("@ran_after_filter")
  24208. end
  24209. def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
  24210. assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter']
  24211. assert_nil test_process(ChildOfConditionalParentController, 'another_action').template.assigns['ran_filter']
  24212. end
  24213. private
  24214. def test_process(controller, action = "show")
  24215. request = ActionController::TestRequest.new
  24216. request.action = action
  24217. controller.process(request, ActionController::TestResponse.new)
  24218. end
  24219. end
  24220. require File.dirname(__FILE__) + '/../abstract_unit'
  24221. class FlashTest < Test::Unit::TestCase
  24222. class TestController < ActionController::Base
  24223. def set_flash
  24224. flash["that"] = "hello"
  24225. render :inline => "hello"
  24226. end
  24227. def set_flash_now
  24228. flash.now["that"] = "hello"
  24229. flash.now["foo"] ||= "bar"
  24230. flash.now["foo"] ||= "err"
  24231. @flashy = flash.now["that"]
  24232. @flash_copy = {}.update flash
  24233. render :inline => "hello"
  24234. end
  24235. def attempt_to_use_flash_now
  24236. @flash_copy = {}.update flash
  24237. @flashy = flash["that"]
  24238. render :inline => "hello"
  24239. end
  24240. def use_flash
  24241. @flash_copy = {}.update flash
  24242. @flashy = flash["that"]
  24243. render :inline => "hello"
  24244. end
  24245. def use_flash_and_keep_it
  24246. @flash_copy = {}.update flash
  24247. @flashy = flash["that"]
  24248. silence_warnings { keep_flash }
  24249. render :inline => "hello"
  24250. end
  24251. def rescue_action(e)
  24252. raise unless ActionController::MissingTemplate === e
  24253. end
  24254. end
  24255. def setup
  24256. @request = ActionController::TestRequest.new
  24257. @response = ActionController::TestResponse.new
  24258. @controller = TestController.new
  24259. end
  24260. def test_flash
  24261. get :set_flash
  24262. get :use_flash
  24263. assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
  24264. assert_equal "hello", @response.template.assigns["flashy"]
  24265. get :use_flash
  24266. assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash"
  24267. end
  24268. def test_keep_flash
  24269. get :set_flash
  24270. get :use_flash_and_keep_it
  24271. assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
  24272. assert_equal "hello", @response.template.assigns["flashy"]
  24273. get :use_flash
  24274. assert_equal "hello", @response.template.assigns["flash_copy"]["that"], "On second flash"
  24275. get :use_flash
  24276. assert_nil @response.template.assigns["flash_copy"]["that"], "On third flash"
  24277. end
  24278. def test_flash_now
  24279. get :set_flash_now
  24280. assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
  24281. assert_equal "bar" , @response.template.assigns["flash_copy"]["foo"]
  24282. assert_equal "hello", @response.template.assigns["flashy"]
  24283. get :attempt_to_use_flash_now
  24284. assert_nil @response.template.assigns["flash_copy"]["that"]
  24285. assert_nil @response.template.assigns["flash_copy"]["foo"]
  24286. assert_nil @response.template.assigns["flashy"]
  24287. end
  24288. endrequire File.dirname(__FILE__) + '/../abstract_unit'
  24289. MemCache = Struct.new(:MemCache, :address) unless Object.const_defined?(:MemCache)
  24290. class FragmentCacheStoreSettingTest < Test::Unit::TestCase
  24291. def teardown
  24292. ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new
  24293. end
  24294. def test_file_fragment_cache_store
  24295. ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
  24296. assert_kind_of(
  24297. ActionController::Caching::Fragments::FileStore,
  24298. ActionController::Base.fragment_cache_store
  24299. )
  24300. assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path
  24301. end
  24302. def test_drb_fragment_cache_store
  24303. ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
  24304. assert_kind_of(
  24305. ActionController::Caching::Fragments::DRbStore,
  24306. ActionController::Base.fragment_cache_store
  24307. )
  24308. assert_equal "druby://localhost:9192", ActionController::Base.fragment_cache_store.address
  24309. end
  24310. def test_mem_cache_fragment_cache_store
  24311. ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
  24312. assert_kind_of(
  24313. ActionController::Caching::Fragments::MemCacheStore,
  24314. ActionController::Base.fragment_cache_store
  24315. )
  24316. assert_equal %w(localhost), ActionController::Base.fragment_cache_store.addresses
  24317. end
  24318. def test_object_assigned_fragment_cache_store
  24319. ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
  24320. assert_kind_of(
  24321. ActionController::Caching::Fragments::FileStore,
  24322. ActionController::Base.fragment_cache_store
  24323. )
  24324. assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path
  24325. end
  24326. end
  24327. require File.dirname(__FILE__) + '/../abstract_unit'
  24328. class TestController < ActionController::Base
  24329. attr_accessor :delegate_attr
  24330. def delegate_method() end
  24331. def rescue_action(e) raise end
  24332. end
  24333. module Fun
  24334. class GamesController < ActionController::Base
  24335. def render_hello_world
  24336. render :inline => "hello: <%= stratego %>"
  24337. end
  24338. def rescue_action(e) raise end
  24339. end
  24340. class PDFController < ActionController::Base
  24341. def test
  24342. render :inline => "test: <%= foobar %>"
  24343. end
  24344. def rescue_action(e) raise end
  24345. end
  24346. end
  24347. module LocalAbcHelper
  24348. def a() end
  24349. def b() end
  24350. def c() end
  24351. end
  24352. class HelperTest < Test::Unit::TestCase
  24353. def setup
  24354. # Increment symbol counter.
  24355. @symbol = (@@counter ||= 'A0').succ!.dup
  24356. # Generate new controller class.
  24357. controller_class_name = "Helper#{@symbol}Controller"
  24358. eval("class #{controller_class_name} < TestController; end")
  24359. @controller_class = self.class.const_get(controller_class_name)
  24360. # Generate new template class and assign to controller.
  24361. template_class_name = "Test#{@symbol}View"
  24362. eval("class #{template_class_name} < ActionView::Base; end")
  24363. @template_class = self.class.const_get(template_class_name)
  24364. @controller_class.template_class = @template_class
  24365. # Set default test helper.
  24366. self.test_helper = LocalAbcHelper
  24367. end
  24368. def teardown
  24369. # Reset template class.
  24370. #ActionController::Base.template_class = ActionView::Base
  24371. end
  24372. def test_deprecated_helper
  24373. assert_equal expected_helper_methods, missing_methods
  24374. assert_nothing_raised { @controller_class.helper TestHelper }
  24375. assert_equal [], missing_methods
  24376. end
  24377. def test_declare_helper
  24378. require 'abc_helper'
  24379. self.test_helper = AbcHelper
  24380. assert_equal expected_helper_methods, missing_methods
  24381. assert_nothing_raised { @controller_class.helper :abc }
  24382. assert_equal [], missing_methods
  24383. end
  24384. def test_declare_missing_helper
  24385. assert_equal expected_helper_methods, missing_methods
  24386. assert_raise(MissingSourceFile) { @controller_class.helper :missing }
  24387. end
  24388. def test_declare_missing_file_from_helper
  24389. require 'broken_helper'
  24390. rescue LoadError => e
  24391. assert_nil /\bbroken_helper\b/.match(e.to_s)[1]
  24392. end
  24393. def test_helper_block
  24394. assert_nothing_raised {
  24395. @controller_class.helper { def block_helper_method; end }
  24396. }
  24397. assert master_helper_methods.include?('block_helper_method')
  24398. end
  24399. def test_helper_block_include
  24400. assert_equal expected_helper_methods, missing_methods
  24401. assert_nothing_raised {
  24402. @controller_class.helper { include TestHelper }
  24403. }
  24404. assert [], missing_methods
  24405. end
  24406. def test_helper_method
  24407. assert_nothing_raised { @controller_class.helper_method :delegate_method }
  24408. assert master_helper_methods.include?('delegate_method')
  24409. end
  24410. def test_helper_attr
  24411. assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
  24412. assert master_helper_methods.include?('delegate_attr')
  24413. assert master_helper_methods.include?('delegate_attr=')
  24414. end
  24415. def test_helper_for_nested_controller
  24416. request = ActionController::TestRequest.new
  24417. response = ActionController::TestResponse.new
  24418. request.action = 'render_hello_world'
  24419. assert_equal 'hello: Iz guuut!', Fun::GamesController.process(request, response).body
  24420. end
  24421. def test_helper_for_acronym_controller
  24422. request = ActionController::TestRequest.new
  24423. response = ActionController::TestResponse.new
  24424. request.action = 'test'
  24425. assert_equal 'test: baz', Fun::PDFController.process(request, response).body
  24426. end
  24427. private
  24428. def expected_helper_methods
  24429. TestHelper.instance_methods
  24430. end
  24431. def master_helper_methods
  24432. @controller_class.master_helper_module.instance_methods
  24433. end
  24434. def missing_methods
  24435. expected_helper_methods - master_helper_methods
  24436. end
  24437. def test_helper=(helper_module)
  24438. silence_warnings { self.class.const_set('TestHelper', helper_module) }
  24439. end
  24440. end
  24441. class IsolatedHelpersTest < Test::Unit::TestCase
  24442. class A < ActionController::Base
  24443. def index
  24444. render :inline => '<%= shout %>'
  24445. end
  24446. def rescue_action(e) raise end
  24447. end
  24448. class B < A
  24449. helper { def shout; 'B' end }
  24450. def index
  24451. render :inline => '<%= shout %>'
  24452. end
  24453. end
  24454. class C < A
  24455. helper { def shout; 'C' end }
  24456. def index
  24457. render :inline => '<%= shout %>'
  24458. end
  24459. end
  24460. def setup
  24461. @request = ActionController::TestRequest.new
  24462. @response = ActionController::TestResponse.new
  24463. @request.action = 'index'
  24464. end
  24465. def test_helper_in_a
  24466. assert_raise(NameError) { A.process(@request, @response) }
  24467. end
  24468. def test_helper_in_b
  24469. assert_equal 'B', B.process(@request, @response).body
  24470. end
  24471. def test_helper_in_c
  24472. assert_equal 'C', C.process(@request, @response).body
  24473. end
  24474. end
  24475. require File.dirname(__FILE__) + '/../abstract_unit'
  24476. # The template_root must be set on Base and not LayoutTest so that LayoutTest's inherited method has access to
  24477. # the template_root when looking for a layout
  24478. ActionController::Base.template_root = File.dirname(__FILE__) + '/../fixtures/layout_tests/'
  24479. class LayoutTest < ActionController::Base
  24480. def self.controller_path; 'views' end
  24481. end
  24482. # Restore template root to be unset
  24483. ActionController::Base.template_root = nil
  24484. class ProductController < LayoutTest
  24485. end
  24486. class ItemController < LayoutTest
  24487. end
  24488. class ThirdPartyTemplateLibraryController < LayoutTest
  24489. end
  24490. module ControllerNameSpace
  24491. end
  24492. class ControllerNameSpace::NestedController < LayoutTest
  24493. end
  24494. class MabView
  24495. def initialize(view)
  24496. end
  24497. def render(text, locals = {})
  24498. text
  24499. end
  24500. end
  24501. ActionView::Base::register_template_handler :mab, MabView
  24502. class LayoutAutoDiscoveryTest < Test::Unit::TestCase
  24503. def setup
  24504. @request = ActionController::TestRequest.new
  24505. @response = ActionController::TestResponse.new
  24506. @request.host = "www.nextangle.com"
  24507. end
  24508. def test_application_layout_is_default_when_no_controller_match
  24509. @controller = ProductController.new
  24510. get :hello
  24511. assert_equal 'layout_test.rhtml hello.rhtml', @response.body
  24512. end
  24513. def test_controller_name_layout_name_match
  24514. @controller = ItemController.new
  24515. get :hello
  24516. assert_equal 'item.rhtml hello.rhtml', @response.body
  24517. end
  24518. def test_third_party_template_library_auto_discovers_layout
  24519. @controller = ThirdPartyTemplateLibraryController.new
  24520. get :hello
  24521. assert_equal 'layouts/third_party_template_library', @controller.active_layout
  24522. assert_equal 'Mab', @response.body
  24523. end
  24524. def test_namespaced_controllers_auto_detect_layouts
  24525. @controller = ControllerNameSpace::NestedController.new
  24526. get :hello
  24527. assert_equal 'layouts/controller_name_space/nested', @controller.active_layout
  24528. assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body
  24529. end
  24530. endrequire File.dirname(__FILE__) + '/../abstract_unit'
  24531. class RespondToController < ActionController::Base
  24532. layout :set_layout
  24533. def html_xml_or_rss
  24534. respond_to do |type|
  24535. type.html { render :text => "HTML" }
  24536. type.xml { render :text => "XML" }
  24537. type.rss { render :text => "RSS" }
  24538. type.all { render :text => "Nothing" }
  24539. end
  24540. end
  24541. def js_or_html
  24542. respond_to do |type|
  24543. type.html { render :text => "HTML" }
  24544. type.js { render :text => "JS" }
  24545. type.all { render :text => "Nothing" }
  24546. end
  24547. end
  24548. def html_or_xml
  24549. respond_to do |type|
  24550. type.html { render :text => "HTML" }
  24551. type.xml { render :text => "XML" }
  24552. type.all { render :text => "Nothing" }
  24553. end
  24554. end
  24555. def just_xml
  24556. respond_to do |type|
  24557. type.xml { render :text => "XML" }
  24558. end
  24559. end
  24560. def using_defaults
  24561. respond_to do |type|
  24562. type.html
  24563. type.js
  24564. type.xml
  24565. end
  24566. end
  24567. def using_defaults_with_type_list
  24568. respond_to(:html, :js, :xml)
  24569. end
  24570. def made_for_content_type
  24571. respond_to do |type|
  24572. type.rss { render :text => "RSS" }
  24573. type.atom { render :text => "ATOM" }
  24574. type.all { render :text => "Nothing" }
  24575. end
  24576. end
  24577. def custom_type_handling
  24578. respond_to do |type|
  24579. type.html { render :text => "HTML" }
  24580. type.custom("application/crazy-xml") { render :text => "Crazy XML" }
  24581. type.all { render :text => "Nothing" }
  24582. end
  24583. end
  24584. def handle_any
  24585. respond_to do |type|
  24586. type.html { render :text => "HTML" }
  24587. type.any(:js, :xml) { render :text => "Either JS or XML" }
  24588. end
  24589. end
  24590. def all_types_with_layout
  24591. respond_to do |type|
  24592. type.html
  24593. type.js
  24594. end
  24595. end
  24596. def rescue_action(e)
  24597. raise
  24598. end
  24599. protected
  24600. def set_layout
  24601. if action_name == "all_types_with_layout"
  24602. "standard"
  24603. end
  24604. end
  24605. end
  24606. RespondToController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  24607. class MimeControllerTest < Test::Unit::TestCase
  24608. def setup
  24609. @request = ActionController::TestRequest.new
  24610. @response = ActionController::TestResponse.new
  24611. @controller = RespondToController.new
  24612. @request.host = "www.example.com"
  24613. end
  24614. def test_html
  24615. @request.env["HTTP_ACCEPT"] = "text/html"
  24616. get :js_or_html
  24617. assert_equal 'HTML', @response.body
  24618. get :html_or_xml
  24619. assert_equal 'HTML', @response.body
  24620. get :just_xml
  24621. assert_response 406
  24622. end
  24623. def test_all
  24624. @request.env["HTTP_ACCEPT"] = "*/*"
  24625. get :js_or_html
  24626. assert_equal 'HTML', @response.body # js is not part of all
  24627. get :html_or_xml
  24628. assert_equal 'HTML', @response.body
  24629. get :just_xml
  24630. assert_equal 'XML', @response.body
  24631. end
  24632. def test_xml
  24633. @request.env["HTTP_ACCEPT"] = "application/xml"
  24634. get :html_xml_or_rss
  24635. assert_equal 'XML', @response.body
  24636. end
  24637. def test_js_or_html
  24638. @request.env["HTTP_ACCEPT"] = "text/javascript, text/html"
  24639. get :js_or_html
  24640. assert_equal 'JS', @response.body
  24641. get :html_or_xml
  24642. assert_equal 'HTML', @response.body
  24643. get :just_xml
  24644. assert_response 406
  24645. end
  24646. def test_js_or_anything
  24647. @request.env["HTTP_ACCEPT"] = "text/javascript, */*"
  24648. get :js_or_html
  24649. assert_equal 'JS', @response.body
  24650. get :html_or_xml
  24651. assert_equal 'HTML', @response.body
  24652. get :just_xml
  24653. assert_equal 'XML', @response.body
  24654. end
  24655. def test_using_defaults
  24656. @request.env["HTTP_ACCEPT"] = "*/*"
  24657. get :using_defaults
  24658. assert_equal 'Hello world!', @response.body
  24659. @request.env["HTTP_ACCEPT"] = "text/javascript"
  24660. get :using_defaults
  24661. assert_equal '$("body").visualEffect("highlight");', @response.body
  24662. @request.env["HTTP_ACCEPT"] = "application/xml"
  24663. get :using_defaults
  24664. assert_equal "<p>Hello world!</p>\n", @response.body
  24665. end
  24666. def test_using_defaults_with_type_list
  24667. @request.env["HTTP_ACCEPT"] = "*/*"
  24668. get :using_defaults_with_type_list
  24669. assert_equal 'Hello world!', @response.body
  24670. @request.env["HTTP_ACCEPT"] = "text/javascript"
  24671. get :using_defaults_with_type_list
  24672. assert_equal '$("body").visualEffect("highlight");', @response.body
  24673. @request.env["HTTP_ACCEPT"] = "application/xml"
  24674. get :using_defaults_with_type_list
  24675. assert_equal "<p>Hello world!</p>\n", @response.body
  24676. end
  24677. def test_with_content_type
  24678. @request.env["CONTENT_TYPE"] = "application/atom+xml"
  24679. get :made_for_content_type
  24680. assert_equal "ATOM", @response.body
  24681. @request.env["CONTENT_TYPE"] = "application/rss+xml"
  24682. get :made_for_content_type
  24683. assert_equal "RSS", @response.body
  24684. end
  24685. def test_synonyms
  24686. @request.env["HTTP_ACCEPT"] = "application/javascript"
  24687. get :js_or_html
  24688. assert_equal 'JS', @response.body
  24689. @request.env["HTTP_ACCEPT"] = "application/x-xml"
  24690. get :html_xml_or_rss
  24691. assert_equal "XML", @response.body
  24692. end
  24693. def test_custom_types
  24694. @request.env["HTTP_ACCEPT"] = "application/crazy-xml"
  24695. get :custom_type_handling
  24696. assert_equal 'Crazy XML', @response.body
  24697. @request.env["HTTP_ACCEPT"] = "text/html"
  24698. get :custom_type_handling
  24699. assert_equal 'HTML', @response.body
  24700. end
  24701. def test_xhtml_alias
  24702. @request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml"
  24703. get :html_or_xml
  24704. assert_equal 'HTML', @response.body
  24705. end
  24706. def test_firefox_simulation
  24707. @request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
  24708. get :html_or_xml
  24709. assert_equal 'HTML', @response.body
  24710. end
  24711. def test_handle_any
  24712. @request.env["HTTP_ACCEPT"] = "*/*"
  24713. get :handle_any
  24714. assert_equal 'HTML', @response.body
  24715. @request.env["HTTP_ACCEPT"] = "text/javascript"
  24716. get :handle_any
  24717. assert_equal 'Either JS or XML', @response.body
  24718. @request.env["HTTP_ACCEPT"] = "text/xml"
  24719. get :handle_any
  24720. assert_equal 'Either JS or XML', @response.body
  24721. end
  24722. def test_all_types_with_layout
  24723. @request.env["HTTP_ACCEPT"] = "text/javascript"
  24724. get :all_types_with_layout
  24725. assert_equal 'RJS for all_types_with_layout', @response.body
  24726. @request.env["HTTP_ACCEPT"] = "text/html"
  24727. get :all_types_with_layout
  24728. assert_equal '<html>HTML for all_types_with_layout</html>', @response.body
  24729. end
  24730. def test_xhr
  24731. xhr :get, :js_or_html
  24732. assert_equal 'JS', @response.body
  24733. xhr :get, :using_defaults
  24734. assert_equal '$("body").visualEffect("highlight");', @response.body
  24735. end
  24736. end
  24737. require File.dirname(__FILE__) + '/../abstract_unit'
  24738. class MimeTypeTest < Test::Unit::TestCase
  24739. Mime::PNG = Mime::Type.new("image/png")
  24740. Mime::PLAIN = Mime::Type.new("text/plain")
  24741. def test_parse_single
  24742. Mime::LOOKUP.keys.each do |mime_type|
  24743. assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
  24744. end
  24745. end
  24746. def test_parse_without_q
  24747. accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,*/*"
  24748. expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::PLAIN, Mime::ALL]
  24749. assert_equal expect, Mime::Type.parse(accept)
  24750. end
  24751. def test_parse_with_q
  24752. accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,*/*; q=0.2"
  24753. expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PLAIN, Mime::YAML, Mime::ALL]
  24754. assert_equal expect, Mime::Type.parse(accept)
  24755. end
  24756. endrequire File.dirname(__FILE__) + '/../abstract_unit'
  24757. silence_warnings { Customer = Struct.new("Customer", :name) }
  24758. module Fun
  24759. class GamesController < ActionController::Base
  24760. def hello_world
  24761. end
  24762. end
  24763. end
  24764. module NewRenderTestHelper
  24765. def rjs_helper_method_from_module
  24766. page.visual_effect :highlight
  24767. end
  24768. end
  24769. class NewRenderTestController < ActionController::Base
  24770. layout :determine_layout
  24771. def self.controller_name; "test"; end
  24772. def self.controller_path; "test"; end
  24773. def hello_world
  24774. end
  24775. def render_hello_world
  24776. render :template => "test/hello_world"
  24777. end
  24778. def render_hello_world_from_variable
  24779. @person = "david"
  24780. render :text => "hello #{@person}"
  24781. end
  24782. def render_action_hello_world
  24783. render :action => "hello_world"
  24784. end
  24785. def render_action_hello_world_as_symbol
  24786. render :action => :hello_world
  24787. end
  24788. def render_text_hello_world
  24789. render :text => "hello world"
  24790. end
  24791. def render_text_hello_world_with_layout
  24792. @variable_for_layout = ", I'm here!"
  24793. render :text => "hello world", :layout => true
  24794. end
  24795. def hello_world_with_layout_false
  24796. render :layout => false
  24797. end
  24798. def render_custom_code
  24799. render :text => "hello world", :status => "404 Moved"
  24800. end
  24801. def render_file_with_instance_variables
  24802. @secret = 'in the sauce'
  24803. path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.rhtml')
  24804. render :file => path
  24805. end
  24806. def render_file_with_locals
  24807. path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.rhtml')
  24808. render :file => path, :locals => {:secret => 'in the sauce'}
  24809. end
  24810. def render_file_not_using_full_path
  24811. @secret = 'in the sauce'
  24812. render :file => 'test/render_file_with_ivar', :use_full_path => true
  24813. end
  24814. def render_file_not_using_full_path_with_relative_path
  24815. @secret = 'in the sauce'
  24816. render :file => 'test/../test/render_file_with_ivar', :use_full_path => true
  24817. end
  24818. def render_file_not_using_full_path_with_dot_in_path
  24819. @secret = 'in the sauce'
  24820. render :file => 'test/dot.directory/render_file_with_ivar', :use_full_path => true
  24821. end
  24822. def render_xml_hello
  24823. @name = "David"
  24824. render :template => "test/hello"
  24825. end
  24826. def greeting
  24827. # let's just rely on the template
  24828. end
  24829. def layout_test
  24830. render :action => "hello_world"
  24831. end
  24832. def layout_test_with_different_layout
  24833. render :action => "hello_world", :layout => "standard"
  24834. end
  24835. def rendering_without_layout
  24836. render :action => "hello_world", :layout => false
  24837. end
  24838. def layout_overriding_layout
  24839. render :action => "hello_world", :layout => "standard"
  24840. end
  24841. def rendering_nothing_on_layout
  24842. render :nothing => true
  24843. end
  24844. def builder_layout_test
  24845. render :action => "hello"
  24846. end
  24847. def partials_list
  24848. @test_unchanged = 'hello'
  24849. @customers = [ Customer.new("david"), Customer.new("mary") ]
  24850. render :action => "list"
  24851. end
  24852. def partial_only
  24853. render :partial => true
  24854. end
  24855. def partial_only_with_layout
  24856. render :partial => "partial_only", :layout => true
  24857. end
  24858. def partial_with_locals
  24859. render :partial => "customer", :locals => { :customer => Customer.new("david") }
  24860. end
  24861. def partial_collection
  24862. render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
  24863. end
  24864. def partial_collection_with_locals
  24865. render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
  24866. end
  24867. def empty_partial_collection
  24868. render :partial => "customer", :collection => []
  24869. end
  24870. def partial_with_hash_object
  24871. render :partial => "hash_object", :object => {:first_name => "Sam"}
  24872. end
  24873. def partial_with_implicit_local_assignment
  24874. @customer = Customer.new("Marcel")
  24875. render :partial => "customer"
  24876. end
  24877. def hello_in_a_string
  24878. @customers = [ Customer.new("david"), Customer.new("mary") ]
  24879. render :text => "How's there? #{render_to_string("test/list")}"
  24880. end
  24881. def accessing_params_in_template
  24882. render :inline => "Hello: <%= params[:name] %>"
  24883. end
  24884. def accessing_params_in_template_with_layout
  24885. render :layout => nil, :inline => "Hello: <%= params[:name] %>"
  24886. end
  24887. def render_with_explicit_template
  24888. render "test/hello_world"
  24889. end
  24890. def double_render
  24891. render :text => "hello"
  24892. render :text => "world"
  24893. end
  24894. def double_redirect
  24895. redirect_to :action => "double_render"
  24896. redirect_to :action => "double_render"
  24897. end
  24898. def render_and_redirect
  24899. render :text => "hello"
  24900. redirect_to :action => "double_render"
  24901. end
  24902. def rendering_with_conflicting_local_vars
  24903. @name = "David"
  24904. def @template.name() nil end
  24905. render :action => "potential_conflicts"
  24906. end
  24907. def hello_world_from_rxml_using_action
  24908. render :action => "hello_world.rxml"
  24909. end
  24910. def hello_world_from_rxml_using_template
  24911. render :template => "test/hello_world.rxml"
  24912. end
  24913. helper NewRenderTestHelper
  24914. helper do
  24915. def rjs_helper_method(value)
  24916. page.visual_effect :highlight, value
  24917. end
  24918. end
  24919. def enum_rjs_test
  24920. render :update do |page|
  24921. page.select('.product').each do |value|
  24922. page.rjs_helper_method_from_module
  24923. page.rjs_helper_method(value)
  24924. page.sortable(value, :url => { :action => "order" })
  24925. page.draggable(value)
  24926. end
  24927. end
  24928. end
  24929. def delete_with_js
  24930. @project_id = 4
  24931. end
  24932. def render_js_with_explicit_template
  24933. @project_id = 4
  24934. render :template => 'test/delete_with_js'
  24935. end
  24936. def render_js_with_explicit_action_template
  24937. @project_id = 4
  24938. render :action => 'delete_with_js'
  24939. end
  24940. def update_page
  24941. render :update do |page|
  24942. page.replace_html 'balance', '$37,000,000.00'
  24943. page.visual_effect :highlight, 'balance'
  24944. end
  24945. end
  24946. def update_page_with_instance_variables
  24947. @money = '$37,000,000.00'
  24948. @div_id = 'balance'
  24949. render :update do |page|
  24950. page.replace_html @div_id, @money
  24951. page.visual_effect :highlight, @div_id
  24952. end
  24953. end
  24954. def action_talk_to_layout
  24955. # Action template sets variable that's picked up by layout
  24956. end
  24957. def render_text_with_assigns
  24958. @hello = "world"
  24959. render :text => "foo"
  24960. end
  24961. def yield_content_for
  24962. render :action => "content_for", :layout => "yield"
  24963. end
  24964. def rescue_action(e) raise end
  24965. private
  24966. def determine_layout
  24967. case action_name
  24968. when "hello_world", "layout_test", "rendering_without_layout",
  24969. "rendering_nothing_on_layout", "render_text_hello_world",
  24970. "render_text_hello_world_with_layout",
  24971. "hello_world_with_layout_false",
  24972. "partial_only", "partial_only_with_layout",
  24973. "accessing_params_in_template",
  24974. "accessing_params_in_template_with_layout",
  24975. "render_with_explicit_template",
  24976. "render_js_with_explicit_template",
  24977. "render_js_with_explicit_action_template",
  24978. "delete_with_js", "update_page", "update_page_with_instance_variables"
  24979. "layouts/standard"
  24980. when "builder_layout_test"
  24981. "layouts/builder"
  24982. when "action_talk_to_layout", "layout_overriding_layout"
  24983. "layouts/talk_from_action"
  24984. end
  24985. end
  24986. end
  24987. NewRenderTestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  24988. Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  24989. class NewRenderTest < Test::Unit::TestCase
  24990. def setup
  24991. @controller = NewRenderTestController.new
  24992. # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
  24993. # a more accurate simulation of what happens in "real life".
  24994. @controller.logger = Logger.new(nil)
  24995. @request = ActionController::TestRequest.new
  24996. @response = ActionController::TestResponse.new
  24997. @request.host = "www.nextangle.com"
  24998. end
  24999. def test_simple_show
  25000. get :hello_world
  25001. assert_response :success
  25002. assert_template "test/hello_world"
  25003. assert_equal "<html>Hello world!</html>", @response.body
  25004. end
  25005. def test_do_with_render
  25006. get :render_hello_world
  25007. assert_template "test/hello_world"
  25008. end
  25009. def test_do_with_render_from_variable
  25010. get :render_hello_world_from_variable
  25011. assert_equal "hello david", @response.body
  25012. end
  25013. def test_do_with_render_action
  25014. get :render_action_hello_world
  25015. assert_template "test/hello_world"
  25016. end
  25017. def test_do_with_render_action_as_symbol
  25018. get :render_action_hello_world_as_symbol
  25019. assert_template "test/hello_world"
  25020. end
  25021. def test_do_with_render_text
  25022. get :render_text_hello_world
  25023. assert_equal "hello world", @response.body
  25024. end
  25025. def test_do_with_render_text_and_layout
  25026. get :render_text_hello_world_with_layout
  25027. assert_equal "<html>hello world, I'm here!</html>", @response.body
  25028. end
  25029. def test_do_with_render_action_and_layout_false
  25030. get :hello_world_with_layout_false
  25031. assert_equal 'Hello world!', @response.body
  25032. end
  25033. def test_do_with_render_custom_code
  25034. get :render_custom_code
  25035. assert_response :missing
  25036. end
  25037. def test_render_file_with_instance_variables
  25038. get :render_file_with_instance_variables
  25039. assert_equal "The secret is in the sauce\n", @response.body
  25040. end
  25041. def test_render_file_not_using_full_path
  25042. get :render_file_not_using_full_path
  25043. assert_equal "The secret is in the sauce\n", @response.body
  25044. end
  25045. def test_render_file_not_using_full_path_with_relative_path
  25046. get :render_file_not_using_full_path_with_relative_path
  25047. assert_equal "The secret is in the sauce\n", @response.body
  25048. end
  25049. def test_render_file_not_using_full_path_with_dot_in_path
  25050. get :render_file_not_using_full_path_with_dot_in_path
  25051. assert_equal "The secret is in the sauce\n", @response.body
  25052. end
  25053. def test_render_file_with_locals
  25054. get :render_file_with_locals
  25055. assert_equal "The secret is in the sauce\n", @response.body
  25056. end
  25057. def test_attempt_to_access_object_method
  25058. assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
  25059. end
  25060. def test_private_methods
  25061. assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
  25062. end
  25063. def test_access_to_request_in_view
  25064. view_internals_old_value = ActionController::Base.view_controller_internals
  25065. ActionController::Base.view_controller_internals = false
  25066. ActionController::Base.protected_variables_cache = nil
  25067. get :hello_world
  25068. assert_nil(assigns["request"])
  25069. ActionController::Base.view_controller_internals = true
  25070. ActionController::Base.protected_variables_cache = nil
  25071. get :hello_world
  25072. assert_kind_of ActionController::AbstractRequest, assigns["request"]
  25073. ActionController::Base.view_controller_internals = view_internals_old_value
  25074. ActionController::Base.protected_variables_cache = nil
  25075. end
  25076. def test_render_xml
  25077. get :render_xml_hello
  25078. assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
  25079. end
  25080. def test_enum_rjs_test
  25081. get :enum_rjs_test
  25082. assert_equal <<-EOS.strip, @response.body
  25083. $$(".product").each(function(value, index) {
  25084. new Effect.Highlight(element,{});
  25085. new Effect.Highlight(value,{});
  25086. Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}});
  25087. new Draggable(value, {});
  25088. });
  25089. EOS
  25090. end
  25091. def test_render_xml_with_default
  25092. get :greeting
  25093. assert_equal "<p>This is grand!</p>\n", @response.body
  25094. end
  25095. def test_render_rjs_with_default
  25096. get :delete_with_js
  25097. assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body
  25098. end
  25099. def test_render_rjs_template_explicitly
  25100. get :render_js_with_explicit_template
  25101. assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body
  25102. end
  25103. def test_rendering_rjs_action_explicitly
  25104. get :render_js_with_explicit_action_template
  25105. assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body
  25106. end
  25107. def test_layout_rendering
  25108. get :layout_test
  25109. assert_equal "<html>Hello world!</html>", @response.body
  25110. end
  25111. def test_layout_test_with_different_layout
  25112. get :layout_test_with_different_layout
  25113. assert_equal "<html>Hello world!</html>", @response.body
  25114. end
  25115. def test_rendering_without_layout
  25116. get :rendering_without_layout
  25117. assert_equal "Hello world!", @response.body
  25118. end
  25119. def test_layout_overriding_layout
  25120. get :layout_overriding_layout
  25121. assert_no_match %r{<title>}, @response.body
  25122. end
  25123. def test_rendering_nothing_on_layout
  25124. get :rendering_nothing_on_layout
  25125. assert_equal " ", @response.body
  25126. end
  25127. def test_render_xml_with_layouts
  25128. get :builder_layout_test
  25129. assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
  25130. end
  25131. def test_partial_only
  25132. get :partial_only
  25133. assert_equal "only partial", @response.body
  25134. end
  25135. def test_partial_only_with_layout
  25136. get :partial_only_with_layout
  25137. assert_equal "<html>only partial</html>", @response.body
  25138. end
  25139. def test_render_to_string
  25140. get :hello_in_a_string
  25141. assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
  25142. end
  25143. def test_nested_rendering
  25144. get :hello_world
  25145. assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body
  25146. end
  25147. def test_accessing_params_in_template
  25148. get :accessing_params_in_template, :name => "David"
  25149. assert_equal "Hello: David", @response.body
  25150. end
  25151. def test_accessing_params_in_template_with_layout
  25152. get :accessing_params_in_template_with_layout, :name => "David"
  25153. assert_equal "<html>Hello: David</html>", @response.body
  25154. end
  25155. def test_render_with_explicit_template
  25156. get :render_with_explicit_template
  25157. assert_response :success
  25158. end
  25159. def test_double_render
  25160. assert_raises(ActionController::DoubleRenderError) { get :double_render }
  25161. end
  25162. def test_double_redirect
  25163. assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
  25164. end
  25165. def test_render_and_redirect
  25166. assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
  25167. end
  25168. def test_rendering_with_conflicting_local_vars
  25169. get :rendering_with_conflicting_local_vars
  25170. assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
  25171. end
  25172. def test_action_talk_to_layout
  25173. get :action_talk_to_layout
  25174. assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
  25175. end
  25176. def test_partials_list
  25177. get :partials_list
  25178. assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
  25179. end
  25180. def test_partial_with_locals
  25181. get :partial_with_locals
  25182. assert_equal "Hello: david", @response.body
  25183. end
  25184. def test_partial_collection
  25185. get :partial_collection
  25186. assert_equal "Hello: davidHello: mary", @response.body
  25187. end
  25188. def test_partial_collection_with_locals
  25189. get :partial_collection_with_locals
  25190. assert_equal "Bonjour: davidBonjour: mary", @response.body
  25191. end
  25192. def test_empty_partial_collection
  25193. get :empty_partial_collection
  25194. assert_equal " ", @response.body
  25195. end
  25196. def test_partial_with_hash_object
  25197. get :partial_with_hash_object
  25198. assert_equal "Sam", @response.body
  25199. end
  25200. def test_partial_with_implicit_local_assignment
  25201. get :partial_with_implicit_local_assignment
  25202. assert_equal "Hello: Marcel", @response.body
  25203. end
  25204. def test_render_text_with_assigns
  25205. get :render_text_with_assigns
  25206. assert_equal "world", assigns["hello"]
  25207. end
  25208. def test_update_page
  25209. get :update_page
  25210. assert_template nil
  25211. assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type']
  25212. assert_equal 2, @response.body.split($/).length
  25213. end
  25214. def test_update_page_with_instance_variables
  25215. get :update_page_with_instance_variables
  25216. assert_template nil
  25217. assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type']
  25218. assert_match /balance/, @response.body
  25219. assert_match /\$37/, @response.body
  25220. end
  25221. def test_yield_content_for
  25222. get :yield_content_for
  25223. assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
  25224. end
  25225. def test_overwritting_rendering_relative_file_with_extension
  25226. get :hello_world_from_rxml_using_template
  25227. assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
  25228. get :hello_world_from_rxml_using_action
  25229. assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
  25230. end
  25231. end
  25232. require 'test/unit'
  25233. require 'cgi'
  25234. require 'stringio'
  25235. require File.dirname(__FILE__) + '/../../lib/action_controller/cgi_ext/raw_post_data_fix'
  25236. class RawPostDataTest < Test::Unit::TestCase
  25237. def setup
  25238. ENV['REQUEST_METHOD'] = 'POST'
  25239. ENV['CONTENT_TYPE'] = ''
  25240. ENV['CONTENT_LENGTH'] = '0'
  25241. end
  25242. def test_raw_post_data
  25243. process_raw "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
  25244. end
  25245. private
  25246. def process_raw(query_string)
  25247. old_stdin = $stdin
  25248. begin
  25249. $stdin = StringIO.new(query_string.dup)
  25250. ENV['CONTENT_LENGTH'] = $stdin.size.to_s
  25251. CGI.new
  25252. assert_not_nil ENV['RAW_POST_DATA']
  25253. assert ENV['RAW_POST_DATA'].frozen?
  25254. assert_equal query_string, ENV['RAW_POST_DATA']
  25255. ensure
  25256. $stdin = old_stdin
  25257. end
  25258. end
  25259. end
  25260. require File.dirname(__FILE__) + '/../abstract_unit'
  25261. class RedirectController < ActionController::Base
  25262. def simple_redirect
  25263. redirect_to :action => "hello_world"
  25264. end
  25265. def method_redirect
  25266. redirect_to :dashbord_url, 1, "hello"
  25267. end
  25268. def host_redirect
  25269. redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host'
  25270. end
  25271. def module_redirect
  25272. redirect_to :controller => 'module_test/module_redirect', :action => "hello_world"
  25273. end
  25274. def redirect_with_assigns
  25275. @hello = "world"
  25276. redirect_to :action => "hello_world"
  25277. end
  25278. def redirect_to_back
  25279. redirect_to :back
  25280. end
  25281. def rescue_errors(e) raise e end
  25282. def rescue_action(e) raise end
  25283. protected
  25284. def dashbord_url(id, message)
  25285. url_for :action => "dashboard", :params => { "id" => id, "message" => message }
  25286. end
  25287. end
  25288. class RedirectTest < Test::Unit::TestCase
  25289. def setup
  25290. @controller = RedirectController.new
  25291. @request = ActionController::TestRequest.new
  25292. @response = ActionController::TestResponse.new
  25293. end
  25294. def test_simple_redirect
  25295. get :simple_redirect
  25296. assert_redirect_url "http://test.host/redirect/hello_world"
  25297. end
  25298. def test_redirect_with_method_reference_and_parameters
  25299. get :method_redirect
  25300. assert_redirect_url "http://test.host/redirect/dashboard/1?message=hello"
  25301. end
  25302. def test_simple_redirect_using_options
  25303. get :host_redirect
  25304. assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
  25305. end
  25306. def test_redirect_error_with_pretty_diff
  25307. get :host_redirect
  25308. begin
  25309. assert_redirected_to :action => "other_host", :only_path => true
  25310. rescue Test::Unit::AssertionFailedError => err
  25311. redirection_msg, diff_msg = err.message.scan(/<\{[^\}]+\}>/).collect { |s| s[2..-3] }
  25312. assert_match %r(:only_path=>false), redirection_msg
  25313. assert_match %r(:host=>"other.test.host"), redirection_msg
  25314. assert_match %r(:action=>"other_host"), redirection_msg
  25315. assert_match %r(:only_path=>true), diff_msg
  25316. assert_match %r(:host=>"other.test.host"), diff_msg
  25317. end
  25318. end
  25319. def test_module_redirect
  25320. get :module_redirect
  25321. assert_redirect_url "http://test.host/module_test/module_redirect/hello_world"
  25322. end
  25323. def test_module_redirect_using_options
  25324. get :module_redirect
  25325. assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world'
  25326. end
  25327. def test_redirect_with_assigns
  25328. get :redirect_with_assigns
  25329. assert_equal "world", assigns["hello"]
  25330. end
  25331. def test_redirect_to_back
  25332. @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
  25333. get :redirect_to_back
  25334. assert_redirect_url "http://www.example.com/coming/from"
  25335. end
  25336. def test_redirect_to_back_with_no_referer
  25337. assert_raises(ActionController::RedirectBackError) {
  25338. @request.env["HTTP_REFERER"] = nil
  25339. get :redirect_to_back
  25340. }
  25341. end
  25342. end
  25343. module ModuleTest
  25344. class ModuleRedirectController < ::RedirectController
  25345. def module_redirect
  25346. redirect_to :controller => '/redirect', :action => "hello_world"
  25347. end
  25348. end
  25349. class ModuleRedirectTest < Test::Unit::TestCase
  25350. def setup
  25351. @controller = ModuleRedirectController.new
  25352. @request = ActionController::TestRequest.new
  25353. @response = ActionController::TestResponse.new
  25354. end
  25355. def test_simple_redirect
  25356. get :simple_redirect
  25357. assert_redirect_url "http://test.host/module_test/module_redirect/hello_world"
  25358. end
  25359. def test_redirect_with_method_reference_and_parameters
  25360. get :method_redirect
  25361. assert_redirect_url "http://test.host/module_test/module_redirect/dashboard/1?message=hello"
  25362. end
  25363. def test_simple_redirect_using_options
  25364. get :host_redirect
  25365. assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
  25366. end
  25367. def test_module_redirect
  25368. get :module_redirect
  25369. assert_redirect_url "http://test.host/redirect/hello_world"
  25370. end
  25371. def test_module_redirect_using_options
  25372. get :module_redirect
  25373. assert_redirected_to :controller => 'redirect', :action => "hello_world"
  25374. end
  25375. end
  25376. end
  25377. require File.dirname(__FILE__) + '/../abstract_unit'
  25378. unless defined?(Customer)
  25379. Customer = Struct.new("Customer", :name)
  25380. end
  25381. module Fun
  25382. class GamesController < ActionController::Base
  25383. def hello_world
  25384. end
  25385. end
  25386. end
  25387. class TestController < ActionController::Base
  25388. layout :determine_layout
  25389. def hello_world
  25390. end
  25391. def render_hello_world
  25392. render "test/hello_world"
  25393. end
  25394. def render_hello_world_from_variable
  25395. @person = "david"
  25396. render_text "hello #{@person}"
  25397. end
  25398. def render_action_hello_world
  25399. render_action "hello_world"
  25400. end
  25401. def render_action_hello_world_with_symbol
  25402. render_action :hello_world
  25403. end
  25404. def render_text_hello_world
  25405. render_text "hello world"
  25406. end
  25407. def render_custom_code
  25408. render_text "hello world", "404 Moved"
  25409. end
  25410. def render_xml_hello
  25411. @name = "David"
  25412. render "test/hello"
  25413. end
  25414. def greeting
  25415. # let's just rely on the template
  25416. end
  25417. def layout_test
  25418. render_action "hello_world"
  25419. end
  25420. def builder_layout_test
  25421. render_action "hello"
  25422. end
  25423. def partials_list
  25424. @test_unchanged = 'hello'
  25425. @customers = [ Customer.new("david"), Customer.new("mary") ]
  25426. render_action "list"
  25427. end
  25428. def partial_only
  25429. render_partial
  25430. end
  25431. def hello_in_a_string
  25432. @customers = [ Customer.new("david"), Customer.new("mary") ]
  25433. render_text "How's there? #{render_to_string("test/list")}"
  25434. end
  25435. def accessing_params_in_template
  25436. render_template "Hello: <%= params[:name] %>"
  25437. end
  25438. def accessing_local_assigns_in_inline_template
  25439. name = params[:local_name]
  25440. render :inline => "<%= 'Goodbye, ' + local_name %>",
  25441. :locals => { :local_name => name }
  25442. end
  25443. def accessing_local_assigns_in_inline_template_with_string_keys
  25444. name = params[:local_name]
  25445. ActionView::Base.local_assigns_support_string_keys = true
  25446. render :inline => "<%= 'Goodbye, ' + local_name %>",
  25447. :locals => { "local_name" => name }
  25448. ActionView::Base.local_assigns_support_string_keys = false
  25449. end
  25450. def render_to_string_test
  25451. @foo = render_to_string :inline => "this is a test"
  25452. end
  25453. def rescue_action(e) raise end
  25454. private
  25455. def determine_layout
  25456. case action_name
  25457. when "layout_test": "layouts/standard"
  25458. when "builder_layout_test": "layouts/builder"
  25459. end
  25460. end
  25461. end
  25462. TestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  25463. Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  25464. class RenderTest < Test::Unit::TestCase
  25465. def setup
  25466. @request = ActionController::TestRequest.new
  25467. @response = ActionController::TestResponse.new
  25468. @controller = TestController.new
  25469. @request.host = "www.nextangle.com"
  25470. end
  25471. def test_simple_show
  25472. get :hello_world
  25473. assert_response 200
  25474. assert_template "test/hello_world"
  25475. end
  25476. def test_do_with_render
  25477. get :render_hello_world
  25478. assert_template "test/hello_world"
  25479. end
  25480. def test_do_with_render_from_variable
  25481. get :render_hello_world_from_variable
  25482. assert_equal "hello david", @response.body
  25483. end
  25484. def test_do_with_render_action
  25485. get :render_action_hello_world
  25486. assert_template "test/hello_world"
  25487. end
  25488. def test_do_with_render_action_with_symbol
  25489. get :render_action_hello_world_with_symbol
  25490. assert_template "test/hello_world"
  25491. end
  25492. def test_do_with_render_text
  25493. get :render_text_hello_world
  25494. assert_equal "hello world", @response.body
  25495. end
  25496. def test_do_with_render_custom_code
  25497. get :render_custom_code
  25498. assert_response 404
  25499. end
  25500. def test_attempt_to_access_object_method
  25501. assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
  25502. end
  25503. def test_private_methods
  25504. assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
  25505. end
  25506. def test_access_to_request_in_view
  25507. view_internals_old_value = ActionController::Base.view_controller_internals
  25508. ActionController::Base.view_controller_internals = false
  25509. ActionController::Base.protected_variables_cache = nil
  25510. get :hello_world
  25511. assert_nil assigns["request"]
  25512. ActionController::Base.view_controller_internals = true
  25513. ActionController::Base.protected_variables_cache = nil
  25514. get :hello_world
  25515. assert_kind_of ActionController::AbstractRequest, assigns["request"]
  25516. ActionController::Base.view_controller_internals = view_internals_old_value
  25517. ActionController::Base.protected_variables_cache = nil
  25518. end
  25519. def test_render_xml
  25520. get :render_xml_hello
  25521. assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
  25522. end
  25523. def test_render_xml_with_default
  25524. get :greeting
  25525. assert_equal "<p>This is grand!</p>\n", @response.body
  25526. end
  25527. def test_layout_rendering
  25528. get :layout_test
  25529. assert_equal "<html>Hello world!</html>", @response.body
  25530. end
  25531. def test_render_xml_with_layouts
  25532. get :builder_layout_test
  25533. assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
  25534. end
  25535. # def test_partials_list
  25536. # get :partials_list
  25537. # assert_equal "goodbyeHello: davidHello: marygoodbye\n", process_request.body
  25538. # end
  25539. def test_partial_only
  25540. get :partial_only
  25541. assert_equal "only partial", @response.body
  25542. end
  25543. def test_render_to_string
  25544. get :hello_in_a_string
  25545. assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
  25546. end
  25547. def test_render_to_string_resets_assigns
  25548. get :render_to_string_test
  25549. assert_equal "The value of foo is: ::this is a test::\n", @response.body
  25550. end
  25551. def test_nested_rendering
  25552. @controller = Fun::GamesController.new
  25553. get :hello_world
  25554. assert_equal "Living in a nested world", @response.body
  25555. end
  25556. def test_accessing_params_in_template
  25557. get :accessing_params_in_template, :name => "David"
  25558. assert_equal "Hello: David", @response.body
  25559. end
  25560. def test_accessing_local_assigns_in_inline_template
  25561. get :accessing_local_assigns_in_inline_template, :local_name => "Local David"
  25562. assert_equal "Goodbye, Local David", @response.body
  25563. end
  25564. def test_accessing_local_assigns_in_inline_template_with_string_keys
  25565. get :accessing_local_assigns_in_inline_template_with_string_keys, :local_name => "Local David"
  25566. assert_equal "Goodbye, Local David", @response.body
  25567. end
  25568. end
  25569. require File.dirname(__FILE__) + '/../abstract_unit'
  25570. class RequestTest < Test::Unit::TestCase
  25571. def setup
  25572. @request = ActionController::TestRequest.new
  25573. end
  25574. def test_remote_ip
  25575. assert_equal '0.0.0.0', @request.remote_ip
  25576. @request.remote_addr = '1.2.3.4'
  25577. assert_equal '1.2.3.4', @request.remote_ip
  25578. @request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
  25579. assert_equal '2.3.4.5', @request.remote_ip
  25580. @request.env.delete 'HTTP_CLIENT_IP'
  25581. @request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
  25582. assert_equal '3.4.5.6', @request.remote_ip
  25583. @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6'
  25584. assert_equal '3.4.5.6', @request.remote_ip
  25585. @request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6'
  25586. assert_equal '3.4.5.6', @request.remote_ip
  25587. @request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6'
  25588. assert_equal '3.4.5.6', @request.remote_ip
  25589. @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
  25590. assert_equal '3.4.5.6', @request.remote_ip
  25591. @request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
  25592. assert_equal '127.0.0.1', @request.remote_ip
  25593. @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
  25594. assert_equal '1.2.3.4', @request.remote_ip
  25595. @request.env.delete 'HTTP_X_FORWARDED_FOR'
  25596. end
  25597. def test_domains
  25598. @request.host = "www.rubyonrails.org"
  25599. assert_equal "rubyonrails.org", @request.domain
  25600. @request.host = "www.rubyonrails.co.uk"
  25601. assert_equal "rubyonrails.co.uk", @request.domain(2)
  25602. @request.host = "192.168.1.200"
  25603. assert_nil @request.domain
  25604. @request.host = nil
  25605. assert_nil @request.domain
  25606. end
  25607. def test_subdomains
  25608. @request.host = "www.rubyonrails.org"
  25609. assert_equal %w( www ), @request.subdomains
  25610. @request.host = "www.rubyonrails.co.uk"
  25611. assert_equal %w( www ), @request.subdomains(2)
  25612. @request.host = "dev.www.rubyonrails.co.uk"
  25613. assert_equal %w( dev www ), @request.subdomains(2)
  25614. @request.host = "foobar.foobar.com"
  25615. assert_equal %w( foobar ), @request.subdomains
  25616. @request.host = nil
  25617. assert_equal [], @request.subdomains
  25618. end
  25619. def test_port_string
  25620. @request.port = 80
  25621. assert_equal "", @request.port_string
  25622. @request.port = 8080
  25623. assert_equal ":8080", @request.port_string
  25624. end
  25625. def test_relative_url_root
  25626. @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
  25627. @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
  25628. assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd"
  25629. @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
  25630. @request.env['SCRIPT_NAME'] = nil
  25631. assert_equal "", @request.relative_url_root
  25632. @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
  25633. assert_equal "", @request.relative_url_root
  25634. @request.env['SCRIPT_NAME'] = "/myapp.rb"
  25635. assert_equal "", @request.relative_url_root
  25636. @request.relative_url_root = nil
  25637. @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
  25638. assert_equal "/hieraki", @request.relative_url_root
  25639. @request.relative_url_root = nil
  25640. @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
  25641. assert_equal "/collaboration/hieraki", @request.relative_url_root
  25642. # apache/scgi case
  25643. @request.relative_url_root = nil
  25644. @request.env['SCRIPT_NAME'] = "/collaboration/hieraki"
  25645. assert_equal "/collaboration/hieraki", @request.relative_url_root
  25646. @request.relative_url_root = nil
  25647. @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
  25648. @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
  25649. @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki"
  25650. assert_equal "/hieraki", @request.relative_url_root
  25651. # @env overrides path guess
  25652. @request.relative_url_root = nil
  25653. @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
  25654. @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
  25655. @request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url"
  25656. assert_equal "/real_url", @request.relative_url_root
  25657. end
  25658. def test_request_uri
  25659. @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432'
  25660. @request.relative_url_root = nil
  25661. @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1"
  25662. assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
  25663. assert_equal "/path/of/some/uri", @request.path
  25664. @request.relative_url_root = nil
  25665. @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri"
  25666. assert_equal "/path/of/some/uri", @request.request_uri
  25667. assert_equal "/path/of/some/uri", @request.path
  25668. @request.relative_url_root = nil
  25669. @request.set_REQUEST_URI "/path/of/some/uri"
  25670. assert_equal "/path/of/some/uri", @request.request_uri
  25671. assert_equal "/path/of/some/uri", @request.path
  25672. @request.relative_url_root = nil
  25673. @request.set_REQUEST_URI "/"
  25674. assert_equal "/", @request.request_uri
  25675. assert_equal "/", @request.path
  25676. @request.relative_url_root = nil
  25677. @request.set_REQUEST_URI "/?m=b"
  25678. assert_equal "/?m=b", @request.request_uri
  25679. assert_equal "/", @request.path
  25680. @request.relative_url_root = nil
  25681. @request.set_REQUEST_URI "/"
  25682. @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
  25683. assert_equal "/", @request.request_uri
  25684. assert_equal "/", @request.path
  25685. @request.relative_url_root = nil
  25686. @request.set_REQUEST_URI "/hieraki/"
  25687. @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
  25688. assert_equal "/hieraki/", @request.request_uri
  25689. assert_equal "/", @request.path
  25690. @request.relative_url_root = nil
  25691. @request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2"
  25692. @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
  25693. assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri
  25694. assert_equal "/books/edit/2", @request.path
  25695. # The following tests are for when REQUEST_URI is not supplied (as in IIS)
  25696. @request.relative_url_root = nil
  25697. @request.set_REQUEST_URI nil
  25698. @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
  25699. @request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb"
  25700. assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
  25701. assert_equal "/path/of/some/uri", @request.path
  25702. @request.relative_url_root = nil
  25703. @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
  25704. @request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
  25705. assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
  25706. assert_equal "/of/some/uri", @request.path
  25707. @request.relative_url_root = nil
  25708. @request.env['PATH_INFO'] = "/path/of/some/uri"
  25709. @request.env['SCRIPT_NAME'] = nil
  25710. assert_equal "/path/of/some/uri", @request.request_uri
  25711. assert_equal "/path/of/some/uri", @request.path
  25712. @request.relative_url_root = nil
  25713. @request.env['PATH_INFO'] = "/"
  25714. assert_equal "/", @request.request_uri
  25715. assert_equal "/", @request.path
  25716. @request.relative_url_root = nil
  25717. @request.env['PATH_INFO'] = "/?m=b"
  25718. assert_equal "/?m=b", @request.request_uri
  25719. assert_equal "/", @request.path
  25720. @request.relative_url_root = nil
  25721. @request.env['PATH_INFO'] = "/"
  25722. @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
  25723. assert_equal "/", @request.request_uri
  25724. assert_equal "/", @request.path
  25725. @request.relative_url_root = nil
  25726. @request.env['PATH_INFO'] = "/hieraki/"
  25727. @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
  25728. assert_equal "/hieraki/", @request.request_uri
  25729. assert_equal "/", @request.path
  25730. # This test ensures that Rails uses REQUEST_URI over PATH_INFO
  25731. @request.relative_url_root = nil
  25732. @request.env['REQUEST_URI'] = "/some/path"
  25733. @request.env['PATH_INFO'] = "/another/path"
  25734. @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
  25735. assert_equal "/some/path", @request.request_uri
  25736. assert_equal "/some/path", @request.path
  25737. end
  25738. def test_host_with_port
  25739. @request.host = "rubyonrails.org"
  25740. @request.port = 80
  25741. assert_equal "rubyonrails.org", @request.host_with_port
  25742. @request.host = "rubyonrails.org"
  25743. @request.port = 81
  25744. assert_equal "rubyonrails.org:81", @request.host_with_port
  25745. end
  25746. def test_server_software
  25747. assert_equal nil, @request.server_software
  25748. @request.env['SERVER_SOFTWARE'] = 'Apache3.422'
  25749. assert_equal 'apache', @request.server_software
  25750. @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
  25751. assert_equal 'lighttpd', @request.server_software
  25752. end
  25753. def test_xml_http_request
  25754. assert !@request.xml_http_request?
  25755. assert !@request.xhr?
  25756. @request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0"
  25757. assert !@request.xml_http_request?
  25758. assert !@request.xhr?
  25759. @request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
  25760. assert @request.xml_http_request?
  25761. assert @request.xhr?
  25762. end
  25763. def test_reports_ssl
  25764. assert !@request.ssl?
  25765. @request.env['HTTPS'] = 'on'
  25766. assert @request.ssl?
  25767. end
  25768. def test_reports_ssl_when_proxied_via_lighttpd
  25769. assert !@request.ssl?
  25770. @request.env['HTTP_X_FORWARDED_PROTO'] = 'https'
  25771. assert @request.ssl?
  25772. end
  25773. end
  25774. require File.dirname(__FILE__) + '/../abstract_unit'
  25775. require 'test/unit'
  25776. require File.dirname(__FILE__) + '/fake_controllers'
  25777. require 'stringio'
  25778. RunTimeTests = ARGV.include? 'time'
  25779. module ActionController::CodeGeneration
  25780. class SourceTests < Test::Unit::TestCase
  25781. attr_accessor :source
  25782. def setup
  25783. @source = Source.new
  25784. end
  25785. def test_initial_state
  25786. assert_equal [], source.lines
  25787. assert_equal 0, source.indentation_level
  25788. end
  25789. def test_trivial_operations
  25790. source << "puts 'Hello World'"
  25791. assert_equal ["puts 'Hello World'"], source.lines
  25792. assert_equal "puts 'Hello World'", source.to_s
  25793. source.line "puts 'Goodbye World'"
  25794. assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], source.lines
  25795. assert_equal "puts 'Hello World'\nputs 'Goodbye World'", source.to_s
  25796. end
  25797. def test_indentation
  25798. source << "x = gets.to_i"
  25799. source << 'if x.odd?'
  25800. source.indent { source << "puts 'x is odd!'" }
  25801. source << 'else'
  25802. source.indent { source << "puts 'x is even!'" }
  25803. source << 'end'
  25804. assert_equal ["x = gets.to_i", "if x.odd?", " puts 'x is odd!'", 'else', " puts 'x is even!'", 'end'], source.lines
  25805. text = "x = gets.to_i
  25806. if x.odd?
  25807. puts 'x is odd!'
  25808. else
  25809. puts 'x is even!'
  25810. end"
  25811. assert_equal text, source.to_s
  25812. end
  25813. end
  25814. class CodeGeneratorTests < Test::Unit::TestCase
  25815. attr_accessor :generator
  25816. def setup
  25817. @generator = CodeGenerator.new
  25818. end
  25819. def test_initial_state
  25820. assert_equal [], generator.source.lines
  25821. assert_equal [], generator.locals
  25822. end
  25823. def test_trivial_operations
  25824. ["puts 'Hello World'", "puts 'Goodbye World'"].each {|l| generator << l}
  25825. assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], generator.source.lines
  25826. assert_equal "puts 'Hello World'\nputs 'Goodbye World'", generator.to_s
  25827. end
  25828. def test_if
  25829. generator << "x = gets.to_i"
  25830. generator.if("x.odd?") { generator << "puts 'x is odd!'" }
  25831. assert_equal "x = gets.to_i\nif x.odd?\n puts 'x is odd!'\nend", generator.to_s
  25832. end
  25833. def test_else
  25834. test_if
  25835. generator.else { generator << "puts 'x is even!'" }
  25836. assert_equal "x = gets.to_i\nif x.odd?\n puts 'x is odd!'\nelse \n puts 'x is even!'\nend", generator.to_s
  25837. end
  25838. def test_dup
  25839. generator << 'x = 2'
  25840. generator.locals << :x
  25841. g = generator.dup
  25842. assert_equal generator.source, g.source
  25843. assert_equal generator.locals, g.locals
  25844. g << 'y = 3'
  25845. g.locals << :y
  25846. assert_equal [:x, :y], g.locals # Make sure they don't share the same array.
  25847. assert_equal [:x], generator.locals
  25848. end
  25849. end
  25850. class RecognitionTests < Test::Unit::TestCase
  25851. attr_accessor :generator
  25852. alias :g :generator
  25853. def setup
  25854. @generator = RecognitionGenerator.new
  25855. end
  25856. def go(components)
  25857. g.current = components.first
  25858. g.after = components[1..-1] || []
  25859. g.go
  25860. end
  25861. def execute(path, show = false)
  25862. path = path.split('/') if path.is_a? String
  25863. source = "index, path = 0, #{path.inspect}\n#{g.to_s}"
  25864. puts source if show
  25865. r = eval source
  25866. r ? r.symbolize_keys : nil
  25867. end
  25868. Static = ::ActionController::Routing::StaticComponent
  25869. Dynamic = ::ActionController::Routing::DynamicComponent
  25870. Path = ::ActionController::Routing::PathComponent
  25871. Controller = ::ActionController::Routing::ControllerComponent
  25872. def test_all_static
  25873. c = %w(hello world how are you).collect {|str| Static.new(str)}
  25874. g.result :controller, "::ContentController", true
  25875. g.constant_result :action, 'index'
  25876. go c
  25877. assert_nil execute('x')
  25878. assert_nil execute('hello/world/how')
  25879. assert_nil execute('hello/world/how/are')
  25880. assert_nil execute('hello/world/how/are/you/today')
  25881. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hello/world/how/are/you'))
  25882. end
  25883. def test_basic_dynamic
  25884. c = [Static.new("hi"), Dynamic.new(:action)]
  25885. g.result :controller, "::ContentController", true
  25886. go c
  25887. assert_nil execute('boo')
  25888. assert_nil execute('boo/blah')
  25889. assert_nil execute('hi')
  25890. assert_nil execute('hi/dude/what')
  25891. assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  25892. end
  25893. def test_basic_dynamic_backwards
  25894. c = [Dynamic.new(:action), Static.new("hi")]
  25895. go c
  25896. assert_nil execute('')
  25897. assert_nil execute('boo')
  25898. assert_nil execute('boo/blah')
  25899. assert_nil execute('hi')
  25900. assert_equal({:action => 'index'}, execute('index/hi'))
  25901. assert_equal({:action => 'show'}, execute('show/hi'))
  25902. assert_nil execute('hi/dude')
  25903. end
  25904. def test_dynamic_with_default
  25905. c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')]
  25906. g.result :controller, "::ContentController", true
  25907. go c
  25908. assert_nil execute('boo')
  25909. assert_nil execute('boo/blah')
  25910. assert_nil execute('hi/dude/what')
  25911. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi'))
  25912. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
  25913. assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  25914. end
  25915. def test_dynamic_with_string_condition
  25916. c = [Static.new("hi"), Dynamic.new(:action, :condition => 'index')]
  25917. g.result :controller, "::ContentController", true
  25918. go c
  25919. assert_nil execute('boo')
  25920. assert_nil execute('boo/blah')
  25921. assert_nil execute('hi')
  25922. assert_nil execute('hi/dude/what')
  25923. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
  25924. assert_nil execute('hi/dude')
  25925. end
  25926. def test_dynamic_with_string_condition_backwards
  25927. c = [Dynamic.new(:action, :condition => 'index'), Static.new("hi")]
  25928. g.result :controller, "::ContentController", true
  25929. go c
  25930. assert_nil execute('boo')
  25931. assert_nil execute('boo/blah')
  25932. assert_nil execute('hi')
  25933. assert_nil execute('dude/what/hi')
  25934. assert_nil execute('index/what')
  25935. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('index/hi'))
  25936. assert_nil execute('dude/hi')
  25937. end
  25938. def test_dynamic_with_regexp_condition
  25939. c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)]
  25940. g.result :controller, "::ContentController", true
  25941. go c
  25942. assert_nil execute('boo')
  25943. assert_nil execute('boo/blah')
  25944. assert_nil execute('hi')
  25945. assert_nil execute('hi/FOXY')
  25946. assert_nil execute('hi/138708jkhdf')
  25947. assert_nil execute('hi/dkjfl8792343dfsf')
  25948. assert_nil execute('hi/dude/what')
  25949. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
  25950. assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  25951. end
  25952. def test_dynamic_with_regexp_and_default
  25953. c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/, :default => 'index')]
  25954. g.result :controller, "::ContentController", true
  25955. go c
  25956. assert_nil execute('boo')
  25957. assert_nil execute('boo/blah')
  25958. assert_nil execute('hi/FOXY')
  25959. assert_nil execute('hi/138708jkhdf')
  25960. assert_nil execute('hi/dkjfl8792343dfsf')
  25961. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi'))
  25962. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
  25963. assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  25964. assert_nil execute('hi/dude/what')
  25965. end
  25966. def test_path
  25967. c = [Static.new("hi"), Path.new(:file)]
  25968. g.result :controller, "::ContentController", true
  25969. g.constant_result :action, "download"
  25970. go c
  25971. assert_nil execute('boo')
  25972. assert_nil execute('boo/blah')
  25973. assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('hi'))
  25974. assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)},
  25975. execute('hi/books/agile_rails_dev.pdf'))
  25976. assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('hi/dude'))
  25977. assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s
  25978. end
  25979. def test_path_with_dynamic
  25980. c = [Dynamic.new(:action), Path.new(:file)]
  25981. g.result :controller, "::ContentController", true
  25982. go c
  25983. assert_nil execute('')
  25984. assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('download'))
  25985. assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)},
  25986. execute('download/books/agile_rails_dev.pdf'))
  25987. assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('download/dude'))
  25988. assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s
  25989. end
  25990. def test_path_with_dynamic_and_default
  25991. c = [Dynamic.new(:action, :default => 'index'), Path.new(:file)]
  25992. go c
  25993. assert_equal({:action => 'index', :file => []}, execute(''))
  25994. assert_equal({:action => 'index', :file => []}, execute('index'))
  25995. assert_equal({:action => 'blarg', :file => []}, execute('blarg'))
  25996. assert_equal({:action => 'index', :file => ['content']}, execute('index/content'))
  25997. assert_equal({:action => 'show', :file => ['rails_dev.pdf']}, execute('show/rails_dev.pdf'))
  25998. end
  25999. def test_controller
  26000. c = [Static.new("hi"), Controller.new(:controller)]
  26001. g.constant_result :action, "hi"
  26002. go c
  26003. assert_nil execute('boo')
  26004. assert_nil execute('boo/blah')
  26005. assert_nil execute('hi/x')
  26006. assert_nil execute('hi/13870948')
  26007. assert_nil execute('hi/content/dog')
  26008. assert_nil execute('hi/admin/user/foo')
  26009. assert_equal({:controller => ::ContentController, :action => 'hi'}, execute('hi/content'))
  26010. assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user'))
  26011. end
  26012. def test_controller_with_regexp
  26013. c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)]
  26014. g.constant_result :action, "hi"
  26015. go c
  26016. assert_nil execute('hi')
  26017. assert_nil execute('hi/x')
  26018. assert_nil execute('hi/content')
  26019. assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user'))
  26020. assert_equal({:controller => ::Admin::NewsFeedController, :action => 'hi'}, execute('hi/admin/news_feed'))
  26021. assert_nil execute('hi/admin/user/foo')
  26022. end
  26023. def test_standard_route(time = ::RunTimeTests)
  26024. c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)]
  26025. go c
  26026. # Make sure we get the right answers
  26027. assert_equal({:controller => ::ContentController, :action => 'index'}, execute('content'))
  26028. assert_equal({:controller => ::ContentController, :action => 'list'}, execute('content/list'))
  26029. assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, execute('content/show/10'))
  26030. assert_equal({:controller => ::Admin::UserController, :action => 'index'}, execute('admin/user'))
  26031. assert_equal({:controller => ::Admin::UserController, :action => 'list'}, execute('admin/user/list'))
  26032. assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => 'nseckar'}, execute('admin/user/show/nseckar'))
  26033. assert_nil execute('content/show/10/20')
  26034. assert_nil execute('food')
  26035. if time
  26036. source = "def self.execute(path)
  26037. path = path.split('/') if path.is_a? String
  26038. index = 0
  26039. r = #{g.to_s}
  26040. end"
  26041. eval(source)
  26042. GC.start
  26043. n = 1000
  26044. time = Benchmark.realtime do n.times {
  26045. execute('content')
  26046. execute('content/list')
  26047. execute('content/show/10')
  26048. execute('admin/user')
  26049. execute('admin/user/list')
  26050. execute('admin/user/show/nseckar')
  26051. execute('admin/user/show/nseckar/dude')
  26052. execute('admin/why/show/nseckar')
  26053. execute('content/show/10/20')
  26054. execute('food')
  26055. } end
  26056. time -= Benchmark.realtime do n.times { } end
  26057. puts "\n\nRecognition:"
  26058. per_url = time / (n * 10)
  26059. puts "#{per_url * 1000} ms/url"
  26060. puts "#{1 / per_url} urls/s\n\n"
  26061. end
  26062. end
  26063. def test_default_route
  26064. g.result :controller, "::ContentController", true
  26065. g.constant_result :action, 'index'
  26066. go []
  26067. assert_nil execute('x')
  26068. assert_nil execute('hello/world/how')
  26069. assert_nil execute('hello/world/how/are')
  26070. assert_nil execute('hello/world/how/are/you/today')
  26071. assert_equal({:controller => ::ContentController, :action => 'index'}, execute([]))
  26072. end
  26073. end
  26074. class GenerationTests < Test::Unit::TestCase
  26075. attr_accessor :generator
  26076. alias :g :generator
  26077. def setup
  26078. @generator = GenerationGenerator.new # ha!
  26079. end
  26080. def go(components)
  26081. g.current = components.first
  26082. g.after = components[1..-1] || []
  26083. g.go
  26084. end
  26085. def execute(options, recall, show = false)
  26086. source = "\n
  26087. expire_on = ::ActionController::Routing.expiry_hash(options, recall)
  26088. hash = merged = recall.merge(options)
  26089. not_expired = true
  26090. #{g.to_s}\n\n"
  26091. puts source if show
  26092. eval(source)
  26093. end
  26094. Static = ::ActionController::Routing::StaticComponent
  26095. Dynamic = ::ActionController::Routing::DynamicComponent
  26096. Path = ::ActionController::Routing::PathComponent
  26097. Controller = ::ActionController::Routing::ControllerComponent
  26098. def test_all_static_no_requirements
  26099. c = [Static.new("hello"), Static.new("world")]
  26100. go c
  26101. assert_equal "/hello/world", execute({}, {})
  26102. end
  26103. def test_basic_dynamic
  26104. c = [Static.new("hi"), Dynamic.new(:action)]
  26105. go c
  26106. assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'})
  26107. assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'})
  26108. assert_equal '/hi/list+people', execute({}, {:action => 'list people'})
  26109. assert_nil execute({},{})
  26110. end
  26111. def test_dynamic_with_default
  26112. c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')]
  26113. go c
  26114. assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'})
  26115. assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'})
  26116. assert_equal '/hi/list+people', execute({}, {:action => 'list people'})
  26117. assert_equal '/hi', execute({}, {})
  26118. end
  26119. def test_dynamic_with_regexp_condition
  26120. c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)]
  26121. go c
  26122. assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'})
  26123. assert_nil execute({:action => 'fox5'}, {:action => 'index'})
  26124. assert_nil execute({:action => 'something_is_up'}, {:action => 'index'})
  26125. assert_nil execute({}, {:action => 'list people'})
  26126. assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {})
  26127. assert_nil execute({}, {})
  26128. end
  26129. def test_dynamic_with_default_and_regexp_condition
  26130. c = [Static.new("hi"), Dynamic.new(:action, :default => 'index', :condition => /^[a-z]+$/)]
  26131. go c
  26132. assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'})
  26133. assert_nil execute({:action => 'fox5'}, {:action => 'index'})
  26134. assert_nil execute({:action => 'something_is_up'}, {:action => 'index'})
  26135. assert_nil execute({}, {:action => 'list people'})
  26136. assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {})
  26137. assert_equal '/hi', execute({}, {})
  26138. end
  26139. def test_path
  26140. c = [Static.new("hi"), Path.new(:file)]
  26141. go c
  26142. assert_equal '/hi', execute({:file => []}, {})
  26143. assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => %w(books agile_rails_dev.pdf)}, {})
  26144. assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => %w(books development&whatever agile_rails_dev.pdf)}, {})
  26145. assert_equal '/hi', execute({:file => ''}, {})
  26146. assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => 'books/agile_rails_dev.pdf'}, {})
  26147. assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => 'books/development&whatever/agile_rails_dev.pdf'}, {})
  26148. end
  26149. def test_controller
  26150. c = [Static.new("hi"), Controller.new(:controller)]
  26151. go c
  26152. assert_nil execute({}, {})
  26153. assert_equal '/hi/content', execute({:controller => 'content'}, {})
  26154. assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {})
  26155. assert_equal '/hi/content', execute({}, {:controller => 'content'})
  26156. assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'})
  26157. end
  26158. def test_controller_with_regexp
  26159. c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)]
  26160. go c
  26161. assert_nil execute({}, {})
  26162. assert_nil execute({:controller => 'content'}, {})
  26163. assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {})
  26164. assert_nil execute({}, {:controller => 'content'})
  26165. assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'})
  26166. end
  26167. def test_standard_route(time = ::RunTimeTests)
  26168. c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)]
  26169. go c
  26170. # Make sure we get the right answers
  26171. assert_equal('/content', execute({:action => 'index'}, {:controller => 'content', :action => 'list'}))
  26172. assert_equal('/content/list', execute({:action => 'list'}, {:controller => 'content', :action => 'index'}))
  26173. assert_equal('/content/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}))
  26174. assert_equal('/admin/user', execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'}))
  26175. assert_equal('/admin/user/list', execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'}))
  26176. assert_equal('/admin/user/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}))
  26177. if time
  26178. GC.start
  26179. n = 1000
  26180. time = Benchmark.realtime do n.times {
  26181. execute({:action => 'index'}, {:controller => 'content', :action => 'list'})
  26182. execute({:action => 'list'}, {:controller => 'content', :action => 'index'})
  26183. execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'})
  26184. execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'})
  26185. execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'})
  26186. execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'})
  26187. } end
  26188. time -= Benchmark.realtime do n.times { } end
  26189. puts "\n\nGeneration:"
  26190. per_url = time / (n * 6)
  26191. puts "#{per_url * 1000} ms/url"
  26192. puts "#{1 / per_url} urls/s\n\n"
  26193. end
  26194. end
  26195. def test_default_route
  26196. g.if(g.check_conditions(:controller => 'content', :action => 'welcome')) { go [] }
  26197. assert_nil execute({:controller => 'foo', :action => 'welcome'}, {})
  26198. assert_nil execute({:controller => 'content', :action => 'elcome'}, {})
  26199. assert_nil execute({:action => 'elcome'}, {:controller => 'content'})
  26200. assert_equal '/', execute({:controller => 'content', :action => 'welcome'}, {})
  26201. assert_equal '/', execute({:action => 'welcome'}, {:controller => 'content'})
  26202. assert_equal '/', execute({:action => 'welcome', :id => '10'}, {:controller => 'content'})
  26203. end
  26204. end
  26205. class RouteTests < Test::Unit::TestCase
  26206. def route(*args)
  26207. @route = ::ActionController::Routing::Route.new(*args) unless args.empty?
  26208. return @route
  26209. end
  26210. def rec(path, show = false)
  26211. path = path.split('/') if path.is_a? String
  26212. index = 0
  26213. source = route.write_recognition.to_s
  26214. puts "\n\n#{source}\n\n" if show
  26215. r = eval(source)
  26216. r ? r.symbolize_keys : r
  26217. end
  26218. def gen(options, recall = nil, show = false)
  26219. recall ||= options.dup
  26220. expire_on = ::ActionController::Routing.expiry_hash(options, recall)
  26221. hash = merged = recall.merge(options)
  26222. not_expired = true
  26223. source = route.write_generation.to_s
  26224. puts "\n\n#{source}\n\n" if show
  26225. eval(source)
  26226. end
  26227. def test_static
  26228. route 'hello/world', :known => 'known_value', :controller => 'content', :action => 'index'
  26229. assert_nil rec('hello/turn')
  26230. assert_nil rec('turn/world')
  26231. assert_equal(
  26232. {:known => 'known_value', :controller => ::ContentController, :action => 'index'},
  26233. rec('hello/world')
  26234. )
  26235. assert_nil gen(:known => 'foo')
  26236. assert_nil gen({})
  26237. assert_equal '/hello/world', gen(:known => 'known_value', :controller => 'content', :action => 'index')
  26238. assert_equal '/hello/world', gen(:known => 'known_value', :extra => 'hi', :controller => 'content', :action => 'index')
  26239. assert_equal [:extra], route.extra_keys(:known => 'known_value', :extra => 'hi')
  26240. end
  26241. def test_dynamic
  26242. route 'hello/:name', :controller => 'content', :action => 'show_person'
  26243. assert_nil rec('hello')
  26244. assert_nil rec('foo/bar')
  26245. assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'rails'}, rec('hello/rails'))
  26246. assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'Nicholas Seckar'}, rec('hello/Nicholas+Seckar'))
  26247. assert_nil gen(:controller => 'content', :action => 'show_dude', :name => 'rails')
  26248. assert_nil gen(:controller => 'content', :action => 'show_person')
  26249. assert_nil gen(:controller => 'admin/user', :action => 'show_person', :name => 'rails')
  26250. assert_equal '/hello/rails', gen(:controller => 'content', :action => 'show_person', :name => 'rails')
  26251. assert_equal '/hello/Nicholas+Seckar', gen(:controller => 'content', :action => 'show_person', :name => 'Nicholas Seckar')
  26252. end
  26253. def test_typical
  26254. route ':controller/:action/:id', :action => 'index', :id => nil
  26255. assert_nil rec('hello')
  26256. assert_nil rec('foo bar')
  26257. assert_equal({:controller => ::ContentController, :action => 'index'}, rec('content'))
  26258. assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user'))
  26259. assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user/index'))
  26260. assert_equal({:controller => ::Admin::UserController, :action => 'list'}, rec('admin/user/list'))
  26261. assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}, rec('admin/user/show/10'))
  26262. assert_equal({:controller => ::ContentController, :action => 'list'}, rec('content/list'))
  26263. assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, rec('content/show/10'))
  26264. assert_equal '/content', gen(:controller => 'content', :action => 'index')
  26265. assert_equal '/content/list', gen(:controller => 'content', :action => 'list')
  26266. assert_equal '/content/show/10', gen(:controller => 'content', :action => 'show', :id => '10')
  26267. assert_equal '/admin/user', gen(:controller => 'admin/user', :action => 'index')
  26268. assert_equal '/admin/user', gen(:controller => 'admin/user')
  26269. assert_equal '/admin/user', gen({:controller => 'admin/user'}, {:controller => 'content', :action => 'list', :id => '10'})
  26270. assert_equal '/admin/user/show/10', gen(:controller => 'admin/user', :action => 'show', :id => '10')
  26271. end
  26272. end
  26273. class RouteSetTests < Test::Unit::TestCase
  26274. attr_reader :rs
  26275. def setup
  26276. @rs = ::ActionController::Routing::RouteSet.new
  26277. @rs.draw {|m| m.connect ':controller/:action/:id' }
  26278. ::ActionController::Routing::NamedRoutes.clear
  26279. end
  26280. def test_default_setup
  26281. assert_equal({:controller => ::ContentController, :action => 'index'}.stringify_keys, rs.recognize_path(%w(content)))
  26282. assert_equal({:controller => ::ContentController, :action => 'list'}.stringify_keys, rs.recognize_path(%w(content list)))
  26283. assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(content show 10)))
  26284. assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(admin user show 10)))
  26285. assert_equal ['/admin/user/show/10', []], rs.generate({:controller => 'admin/user', :action => 'show', :id => 10})
  26286. assert_equal ['/admin/user/show', []], rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
  26287. assert_equal ['/admin/user/list/10', []], rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'})
  26288. assert_equal ['/admin/stuff', []], rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
  26289. assert_equal ['/stuff', []], rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
  26290. end
  26291. def test_ignores_leading_slash
  26292. @rs.draw {|m| m.connect '/:controller/:action/:id'}
  26293. test_default_setup
  26294. end
  26295. def test_time_recognition
  26296. n = 10000
  26297. if RunTimeTests
  26298. GC.start
  26299. rectime = Benchmark.realtime do
  26300. n.times do
  26301. rs.recognize_path(%w(content))
  26302. rs.recognize_path(%w(content list))
  26303. rs.recognize_path(%w(content show 10))
  26304. rs.recognize_path(%w(admin user))
  26305. rs.recognize_path(%w(admin user list))
  26306. rs.recognize_path(%w(admin user show 10))
  26307. end
  26308. end
  26309. puts "\n\nRecognition (RouteSet):"
  26310. per_url = rectime / (n * 6)
  26311. puts "#{per_url * 1000} ms/url"
  26312. puts "#{1 / per_url} url/s\n\n"
  26313. end
  26314. end
  26315. def test_time_generation
  26316. n = 5000
  26317. if RunTimeTests
  26318. GC.start
  26319. pairs = [
  26320. [{:controller => 'content', :action => 'index'}, {:controller => 'content', :action => 'show'}],
  26321. [{:controller => 'content'}, {:controller => 'content', :action => 'index'}],
  26322. [{:controller => 'content', :action => 'list'}, {:controller => 'content', :action => 'index'}],
  26323. [{:controller => 'content', :action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}],
  26324. [{:controller => 'admin/user', :action => 'index'}, {:controller => 'admin/user', :action => 'show'}],
  26325. [{:controller => 'admin/user'}, {:controller => 'admin/user', :action => 'index'}],
  26326. [{:controller => 'admin/user', :action => 'list'}, {:controller => 'admin/user', :action => 'index'}],
  26327. [{:controller => 'admin/user', :action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}],
  26328. ]
  26329. p = nil
  26330. gentime = Benchmark.realtime do
  26331. n.times do
  26332. pairs.each {|(a, b)| rs.generate(a, b)}
  26333. end
  26334. end
  26335. puts "\n\nGeneration (RouteSet): (#{(n * 8)} urls)"
  26336. per_url = gentime / (n * 8)
  26337. puts "#{per_url * 1000} ms/url"
  26338. puts "#{1 / per_url} url/s\n\n"
  26339. end
  26340. end
  26341. def test_route_with_colon_first
  26342. rs.draw do |map|
  26343. map.connect '/:controller/:action/:id', :action => 'index', :id => nil
  26344. map.connect ':url', :controller => 'tiny_url', :action => 'translate'
  26345. end
  26346. end
  26347. def test_route_generating_string_literal_in_comparison_warning
  26348. old_stderr = $stderr
  26349. $stderr = StringIO.new
  26350. rs.draw do |map|
  26351. map.connect 'subscriptions/:action/:subscription_type', :controller => "subscriptions"
  26352. end
  26353. assert_equal "", $stderr.string
  26354. ensure
  26355. $stderr = old_stderr
  26356. end
  26357. def test_route_with_regexp_for_controller
  26358. rs.draw do |map|
  26359. map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/
  26360. map.connect ':controller/:action/:id'
  26361. end
  26362. assert_equal({:controller => ::Admin::UserController, :admintoken => "foo", :action => "index"}.stringify_keys,
  26363. rs.recognize_path(%w(admin user foo)))
  26364. assert_equal({:controller => ::ContentController, :action => "foo"}.stringify_keys,
  26365. rs.recognize_path(%w(content foo)))
  26366. assert_equal ['/admin/user/foo', []], rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index")
  26367. assert_equal ['/content/foo',[]], rs.generate(:controller => "content", :action => "foo")
  26368. end
  26369. def test_basic_named_route
  26370. rs.home '', :controller => 'content', :action => 'list'
  26371. x = setup_for_named_route
  26372. assert_equal({:controller => '/content', :action => 'list'},
  26373. x.new.send(:home_url))
  26374. end
  26375. def test_named_route_with_option
  26376. rs.page 'page/:title', :controller => 'content', :action => 'show_page'
  26377. x = setup_for_named_route
  26378. assert_equal({:controller => '/content', :action => 'show_page', :title => 'new stuff'},
  26379. x.new.send(:page_url, :title => 'new stuff'))
  26380. end
  26381. def test_named_route_with_default
  26382. rs.page 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
  26383. x = setup_for_named_route
  26384. assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutPage'},
  26385. x.new.send(:page_url))
  26386. assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutRails'},
  26387. x.new.send(:page_url, :title => "AboutRails"))
  26388. end
  26389. def setup_for_named_route
  26390. x = Class.new
  26391. x.send(:define_method, :url_for) {|x| x}
  26392. x.send :include, ::ActionController::Routing::NamedRoutes
  26393. x
  26394. end
  26395. def test_named_route_without_hash
  26396. rs.draw do |map|
  26397. rs.normal ':controller/:action/:id'
  26398. end
  26399. end
  26400. def test_named_route_with_regexps
  26401. rs.draw do |map|
  26402. rs.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show',
  26403. :year => /^\d+$/, :month => /^\d+$/, :day => /^\d+$/
  26404. rs.connect ':controller/:action/:id'
  26405. end
  26406. x = setup_for_named_route
  26407. assert_equal(
  26408. {:controller => '/page', :action => 'show', :title => 'hi'},
  26409. x.new.send(:article_url, :title => 'hi')
  26410. )
  26411. assert_equal(
  26412. {:controller => '/page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6},
  26413. x.new.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
  26414. )
  26415. end
  26416. def test_changing_controller
  26417. assert_equal ['/admin/stuff/show/10', []], rs.generate(
  26418. {:controller => 'stuff', :action => 'show', :id => 10},
  26419. {:controller => 'admin/user', :action => 'index'}
  26420. )
  26421. end
  26422. def test_paths_escaped
  26423. rs.draw do |map|
  26424. rs.path 'file/*path', :controller => 'content', :action => 'show_file'
  26425. rs.connect ':controller/:action/:id'
  26426. end
  26427. results = rs.recognize_path %w(file hello+world how+are+you%3F)
  26428. assert results, "Recognition should have succeeded"
  26429. assert_equal ['hello world', 'how are you?'], results['path']
  26430. results = rs.recognize_path %w(file)
  26431. assert results, "Recognition should have succeeded"
  26432. assert_equal [], results['path']
  26433. end
  26434. def test_non_controllers_cannot_be_matched
  26435. rs.draw do
  26436. rs.connect ':controller/:action/:id'
  26437. end
  26438. assert_nil rs.recognize_path(%w(not_a show 10)), "Shouldn't recognize non-controllers as controllers!"
  26439. end
  26440. def test_paths_do_not_accept_defaults
  26441. assert_raises(ActionController::RoutingError) do
  26442. rs.draw do |map|
  26443. rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
  26444. rs.connect ':controller/:action/:id'
  26445. end
  26446. end
  26447. rs.draw do |map|
  26448. rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => []
  26449. rs.connect ':controller/:action/:id'
  26450. end
  26451. end
  26452. def test_backwards
  26453. rs.draw do |map|
  26454. rs.connect 'page/:id/:action', :controller => 'pages', :action => 'show'
  26455. rs.connect ':controller/:action/:id'
  26456. end
  26457. assert_equal ['/page/20', []], rs.generate({:id => 20}, {:controller => 'pages'})
  26458. assert_equal ['/page/20', []], rs.generate(:controller => 'pages', :id => 20, :action => 'show')
  26459. assert_equal ['/pages/boo', []], rs.generate(:controller => 'pages', :action => 'boo')
  26460. end
  26461. def test_route_with_fixnum_default
  26462. rs.draw do |map|
  26463. rs.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1
  26464. rs.connect ':controller/:action/:id'
  26465. end
  26466. assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page')
  26467. assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 1)
  26468. assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => '1')
  26469. assert_equal ['/page/10', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 10)
  26470. ctrl = ::ContentController
  26471. assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => 1}, rs.recognize_path(%w(page)))
  26472. assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '1'}, rs.recognize_path(%w(page 1)))
  26473. assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '10'}, rs.recognize_path(%w(page 10)))
  26474. end
  26475. def test_action_expiry
  26476. assert_equal ['/content', []], rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
  26477. end
  26478. def test_recognition_with_uppercase_controller_name
  26479. assert_equal({'controller' => ::ContentController, 'action' => 'index'}, rs.recognize_path(%w(Content)))
  26480. assert_equal({'controller' => ::ContentController, 'action' => 'list'}, rs.recognize_path(%w(Content list)))
  26481. assert_equal({'controller' => ::ContentController, 'action' => 'show', 'id' => '10'}, rs.recognize_path(%w(Content show 10)))
  26482. assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin NewsFeed)))
  26483. assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin News_Feed)))
  26484. end
  26485. def test_both_requirement_and_optional
  26486. rs.draw do
  26487. rs.blog('test/:year', :controller => 'post', :action => 'show',
  26488. :defaults => { :year => nil },
  26489. :requirements => { :year => /\d{4}/ }
  26490. )
  26491. rs.connect ':controller/:action/:id'
  26492. end
  26493. assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show')
  26494. assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show', :year => nil)
  26495. x = setup_for_named_route
  26496. assert_equal({:controller => '/post', :action => 'show'},
  26497. x.new.send(:blog_url))
  26498. end
  26499. def test_set_to_nil_forgets
  26500. rs.draw do
  26501. rs.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil
  26502. rs.connect ':controller/:action/:id'
  26503. end
  26504. assert_equal ['/pages/2005', []],
  26505. rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005)
  26506. assert_equal ['/pages/2005/6', []],
  26507. rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6)
  26508. assert_equal ['/pages/2005/6/12', []],
  26509. rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12)
  26510. assert_equal ['/pages/2005/6/4', []],
  26511. rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
  26512. assert_equal ['/pages/2005/6', []],
  26513. rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
  26514. assert_equal ['/pages/2005', []],
  26515. rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
  26516. end
  26517. def test_url_with_no_action_specified
  26518. rs.draw do
  26519. rs.connect '', :controller => 'content'
  26520. rs.connect ':controller/:action/:id'
  26521. end
  26522. assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index')
  26523. assert_equal ['/', []], rs.generate(:controller => 'content')
  26524. end
  26525. def test_named_url_with_no_action_specified
  26526. rs.draw do
  26527. rs.root '', :controller => 'content'
  26528. rs.connect ':controller/:action/:id'
  26529. end
  26530. assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index')
  26531. assert_equal ['/', []], rs.generate(:controller => 'content')
  26532. x = setup_for_named_route
  26533. assert_equal({:controller => '/content', :action => 'index'},
  26534. x.new.send(:root_url))
  26535. end
  26536. def test_url_generated_when_forgetting_action
  26537. [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash|
  26538. rs.draw do
  26539. rs.root '', hash
  26540. rs.connect ':controller/:action/:id'
  26541. end
  26542. assert_equal ['/', []], rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
  26543. assert_equal ['/', []], rs.generate({:controller => 'content'})
  26544. assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'})
  26545. end
  26546. end
  26547. def test_named_route_method
  26548. rs.draw do
  26549. assert_raises(ArgumentError) { rs.categories 'categories', :controller => 'content', :action => 'categories' }
  26550. rs.named_route :categories, 'categories', :controller => 'content', :action => 'categories'
  26551. rs.connect ':controller/:action/:id'
  26552. end
  26553. assert_equal ['/categories', []], rs.generate(:controller => 'content', :action => 'categories')
  26554. assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'})
  26555. end
  26556. def test_named_route_helper_array
  26557. test_named_route_method
  26558. assert_equal [:categories_url, :hash_for_categories_url], ::ActionController::Routing::NamedRoutes::Helpers
  26559. end
  26560. def test_nil_defaults
  26561. rs.draw do
  26562. rs.connect 'journal',
  26563. :controller => 'content',
  26564. :action => 'list_journal',
  26565. :date => nil, :user_id => nil
  26566. rs.connect ':controller/:action/:id'
  26567. end
  26568. assert_equal ['/journal', []], rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil)
  26569. end
  26570. end
  26571. class ControllerComponentTest < Test::Unit::TestCase
  26572. def test_traverse_to_controller_should_not_load_arbitrary_files
  26573. load_path = $:.dup
  26574. base = File.dirname(File.dirname(File.expand_path(__FILE__)))
  26575. $: << File.join(base, 'fixtures')
  26576. Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root')
  26577. assert_equal nil, ActionController::Routing::ControllerComponent.traverse_to_controller(%w(dont_load pretty please))
  26578. ensure
  26579. $:[0..-1] = load_path
  26580. Object.send :remove_const, :RAILS_ROOT
  26581. end
  26582. def test_traverse_should_not_trip_on_non_module_constants
  26583. assert_equal nil, ActionController::Routing::ControllerComponent.traverse_to_controller(%w(admin some_constant a))
  26584. end
  26585. # This is evil, but people do it.
  26586. def test_traverse_to_controller_should_pass_thru_classes
  26587. load_path = $:.dup
  26588. base = File.dirname(File.dirname(File.expand_path(__FILE__)))
  26589. $: << File.join(base, 'fixtures')
  26590. $: << File.join(base, 'fixtures/application_root/app/controllers')
  26591. $: << File.join(base, 'fixtures/application_root/app/models')
  26592. Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root')
  26593. pair = ActionController::Routing::ControllerComponent.traverse_to_controller(%w(a_class_that_contains_a_controller poorly_placed))
  26594. # Make sure the container class was loaded properly
  26595. assert defined?(AClassThatContainsAController)
  26596. assert_kind_of Class, AClassThatContainsAController
  26597. assert_equal :you_know_it, AClassThatContainsAController.is_special?
  26598. # Make sure the controller was too
  26599. assert_kind_of Array, pair
  26600. assert_equal 2, pair[1]
  26601. klass = pair.first
  26602. assert_kind_of Class, klass
  26603. assert_equal :decidedly_so, klass.is_evil?
  26604. assert klass.ancestors.include?(ActionController::Base)
  26605. assert defined?(AClassThatContainsAController::PoorlyPlacedController)
  26606. assert_equal klass, AClassThatContainsAController::PoorlyPlacedController
  26607. ensure
  26608. $:[0..-1] = load_path
  26609. Object.send :remove_const, :RAILS_ROOT
  26610. end
  26611. def test_traverse_to_nested_controller
  26612. load_path = $:.dup
  26613. base = File.dirname(File.dirname(File.expand_path(__FILE__)))
  26614. $: << File.join(base, 'fixtures')
  26615. $: << File.join(base, 'fixtures/application_root/app/controllers')
  26616. Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root')
  26617. pair = ActionController::Routing::ControllerComponent.traverse_to_controller(%w(module_that_holds_controllers nested))
  26618. assert_not_equal nil, pair
  26619. # Make sure that we created a module for the dir
  26620. assert defined?(ModuleThatHoldsControllers)
  26621. assert_kind_of Module, ModuleThatHoldsControllers
  26622. # Make sure the controller is ok
  26623. assert_kind_of Array, pair
  26624. assert_equal 2, pair[1]
  26625. klass = pair.first
  26626. assert_kind_of Class, klass
  26627. assert klass.ancestors.include?(ActionController::Base)
  26628. assert defined?(ModuleThatHoldsControllers::NestedController)
  26629. assert_equal klass, ModuleThatHoldsControllers::NestedController
  26630. ensure
  26631. $:[0..-1] = load_path
  26632. Object.send :remove_const, :RAILS_ROOT
  26633. end
  26634. end
  26635. end
  26636. require File.join(File.dirname(__FILE__), '..', 'abstract_unit')
  26637. module TestFileUtils
  26638. def file_name() File.basename(__FILE__) end
  26639. def file_path() File.expand_path(__FILE__) end
  26640. def file_data() File.open(file_path, 'rb') { |f| f.read } end
  26641. end
  26642. class SendFileController < ActionController::Base
  26643. include TestFileUtils
  26644. layout "layouts/standard" # to make sure layouts don't interfere
  26645. attr_writer :options
  26646. def options() @options ||= {} end
  26647. def file() send_file(file_path, options) end
  26648. def data() send_data(file_data, options) end
  26649. def rescue_action(e) raise end
  26650. end
  26651. SendFileController.template_root = File.dirname(__FILE__) + "/../fixtures/"
  26652. class SendFileTest < Test::Unit::TestCase
  26653. include TestFileUtils
  26654. def setup
  26655. @controller = SendFileController.new
  26656. @request = ActionController::TestRequest.new
  26657. @response = ActionController::TestResponse.new
  26658. end
  26659. def test_file_nostream
  26660. @controller.options = { :stream => false }
  26661. response = nil
  26662. assert_nothing_raised { response = process('file') }
  26663. assert_not_nil response
  26664. assert_kind_of String, response.body
  26665. assert_equal file_data, response.body
  26666. end
  26667. def test_file_stream
  26668. response = nil
  26669. assert_nothing_raised { response = process('file') }
  26670. assert_not_nil response
  26671. assert_kind_of Proc, response.body
  26672. require 'stringio'
  26673. output = StringIO.new
  26674. output.binmode
  26675. assert_nothing_raised { response.body.call(response, output) }
  26676. assert_equal file_data, output.string
  26677. end
  26678. def test_data
  26679. response = nil
  26680. assert_nothing_raised { response = process('data') }
  26681. assert_not_nil response
  26682. assert_kind_of String, response.body
  26683. assert_equal file_data, response.body
  26684. end
  26685. # Test that send_file_headers! is setting the correct HTTP headers.
  26686. def test_send_file_headers!
  26687. options = {
  26688. :length => 1,
  26689. :type => 'type',
  26690. :disposition => 'disposition',
  26691. :filename => 'filename'
  26692. }
  26693. # Do it a few times: the resulting headers should be identical
  26694. # no matter how many times you send with the same options.
  26695. # Test resolving Ticket #458.
  26696. @controller.headers = {}
  26697. @controller.send(:send_file_headers!, options)
  26698. @controller.send(:send_file_headers!, options)
  26699. @controller.send(:send_file_headers!, options)
  26700. h = @controller.headers
  26701. assert_equal 1, h['Content-Length']
  26702. assert_equal 'type', h['Content-Type']
  26703. assert_equal 'disposition; filename="filename"', h['Content-Disposition']
  26704. assert_equal 'binary', h['Content-Transfer-Encoding']
  26705. # test overriding Cache-Control: no-cache header to fix IE open/save dialog
  26706. @controller.headers = { 'Cache-Control' => 'no-cache' }
  26707. @controller.send(:send_file_headers!, options)
  26708. h = @controller.headers
  26709. assert_equal 'private', h['Cache-Control']
  26710. end
  26711. %w(file data).each do |method|
  26712. define_method "test_send_#{method}_status" do
  26713. @controller.options = { :stream => false, :status => 500 }
  26714. assert_nothing_raised { assert_not_nil process(method) }
  26715. assert_equal '500', @controller.headers['Status']
  26716. end
  26717. define_method "test_default_send_#{method}_status" do
  26718. @controller.options = { :stream => false }
  26719. assert_nothing_raised { assert_not_nil process(method) }
  26720. assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @controller.headers['Status']
  26721. end
  26722. end
  26723. end
  26724. require File.dirname(__FILE__) + '/../abstract_unit'
  26725. class SessionManagementTest < Test::Unit::TestCase
  26726. class SessionOffController < ActionController::Base
  26727. session :off
  26728. def show
  26729. render_text "done"
  26730. end
  26731. def tell
  26732. render_text "done"
  26733. end
  26734. end
  26735. class TestController < ActionController::Base
  26736. session :off, :only => :show
  26737. session :session_secure => true, :except => :show
  26738. session :off, :only => :conditional,
  26739. :if => Proc.new { |r| r.parameters[:ws] }
  26740. def show
  26741. render_text "done"
  26742. end
  26743. def tell
  26744. render_text "done"
  26745. end
  26746. def conditional
  26747. render_text ">>>#{params[:ws]}<<<"
  26748. end
  26749. end
  26750. class SpecializedController < SessionOffController
  26751. session :disabled => false, :only => :something
  26752. def something
  26753. render_text "done"
  26754. end
  26755. def another
  26756. render_text "done"
  26757. end
  26758. end
  26759. def setup
  26760. @request, @response = ActionController::TestRequest.new,
  26761. ActionController::TestResponse.new
  26762. end
  26763. def test_session_off_globally
  26764. @controller = SessionOffController.new
  26765. get :show
  26766. assert_equal false, @request.session_options
  26767. get :tell
  26768. assert_equal false, @request.session_options
  26769. end
  26770. def test_session_off_conditionally
  26771. @controller = TestController.new
  26772. get :show
  26773. assert_equal false, @request.session_options
  26774. get :tell
  26775. assert_instance_of Hash, @request.session_options
  26776. assert @request.session_options[:session_secure]
  26777. end
  26778. def test_controller_specialization_overrides_settings
  26779. @controller = SpecializedController.new
  26780. get :something
  26781. assert_instance_of Hash, @request.session_options
  26782. get :another
  26783. assert_equal false, @request.session_options
  26784. end
  26785. def test_session_off_with_if
  26786. @controller = TestController.new
  26787. get :conditional
  26788. assert_instance_of Hash, @request.session_options
  26789. get :conditional, :ws => "ws"
  26790. assert_equal false, @request.session_options
  26791. end
  26792. def test_session_store_setting
  26793. ActionController::Base.session_store = :drb_store
  26794. assert_equal CGI::Session::DRbStore, ActionController::Base.session_store
  26795. if Object.const_defined?(:ActiveRecord)
  26796. ActionController::Base.session_store = :active_record_store
  26797. assert_equal CGI::Session::ActiveRecordStore, ActionController::Base.session_store
  26798. end
  26799. end
  26800. end
  26801. require File.dirname(__FILE__) + '/../abstract_unit'
  26802. require File.dirname(__FILE__) + '/fake_controllers'
  26803. class TestTest < Test::Unit::TestCase
  26804. class TestController < ActionController::Base
  26805. def set_flash
  26806. flash["test"] = ">#{flash["test"]}<"
  26807. render :text => 'ignore me'
  26808. end
  26809. def render_raw_post
  26810. raise Test::Unit::AssertionFailedError, "#raw_post is blank" if request.raw_post.blank?
  26811. render :text => request.raw_post
  26812. end
  26813. def test_params
  26814. render :text => params.inspect
  26815. end
  26816. def test_uri
  26817. render :text => request.request_uri
  26818. end
  26819. def test_html_output
  26820. render :text => <<HTML
  26821. <html>
  26822. <body>
  26823. <a href="/"><img src="/images/button.png" /></a>
  26824. <div id="foo">
  26825. <ul>
  26826. <li class="item">hello</li>
  26827. <li class="item">goodbye</li>
  26828. </ul>
  26829. </div>
  26830. <div id="bar">
  26831. <form action="/somewhere">
  26832. Name: <input type="text" name="person[name]" id="person_name" />
  26833. </form>
  26834. </div>
  26835. </body>
  26836. </html>
  26837. HTML
  26838. end
  26839. def test_only_one_param
  26840. render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
  26841. end
  26842. def test_remote_addr
  26843. render :text => (request.remote_addr || "not specified")
  26844. end
  26845. def test_file_upload
  26846. render :text => params[:file].size
  26847. end
  26848. def redirect_to_symbol
  26849. redirect_to :generate_url, :id => 5
  26850. end
  26851. private
  26852. def rescue_action(e)
  26853. raise e
  26854. end
  26855. def generate_url(opts)
  26856. url_for(opts.merge(:action => "test_uri"))
  26857. end
  26858. end
  26859. def setup
  26860. @controller = TestController.new
  26861. @request = ActionController::TestRequest.new
  26862. @response = ActionController::TestResponse.new
  26863. ActionController::Routing::Routes.reload
  26864. end
  26865. def teardown
  26866. ActionController::Routing::Routes.reload
  26867. end
  26868. def test_raw_post_handling
  26869. params = {:page => {:name => 'page name'}, 'some key' => 123}
  26870. get :render_raw_post, params.dup
  26871. raw_post = params.map {|k,v| [CGI::escape(k.to_s), CGI::escape(v.to_s)].join('=')}.sort.join('&')
  26872. assert_equal raw_post, @response.body
  26873. end
  26874. def test_process_without_flash
  26875. process :set_flash
  26876. assert_equal '><', flash['test']
  26877. end
  26878. def test_process_with_flash
  26879. process :set_flash, nil, nil, { "test" => "value" }
  26880. assert_equal '>value<', flash['test']
  26881. end
  26882. def test_process_with_request_uri_with_no_params
  26883. process :test_uri
  26884. assert_equal "/test_test/test/test_uri", @response.body
  26885. end
  26886. def test_process_with_request_uri_with_params
  26887. process :test_uri, :id => 7
  26888. assert_equal "/test_test/test/test_uri/7", @response.body
  26889. end
  26890. def test_process_with_request_uri_with_params_with_explicit_uri
  26891. @request.set_REQUEST_URI "/explicit/uri"
  26892. process :test_uri, :id => 7
  26893. assert_equal "/explicit/uri", @response.body
  26894. end
  26895. def test_multiple_calls
  26896. process :test_only_one_param, :left => true
  26897. assert_equal "OK", @response.body
  26898. process :test_only_one_param, :right => true
  26899. assert_equal "OK", @response.body
  26900. end
  26901. def test_assert_tag_tag
  26902. process :test_html_output
  26903. # there is a 'form' tag
  26904. assert_tag :tag => 'form'
  26905. # there is not an 'hr' tag
  26906. assert_no_tag :tag => 'hr'
  26907. end
  26908. def test_assert_tag_attributes
  26909. process :test_html_output
  26910. # there is a tag with an 'id' of 'bar'
  26911. assert_tag :attributes => { :id => "bar" }
  26912. # there is no tag with a 'name' of 'baz'
  26913. assert_no_tag :attributes => { :name => "baz" }
  26914. end
  26915. def test_assert_tag_parent
  26916. process :test_html_output
  26917. # there is a tag with a parent 'form' tag
  26918. assert_tag :parent => { :tag => "form" }
  26919. # there is no tag with a parent of 'input'
  26920. assert_no_tag :parent => { :tag => "input" }
  26921. end
  26922. def test_assert_tag_child
  26923. process :test_html_output
  26924. # there is a tag with a child 'input' tag
  26925. assert_tag :child => { :tag => "input" }
  26926. # there is no tag with a child 'strong' tag
  26927. assert_no_tag :child => { :tag => "strong" }
  26928. end
  26929. def test_assert_tag_ancestor
  26930. process :test_html_output
  26931. # there is a 'li' tag with an ancestor having an id of 'foo'
  26932. assert_tag :ancestor => { :attributes => { :id => "foo" } }, :tag => "li"
  26933. # there is no tag of any kind with an ancestor having an href matching 'foo'
  26934. assert_no_tag :ancestor => { :attributes => { :href => /foo/ } }
  26935. end
  26936. def test_assert_tag_descendant
  26937. process :test_html_output
  26938. # there is a tag with a decendant 'li' tag
  26939. assert_tag :descendant => { :tag => "li" }
  26940. # there is no tag with a descendant 'html' tag
  26941. assert_no_tag :descendant => { :tag => "html" }
  26942. end
  26943. def test_assert_tag_sibling
  26944. process :test_html_output
  26945. # there is a tag with a sibling of class 'item'
  26946. assert_tag :sibling => { :attributes => { :class => "item" } }
  26947. # there is no tag with a sibling 'ul' tag
  26948. assert_no_tag :sibling => { :tag => "ul" }
  26949. end
  26950. def test_assert_tag_after
  26951. process :test_html_output
  26952. # there is a tag following a sibling 'div' tag
  26953. assert_tag :after => { :tag => "div" }
  26954. # there is no tag following a sibling tag with id 'bar'
  26955. assert_no_tag :after => { :attributes => { :id => "bar" } }
  26956. end
  26957. def test_assert_tag_before
  26958. process :test_html_output
  26959. # there is a tag preceeding a tag with id 'bar'
  26960. assert_tag :before => { :attributes => { :id => "bar" } }
  26961. # there is no tag preceeding a 'form' tag
  26962. assert_no_tag :before => { :tag => "form" }
  26963. end
  26964. def test_assert_tag_children_count
  26965. process :test_html_output
  26966. # there is a tag with 2 children
  26967. assert_tag :children => { :count => 2 }
  26968. # there is no tag with 4 children
  26969. assert_no_tag :children => { :count => 4 }
  26970. end
  26971. def test_assert_tag_children_less_than
  26972. process :test_html_output
  26973. # there is a tag with less than 5 children
  26974. assert_tag :children => { :less_than => 5 }
  26975. # there is no 'ul' tag with less than 2 children
  26976. assert_no_tag :children => { :less_than => 2 }, :tag => "ul"
  26977. end
  26978. def test_assert_tag_children_greater_than
  26979. process :test_html_output
  26980. # there is a 'body' tag with more than 1 children
  26981. assert_tag :children => { :greater_than => 1 }, :tag => "body"
  26982. # there is no tag with more than 10 children
  26983. assert_no_tag :children => { :greater_than => 10 }
  26984. end
  26985. def test_assert_tag_children_only
  26986. process :test_html_output
  26987. # there is a tag containing only one child with an id of 'foo'
  26988. assert_tag :children => { :count => 1,
  26989. :only => { :attributes => { :id => "foo" } } }
  26990. # there is no tag containing only one 'li' child
  26991. assert_no_tag :children => { :count => 1, :only => { :tag => "li" } }
  26992. end
  26993. def test_assert_tag_content
  26994. process :test_html_output
  26995. # the output contains the string "Name"
  26996. assert_tag :content => "Name"
  26997. # the output does not contain the string "test"
  26998. assert_no_tag :content => "test"
  26999. end
  27000. def test_assert_tag_multiple
  27001. process :test_html_output
  27002. # there is a 'div', id='bar', with an immediate child whose 'action'
  27003. # attribute matches the regexp /somewhere/.
  27004. assert_tag :tag => "div", :attributes => { :id => "bar" },
  27005. :child => { :attributes => { :action => /somewhere/ } }
  27006. # there is no 'div', id='foo', with a 'ul' child with more than
  27007. # 2 "li" children.
  27008. assert_no_tag :tag => "div", :attributes => { :id => "foo" },
  27009. :child => {
  27010. :tag => "ul",
  27011. :children => { :greater_than => 2,
  27012. :only => { :tag => "li" } } }
  27013. end
  27014. def test_assert_tag_children_without_content
  27015. process :test_html_output
  27016. # there is a form tag with an 'input' child which is a self closing tag
  27017. assert_tag :tag => "form",
  27018. :children => { :count => 1,
  27019. :only => { :tag => "input" } }
  27020. # the body tag has an 'a' child which in turn has an 'img' child
  27021. assert_tag :tag => "body",
  27022. :children => { :count => 1,
  27023. :only => { :tag => "a",
  27024. :children => { :count => 1,
  27025. :only => { :tag => "img" } } } }
  27026. end
  27027. def test_assert_generates
  27028. assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5'
  27029. end
  27030. def test_assert_routing
  27031. assert_routing 'content', :controller => 'content', :action => 'index'
  27032. end
  27033. def test_assert_routing_in_module
  27034. assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
  27035. end
  27036. def test_params_passing
  27037. get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
  27038. parsed_params = eval(@response.body)
  27039. assert_equal(
  27040. {'controller' => 'test_test/test', 'action' => 'test_params',
  27041. 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
  27042. parsed_params
  27043. )
  27044. end
  27045. def test_id_converted_to_string
  27046. get :test_params, :id => 20, :foo => Object.new
  27047. assert_kind_of String, @request.path_parameters['id']
  27048. end
  27049. def test_array_path_parameter_handled_properly
  27050. with_routing do |set|
  27051. set.draw do
  27052. set.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params'
  27053. set.connect ':controller/:action/:id'
  27054. end
  27055. get :test_params, :path => ['hello', 'world']
  27056. assert_equal ['hello', 'world'], @request.path_parameters['path']
  27057. assert_equal 'hello/world', @request.path_parameters['path'].to_s
  27058. end
  27059. end
  27060. def test_assert_realistic_path_parameters
  27061. get :test_params, :id => 20, :foo => Object.new
  27062. # All elements of path_parameters should use string keys
  27063. @request.path_parameters.keys.each do |key|
  27064. assert_kind_of String, key
  27065. end
  27066. end
  27067. def test_with_routing_places_routes_back
  27068. assert ActionController::Routing::Routes
  27069. routes_id = ActionController::Routing::Routes.object_id
  27070. begin
  27071. with_routing { raise 'fail' }
  27072. fail 'Should not be here.'
  27073. rescue RuntimeError
  27074. end
  27075. assert ActionController::Routing::Routes
  27076. assert_equal routes_id, ActionController::Routing::Routes.object_id
  27077. end
  27078. def test_remote_addr
  27079. get :test_remote_addr
  27080. assert_equal "0.0.0.0", @response.body
  27081. @request.remote_addr = "192.0.0.1"
  27082. get :test_remote_addr
  27083. assert_equal "192.0.0.1", @response.body
  27084. end
  27085. def test_header_properly_reset_after_remote_http_request
  27086. xhr :get, :test_params
  27087. assert_nil @request.env['HTTP_X_REQUESTED_WITH']
  27088. end
  27089. def test_header_properly_reset_after_get_request
  27090. get :test_params
  27091. @request.recycle!
  27092. assert_nil @request.instance_variable_get("@request_method")
  27093. end
  27094. %w(controller response request).each do |variable|
  27095. %w(get post put delete head process).each do |method|
  27096. define_method("test_#{variable}_missing_for_#{method}_raises_error") do
  27097. remove_instance_variable "@#{variable}"
  27098. begin
  27099. send(method, :test_remote_addr)
  27100. assert false, "expected RuntimeError, got nothing"
  27101. rescue RuntimeError => error
  27102. assert true
  27103. assert_match %r{@#{variable} is nil}, error.message
  27104. rescue => error
  27105. assert false, "expected RuntimeError, got #{error.class}"
  27106. end
  27107. end
  27108. end
  27109. end
  27110. FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart'
  27111. def test_test_uploaded_file
  27112. filename = 'mona_lisa.jpg'
  27113. path = "#{FILES_DIR}/#{filename}"
  27114. content_type = 'image/png'
  27115. file = ActionController::TestUploadedFile.new(path, content_type)
  27116. assert_equal filename, file.original_filename
  27117. assert_equal content_type, file.content_type
  27118. assert_equal file.path, file.local_path
  27119. assert_equal File.read(path), file.read
  27120. end
  27121. def test_fixture_file_upload
  27122. post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
  27123. assert_equal 159528, @response.body
  27124. end
  27125. def test_test_uploaded_file_exception_when_file_doesnt_exist
  27126. assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') }
  27127. end
  27128. def test_assert_redirected_to_symbol
  27129. get :redirect_to_symbol
  27130. assert_redirected_to :generate_url
  27131. end
  27132. end
  27133. require File.dirname(__FILE__) + '/../abstract_unit'
  27134. class UrlRewriterTests < Test::Unit::TestCase
  27135. def setup
  27136. @request = ActionController::TestRequest.new
  27137. @params = {}
  27138. @rewriter = ActionController::UrlRewriter.new(@request, @params)
  27139. end
  27140. def test_simple_build_query_string
  27141. assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2')
  27142. end
  27143. def test_convert_ints_build_query_string
  27144. assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2)
  27145. end
  27146. def test_escape_spaces_build_query_string
  27147. assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world')
  27148. end
  27149. def test_expand_array_build_query_string
  27150. assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2])
  27151. end
  27152. def test_escape_spaces_build_query_string_selected_keys
  27153. assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x])
  27154. end
  27155. def test_overwrite_params
  27156. @params[:controller] = 'hi'
  27157. @params[:action] = 'bye'
  27158. @params[:id] = '2'
  27159. assert_equal '/hi/hi/2', @rewriter.rewrite(:only_path => true, :overwrite_params => {:action => 'hi'})
  27160. u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'})
  27161. assert_match %r(/hi/hi/2$), u
  27162. end
  27163. private
  27164. def split_query_string(str)
  27165. [str[0].chr] + str[1..-1].split(/&/).sort
  27166. end
  27167. def assert_query_equal(q1, q2)
  27168. assert_equal(split_query_string(q1), split_query_string(q2))
  27169. end
  27170. end
  27171. require File.dirname(__FILE__) + '/../abstract_unit'
  27172. class VerificationTest < Test::Unit::TestCase
  27173. class TestController < ActionController::Base
  27174. verify :only => :guarded_one, :params => "one",
  27175. :redirect_to => { :action => "unguarded" }
  27176. verify :only => :guarded_two, :params => %w( one two ),
  27177. :redirect_to => { :action => "unguarded" }
  27178. verify :only => :guarded_with_flash, :params => "one",
  27179. :add_flash => { "notice" => "prereqs failed" },
  27180. :redirect_to => { :action => "unguarded" }
  27181. verify :only => :guarded_in_session, :session => "one",
  27182. :redirect_to => { :action => "unguarded" }
  27183. verify :only => [:multi_one, :multi_two], :session => %w( one two ),
  27184. :redirect_to => { :action => "unguarded" }
  27185. verify :only => :guarded_by_method, :method => :post,
  27186. :redirect_to => { :action => "unguarded" }
  27187. verify :only => :guarded_by_xhr, :xhr => true,
  27188. :redirect_to => { :action => "unguarded" }
  27189. verify :only => :guarded_by_not_xhr, :xhr => false,
  27190. :redirect_to => { :action => "unguarded" }
  27191. before_filter :unconditional_redirect, :only => :two_redirects
  27192. verify :only => :two_redirects, :method => :post,
  27193. :redirect_to => { :action => "unguarded" }
  27194. verify :only => :must_be_post, :method => :post, :render => { :status => 500, :text => "Must be post"}
  27195. def guarded_one
  27196. render :text => "#{@params["one"]}"
  27197. end
  27198. def guarded_with_flash
  27199. render :text => "#{@params["one"]}"
  27200. end
  27201. def guarded_two
  27202. render :text => "#{@params["one"]}:#{@params["two"]}"
  27203. end
  27204. def guarded_in_session
  27205. render :text => "#{@session["one"]}"
  27206. end
  27207. def multi_one
  27208. render :text => "#{@session["one"]}:#{@session["two"]}"
  27209. end
  27210. def multi_two
  27211. render :text => "#{@session["two"]}:#{@session["one"]}"
  27212. end
  27213. def guarded_by_method
  27214. render :text => "#{@request.method}"
  27215. end
  27216. def guarded_by_xhr
  27217. render :text => "#{@request.xhr?}"
  27218. end
  27219. def guarded_by_not_xhr
  27220. render :text => "#{@request.xhr?}"
  27221. end
  27222. def unguarded
  27223. render :text => "#{@params["one"]}"
  27224. end
  27225. def two_redirects
  27226. render :nothing => true
  27227. end
  27228. def must_be_post
  27229. render :text => "Was a post!"
  27230. end
  27231. protected
  27232. def rescue_action(e) raise end
  27233. def unconditional_redirect
  27234. redirect_to :action => "unguarded"
  27235. end
  27236. end
  27237. def setup
  27238. @controller = TestController.new
  27239. @request = ActionController::TestRequest.new
  27240. @response = ActionController::TestResponse.new
  27241. end
  27242. def test_guarded_one_with_prereqs
  27243. get :guarded_one, :one => "here"
  27244. assert_equal "here", @response.body
  27245. end
  27246. def test_guarded_one_without_prereqs
  27247. get :guarded_one
  27248. assert_redirected_to :action => "unguarded"
  27249. end
  27250. def test_guarded_with_flash_with_prereqs
  27251. get :guarded_with_flash, :one => "here"
  27252. assert_equal "here", @response.body
  27253. assert_flash_empty
  27254. end
  27255. def test_guarded_with_flash_without_prereqs
  27256. get :guarded_with_flash
  27257. assert_redirected_to :action => "unguarded"
  27258. assert_flash_equal "prereqs failed", "notice"
  27259. end
  27260. def test_guarded_two_with_prereqs
  27261. get :guarded_two, :one => "here", :two => "there"
  27262. assert_equal "here:there", @response.body
  27263. end
  27264. def test_guarded_two_without_prereqs_one
  27265. get :guarded_two, :two => "there"
  27266. assert_redirected_to :action => "unguarded"
  27267. end
  27268. def test_guarded_two_without_prereqs_two
  27269. get :guarded_two, :one => "here"
  27270. assert_redirected_to :action => "unguarded"
  27271. end
  27272. def test_guarded_two_without_prereqs_both
  27273. get :guarded_two
  27274. assert_redirected_to :action => "unguarded"
  27275. end
  27276. def test_unguarded_with_params
  27277. get :unguarded, :one => "here"
  27278. assert_equal "here", @response.body
  27279. end
  27280. def test_unguarded_without_params
  27281. get :unguarded
  27282. assert_equal "", @response.body
  27283. end
  27284. def test_guarded_in_session_with_prereqs
  27285. get :guarded_in_session, {}, "one" => "here"
  27286. assert_equal "here", @response.body
  27287. end
  27288. def test_guarded_in_session_without_prereqs
  27289. get :guarded_in_session
  27290. assert_redirected_to :action => "unguarded"
  27291. end
  27292. def test_multi_one_with_prereqs
  27293. get :multi_one, {}, "one" => "here", "two" => "there"
  27294. assert_equal "here:there", @response.body
  27295. end
  27296. def test_multi_one_without_prereqs
  27297. get :multi_one
  27298. assert_redirected_to :action => "unguarded"
  27299. end
  27300. def test_multi_two_with_prereqs
  27301. get :multi_two, {}, "one" => "here", "two" => "there"
  27302. assert_equal "there:here", @response.body
  27303. end
  27304. def test_multi_two_without_prereqs
  27305. get :multi_two
  27306. assert_redirected_to :action => "unguarded"
  27307. end
  27308. def test_guarded_by_method_with_prereqs
  27309. post :guarded_by_method
  27310. assert_equal "post", @response.body
  27311. end
  27312. def test_guarded_by_method_without_prereqs
  27313. get :guarded_by_method
  27314. assert_redirected_to :action => "unguarded"
  27315. end
  27316. def test_guarded_by_xhr_with_prereqs
  27317. xhr :post, :guarded_by_xhr
  27318. assert_equal "true", @response.body
  27319. end
  27320. def test_guarded_by_xhr_without_prereqs
  27321. get :guarded_by_xhr
  27322. assert_redirected_to :action => "unguarded"
  27323. end
  27324. def test_guarded_by_not_xhr_with_prereqs
  27325. get :guarded_by_not_xhr
  27326. assert_equal "false", @response.body
  27327. end
  27328. def test_guarded_by_not_xhr_without_prereqs
  27329. xhr :post, :guarded_by_not_xhr
  27330. assert_redirected_to :action => "unguarded"
  27331. end
  27332. def test_guarded_post_and_calls_render_succeeds
  27333. post :must_be_post
  27334. assert_equal "Was a post!", @response.body
  27335. end
  27336. def test_guarded_post_and_calls_render_fails
  27337. get :must_be_post
  27338. assert_response 500
  27339. assert_equal "Must be post", @response.body
  27340. end
  27341. def test_second_redirect
  27342. assert_nothing_raised { get :two_redirects }
  27343. end
  27344. end
  27345. require File.dirname(__FILE__) + '/../abstract_unit'
  27346. require 'stringio'
  27347. class WebServiceTest < Test::Unit::TestCase
  27348. class MockCGI < CGI #:nodoc:
  27349. attr_accessor :stdinput, :stdoutput, :env_table
  27350. def initialize(env, data = '')
  27351. self.env_table = env
  27352. self.stdinput = StringIO.new(data)
  27353. self.stdoutput = StringIO.new
  27354. super()
  27355. end
  27356. end
  27357. class TestController < ActionController::Base
  27358. session :off
  27359. def assign_parameters
  27360. if params[:full]
  27361. render :text => dump_params_keys
  27362. else
  27363. render :text => (params.keys - ['controller', 'action']).sort.join(", ")
  27364. end
  27365. end
  27366. def dump_params_keys(hash=params)
  27367. hash.keys.sort.inject("") do |s, k|
  27368. value = hash[k]
  27369. value = Hash === value ? "(#{dump_params_keys(value)})" : ""
  27370. s << ", " unless s.empty?
  27371. s << "#{k}#{value}"
  27372. end
  27373. end
  27374. def rescue_action(e) raise end
  27375. end
  27376. def setup
  27377. @controller = TestController.new
  27378. ActionController::Base.param_parsers.clear
  27379. ActionController::Base.param_parsers[Mime::XML] = :xml_node
  27380. end
  27381. def test_check_parameters
  27382. process('GET')
  27383. assert_equal '', @controller.response.body
  27384. end
  27385. def test_post_xml
  27386. process('POST', 'application/xml', '<entry attributed="true"><summary>content...</summary></entry>')
  27387. assert_equal 'entry', @controller.response.body
  27388. assert @controller.params.has_key?(:entry)
  27389. assert_equal 'content...', @controller.params["entry"].summary.node_value
  27390. assert_equal 'true', @controller.params["entry"]['attributed']
  27391. end
  27392. def test_put_xml
  27393. process('PUT', 'application/xml', '<entry attributed="true"><summary>content...</summary></entry>')
  27394. assert_equal 'entry', @controller.response.body
  27395. assert @controller.params.has_key?(:entry)
  27396. assert_equal 'content...', @controller.params["entry"].summary.node_value
  27397. assert_equal 'true', @controller.params["entry"]['attributed']
  27398. end
  27399. def test_register_and_use_yaml
  27400. ActionController::Base.param_parsers[Mime::YAML] = Proc.new { |d| YAML.load(d) }
  27401. process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml)
  27402. assert_equal 'entry', @controller.response.body
  27403. assert @controller.params.has_key?(:entry)
  27404. assert_equal 'loaded from yaml', @controller.params["entry"]
  27405. end
  27406. def test_register_and_use_yaml_as_symbol
  27407. ActionController::Base.param_parsers[Mime::YAML] = :yaml
  27408. process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml)
  27409. assert_equal 'entry', @controller.response.body
  27410. assert @controller.params.has_key?(:entry)
  27411. assert_equal 'loaded from yaml', @controller.params["entry"]
  27412. end
  27413. def test_register_and_use_xml_simple
  27414. ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
  27415. process('POST', 'application/xml', '<request><summary>content...</summary><title>SimpleXml</title></request>' )
  27416. assert_equal 'summary, title', @controller.response.body
  27417. assert @controller.params.has_key?(:summary)
  27418. assert @controller.params.has_key?(:title)
  27419. assert_equal 'content...', @controller.params["summary"]
  27420. assert_equal 'SimpleXml', @controller.params["title"]
  27421. end
  27422. def test_use_xml_ximple_with_empty_request
  27423. ActionController::Base.param_parsers[Mime::XML] = :xml_simple
  27424. assert_nothing_raised { process('POST', 'application/xml', "") }
  27425. assert_equal "", @controller.response.body
  27426. end
  27427. def test_deprecated_request_methods
  27428. process('POST', 'application/x-yaml')
  27429. assert_equal Mime::YAML, @controller.request.content_type
  27430. assert_equal true, @controller.request.post?
  27431. assert_equal :yaml, @controller.request.post_format
  27432. assert_equal true, @controller.request.yaml_post?
  27433. assert_equal false, @controller.request.xml_post?
  27434. end
  27435. def test_dasherized_keys_as_xml
  27436. ActionController::Base.param_parsers[Mime::XML] = :xml_simple
  27437. process('POST', 'application/xml', "<first-key>\n<sub-key>...</sub-key>\n</first-key>", true)
  27438. assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
  27439. assert_equal "...", @controller.params[:first_key][:sub_key]
  27440. end
  27441. def test_typecast_as_xml
  27442. ActionController::Base.param_parsers[Mime::XML] = :xml_simple
  27443. process('POST', 'application/xml', <<-XML)
  27444. <data>
  27445. <a type="integer">15</a>
  27446. <b type="boolean">false</b>
  27447. <c type="boolean">true</c>
  27448. <d type="date">2005-03-17</d>
  27449. <e type="datetime">2005-03-17T21:41:07Z</e>
  27450. <f>unparsed</f>
  27451. <g type="integer">1</g>
  27452. <g>hello</g>
  27453. <g type="date">1974-07-25</g>
  27454. </data>
  27455. XML
  27456. params = @controller.params
  27457. assert_equal 15, params[:data][:a]
  27458. assert_equal false, params[:data][:b]
  27459. assert_equal true, params[:data][:c]
  27460. assert_equal Date.new(2005,3,17), params[:data][:d]
  27461. assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
  27462. assert_equal "unparsed", params[:data][:f]
  27463. assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
  27464. end
  27465. def test_entities_unescaped_as_xml_simple
  27466. ActionController::Base.param_parsers[Mime::XML] = :xml_simple
  27467. process('POST', 'application/xml', <<-XML)
  27468. <data>&lt;foo &quot;bar&apos;s&quot; &amp; friends&gt;</data>
  27469. XML
  27470. assert_equal %(<foo "bar's" & friends>), @controller.params[:data]
  27471. end
  27472. def test_dasherized_keys_as_yaml
  27473. ActionController::Base.param_parsers[Mime::YAML] = :yaml
  27474. process('POST', 'application/x-yaml', "---\nfirst-key:\n sub-key: ...\n", true)
  27475. assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
  27476. assert_equal "...", @controller.params[:first_key][:sub_key]
  27477. end
  27478. def test_typecast_as_yaml
  27479. ActionController::Base.param_parsers[Mime::YAML] = :yaml
  27480. process('POST', 'application/x-yaml', <<-YAML)
  27481. ---
  27482. data:
  27483. a: 15
  27484. b: false
  27485. c: true
  27486. d: 2005-03-17
  27487. e: 2005-03-17T21:41:07Z
  27488. f: unparsed
  27489. g:
  27490. - 1
  27491. - hello
  27492. - 1974-07-25
  27493. YAML
  27494. params = @controller.params
  27495. assert_equal 15, params[:data][:a]
  27496. assert_equal false, params[:data][:b]
  27497. assert_equal true, params[:data][:c]
  27498. assert_equal Date.new(2005,3,17), params[:data][:d]
  27499. assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
  27500. assert_equal "unparsed", params[:data][:f]
  27501. assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
  27502. end
  27503. private
  27504. def process(verb, content_type = 'application/x-www-form-urlencoded', data = '', full=false)
  27505. cgi = MockCGI.new({
  27506. 'REQUEST_METHOD' => verb,
  27507. 'CONTENT_TYPE' => content_type,
  27508. 'QUERY_STRING' => "action=assign_parameters&controller=webservicetest/test#{"&full=1" if full}",
  27509. "REQUEST_URI" => "/",
  27510. "HTTP_HOST" => 'testdomain.com',
  27511. "CONTENT_LENGTH" => data.size,
  27512. "SERVER_PORT" => "80",
  27513. "HTTPS" => "off"}, data)
  27514. @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
  27515. end
  27516. end
  27517. class XmlNodeTest < Test::Unit::TestCase
  27518. def test_all
  27519. xn = XmlNode.from_xml(%{<?xml version="1.0" encoding="UTF-8"?>
  27520. <response success='true'>
  27521. <page title='Ajax Summit' id='1133' email_address='ry87ib@backpackit.com'>
  27522. <description>With O'Reilly and Adaptive Path</description>
  27523. <notes>
  27524. <note title='Hotel' id='1020' created_at='2005-05-14 16:41:11'>
  27525. Staying at the Savoy
  27526. </note>
  27527. </notes>
  27528. <tags>
  27529. <tag name='Technology' id='4' />
  27530. <tag name='Travel' id='5' />
  27531. </tags>
  27532. </page>
  27533. </response>
  27534. }
  27535. )
  27536. assert_equal 'UTF-8', xn.node.document.encoding
  27537. assert_equal '1.0', xn.node.document.version
  27538. assert_equal 'true', xn['success']
  27539. assert_equal 'response', xn.node_name
  27540. assert_equal 'Ajax Summit', xn.page['title']
  27541. assert_equal '1133', xn.page['id']
  27542. assert_equal "With O'Reilly and Adaptive Path", xn.page.description.node_value
  27543. assert_equal nil, xn.nonexistent
  27544. assert_equal "Staying at the Savoy", xn.page.notes.note.node_value.strip
  27545. assert_equal 'Technology', xn.page.tags.tag[0]['name']
  27546. assert_equal 'Travel', xn.page.tags.tag[1][:name]
  27547. matches = xn.xpath('//@id').map{ |id| id.to_i }
  27548. assert_equal [4, 5, 1020, 1133], matches.sort
  27549. matches = xn.xpath('//tag').map{ |tag| tag['name'] }
  27550. assert_equal ['Technology', 'Travel'], matches.sort
  27551. assert_equal "Ajax Summit", xn.page['title']
  27552. xn.page['title'] = 'Ajax Summit V2'
  27553. assert_equal "Ajax Summit V2", xn.page['title']
  27554. assert_equal "Staying at the Savoy", xn.page.notes.note.node_value.strip
  27555. xn.page.notes.note.node_value = "Staying at the Ritz"
  27556. assert_equal "Staying at the Ritz", xn.page.notes.note.node_value.strip
  27557. assert_equal '5', xn.page.tags.tag[1][:id]
  27558. xn.page.tags.tag[1]['id'] = '7'
  27559. assert_equal '7', xn.page.tags.tag[1]['id']
  27560. end
  27561. def test_small_entry
  27562. node = XmlNode.from_xml('<entry>hi</entry>')
  27563. assert_equal 'hi', node.node_value
  27564. end
  27565. end
  27566. class AClassThatContainsAController::PoorlyPlacedController < ActionController::Base
  27567. def self.is_evil?
  27568. :decidedly_so
  27569. end
  27570. endclass ModuleThatHoldsControllers::NestedController < ActionController::Base
  27571. endclass AClassThatContainsAController #often < ActiveRecord::Base
  27572. def self.is_special?
  27573. :you_know_it
  27574. end
  27575. endclass Company < ActiveRecord::Base
  27576. attr_protected :rating
  27577. set_sequence_name :companies_nonstd_seq
  27578. validates_presence_of :name
  27579. def validate
  27580. errors.add('rating', 'rating should not be 2') if rating == 2
  27581. end
  27582. endclass Developer < ActiveRecord::Base
  27583. has_and_belongs_to_many :projects
  27584. end
  27585. class DeVeLoPeR < ActiveRecord::Base
  27586. set_table_name "developers"
  27587. end
  27588. # see routing/controller component tests
  27589. raise Exception, "I should never be loaded"module AbcHelper
  27590. def bare_a() end
  27591. def bare_b() end
  27592. def bare_c() end
  27593. end
  27594. module Fun::GamesHelper
  27595. def stratego() "Iz guuut!" end
  27596. endmodule Fun::PDFHelper
  27597. def foobar() 'baz' end
  27598. end
  27599. class Project < ActiveRecord::Base
  27600. has_and_belongs_to_many :developers, :uniq => true
  27601. end
  27602. class Reply < ActiveRecord::Base
  27603. belongs_to :topic, :include => [:replies]
  27604. validates_presence_of :content
  27605. end
  27606. class Topic < ActiveRecord::Base
  27607. has_many :replies, :include => [:user], :dependent => true
  27608. endrequire 'test/unit'
  27609. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper'
  27610. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_helper'
  27611. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/text_helper'
  27612. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
  27613. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
  27614. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_tag_helper'
  27615. # require File.dirname(__FILE__) + '/../../lib/action_view/helpers/active_record_helper'
  27616. class ActiveRecordHelperTest < Test::Unit::TestCase
  27617. include ActionView::Helpers::FormHelper
  27618. include ActionView::Helpers::ActiveRecordHelper
  27619. include ActionView::Helpers::TextHelper
  27620. include ActionView::Helpers::TagHelper
  27621. include ActionView::Helpers::UrlHelper
  27622. include ActionView::Helpers::FormTagHelper
  27623. silence_warnings do
  27624. Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
  27625. Post.class_eval do
  27626. alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
  27627. alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
  27628. alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
  27629. end
  27630. Column = Struct.new("Column", :type, :name, :human_name)
  27631. end
  27632. def setup
  27633. @post = Post.new
  27634. def @post.errors
  27635. Class.new {
  27636. def on(field) field == "author_name" || field == "body" end
  27637. def empty?() false end
  27638. def count() 1 end
  27639. def full_messages() [ "Author name can't be empty" ] end
  27640. }.new
  27641. end
  27642. def @post.new_record?() true end
  27643. def @post.to_param() nil end
  27644. def @post.column_for_attribute(attr_name)
  27645. Post.content_columns.select { |column| column.name == attr_name }.first
  27646. end
  27647. def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
  27648. @post.title = "Hello World"
  27649. @post.author_name = ""
  27650. @post.body = "Back to the hill and over it again!"
  27651. @post.secret = 1
  27652. @post.written_on = Date.new(2004, 6, 15)
  27653. @controller = Object.new
  27654. def @controller.url_for(options, *parameters_for_method_reference)
  27655. options = options.symbolize_keys
  27656. [options[:action], options[:id].to_param].compact.join('/')
  27657. end
  27658. end
  27659. def test_generic_input_tag
  27660. assert_dom_equal(
  27661. %(<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />), input("post", "title")
  27662. )
  27663. end
  27664. def test_text_area_with_errors
  27665. assert_dom_equal(
  27666. %(<div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>),
  27667. text_area("post", "body")
  27668. )
  27669. end
  27670. def test_text_field_with_errors
  27671. assert_dom_equal(
  27672. %(<div class="fieldWithErrors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>),
  27673. text_field("post", "author_name")
  27674. )
  27675. end
  27676. def test_form_with_string
  27677. assert_dom_equal(
  27678. %(<form action="create" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
  27679. form("post")
  27680. )
  27681. class << @post
  27682. def new_record?() false end
  27683. def to_param() id end
  27684. def id() 1 end
  27685. end
  27686. assert_dom_equal(
  27687. %(<form action="update/1" method="post"><input id="post_id" name="post[id]" type="hidden" value="1" /><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Update" /></form>),
  27688. form("post")
  27689. )
  27690. end
  27691. def test_form_with_date
  27692. def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
  27693. assert_dom_equal(
  27694. %(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
  27695. form("post")
  27696. )
  27697. end
  27698. def test_form_with_datetime
  27699. def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
  27700. @post.written_on = Time.gm(2004, 6, 15, 16, 30)
  27701. assert_dom_equal(
  27702. %(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n &mdash; <select name="post[written_on(4i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select name="post[written_on(5i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
  27703. form("post")
  27704. )
  27705. end
  27706. def test_error_for_block
  27707. assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post")
  27708. assert_equal %(<div class="errorDeathByClass" id="errorDeathById"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
  27709. end
  27710. def test_error_messages_for_handles_nil
  27711. assert_equal "", error_messages_for("notthere")
  27712. end
  27713. def test_form_with_string_multipart
  27714. assert_dom_equal(
  27715. %(<form action="create" enctype="multipart/form-data" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
  27716. form("post", :multipart => true)
  27717. )
  27718. end
  27719. end
  27720. require File.dirname(__FILE__) + '/../abstract_unit'
  27721. class AssetTagHelperTest < Test::Unit::TestCase
  27722. include ActionView::Helpers::TagHelper
  27723. include ActionView::Helpers::UrlHelper
  27724. include ActionView::Helpers::AssetTagHelper
  27725. def setup
  27726. @controller = Class.new do
  27727. attr_accessor :request
  27728. def url_for(options, *parameters_for_method_reference)
  27729. "http://www.example.com"
  27730. end
  27731. end.new
  27732. @request = Class.new do
  27733. def relative_url_root
  27734. ""
  27735. end
  27736. end.new
  27737. @controller.request = @request
  27738. ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
  27739. end
  27740. def teardown
  27741. Object.send(:remove_const, :RAILS_ROOT) if defined?(RAILS_ROOT)
  27742. ENV["RAILS_ASSET_ID"] = nil
  27743. end
  27744. AutoDiscoveryToTag = {
  27745. %(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
  27746. %(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />),
  27747. %(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
  27748. %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
  27749. %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
  27750. %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
  27751. %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="" type="text/html" />),
  27752. %(auto_discovery_link_tag(nil, {}, {:title => "No stream.. really", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="No stream.. really" type="text/html" />),
  27753. %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="text/html" />),
  27754. %(auto_discovery_link_tag(:atom, {}, {:rel => "Not so alternate"})) => %(<link href="http://www.example.com" rel="Not so alternate" title="ATOM" type="application/atom+xml" />),
  27755. }
  27756. JavascriptPathToTag = {
  27757. %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js),
  27758. %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js)
  27759. }
  27760. JavascriptIncludeToTag = {
  27761. %(javascript_include_tag("xmlhr")) => %(<script src="/javascripts/xmlhr.js" type="text/javascript"></script>),
  27762. %(javascript_include_tag("xmlhr", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/xmlhr.js" type="text/javascript"></script>),
  27763. %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
  27764. %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>),
  27765. %(javascript_include_tag(:defaults, "test")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/test.js" type="text/javascript"></script>),
  27766. %(javascript_include_tag("test", :defaults)) => %(<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>)
  27767. }
  27768. StylePathToTag = {
  27769. %(stylesheet_path("style")) => %(/stylesheets/style.css),
  27770. %(stylesheet_path('dir/file')) => %(/stylesheets/dir/file.css),
  27771. %(stylesheet_path('/dir/file')) => %(/dir/file.css)
  27772. }
  27773. StyleLinkToTag = {
  27774. %(stylesheet_link_tag("style")) => %(<link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
  27775. %(stylesheet_link_tag("/dir/file")) => %(<link href="/dir/file.css" media="screen" rel="Stylesheet" type="text/css" />),
  27776. %(stylesheet_link_tag("dir/file")) => %(<link href="/stylesheets/dir/file.css" media="screen" rel="Stylesheet" type="text/css" />),
  27777. %(stylesheet_link_tag("style", :media => "all")) => %(<link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />),
  27778. %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />\n<link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />)
  27779. }
  27780. ImagePathToTag = {
  27781. %(image_path("xml")) => %(/images/xml.png),
  27782. }
  27783. ImageLinkToTag = {
  27784. %(image_tag("xml")) => %(<img alt="Xml" src="/images/xml.png" />),
  27785. %(image_tag("rss", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.png" />),
  27786. %(image_tag("gold", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
  27787. %(image_tag("symbolize", "size" => "45x70")) => %(<img alt="Symbolize" height="70" src="/images/symbolize.png" width="45" />),
  27788. %(image_tag("http://www.rubyonrails.com/images/rails")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />)
  27789. }
  27790. def test_auto_discovery
  27791. AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27792. end
  27793. def test_javascript_path
  27794. JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27795. end
  27796. def test_javascript_include
  27797. JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27798. end
  27799. def test_register_javascript_include_default
  27800. ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider'
  27801. assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
  27802. ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
  27803. assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>\n<script src="/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/elsewhere/blub/lib2.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
  27804. end
  27805. def test_style_path
  27806. StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27807. end
  27808. def test_style_link
  27809. StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27810. end
  27811. def test_image_path
  27812. ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27813. end
  27814. def test_image_tag
  27815. ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27816. end
  27817. def test_timebased_asset_id
  27818. Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
  27819. expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
  27820. assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
  27821. end
  27822. def test_skipping_asset_id_on_complete_url
  27823. Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
  27824. assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png")
  27825. end
  27826. def test_preset_asset_id
  27827. Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
  27828. ENV["RAILS_ASSET_ID"] = "4500"
  27829. assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png")
  27830. end
  27831. end
  27832. class AssetTagHelperNonVhostTest < Test::Unit::TestCase
  27833. include ActionView::Helpers::TagHelper
  27834. include ActionView::Helpers::UrlHelper
  27835. include ActionView::Helpers::AssetTagHelper
  27836. def setup
  27837. @controller = Class.new do
  27838. attr_accessor :request
  27839. def url_for(options, *parameters_for_method_reference)
  27840. "http://www.example.com/calloboration/hieraki"
  27841. end
  27842. end.new
  27843. @request = Class.new do
  27844. def relative_url_root
  27845. "/calloboration/hieraki"
  27846. end
  27847. end.new
  27848. @controller.request = @request
  27849. ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
  27850. end
  27851. AutoDiscoveryToTag = {
  27852. %(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />),
  27853. %(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="ATOM" type="application/atom+xml" />),
  27854. %(auto_discovery_link_tag) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />),
  27855. }
  27856. JavascriptPathToTag = {
  27857. %(javascript_path("xmlhr")) => %(/calloboration/hieraki/javascripts/xmlhr.js),
  27858. }
  27859. JavascriptIncludeToTag = {
  27860. %(javascript_include_tag("xmlhr")) => %(<script src="/calloboration/hieraki/javascripts/xmlhr.js" type="text/javascript"></script>),
  27861. %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/calloboration/hieraki/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/calloboration/hieraki/elsewhere/cools.js" type="text/javascript"></script>),
  27862. %(javascript_include_tag(:defaults)) => %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>)
  27863. }
  27864. StylePathToTag = {
  27865. %(stylesheet_path("style")) => %(/calloboration/hieraki/stylesheets/style.css),
  27866. }
  27867. StyleLinkToTag = {
  27868. %(stylesheet_link_tag("style")) => %(<link href="/calloboration/hieraki/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
  27869. %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/calloboration/hieraki/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />\n<link href="/calloboration/hieraki/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />)
  27870. }
  27871. ImagePathToTag = {
  27872. %(image_path("xml")) => %(/calloboration/hieraki/images/xml.png),
  27873. }
  27874. ImageLinkToTag = {
  27875. %(image_tag("xml")) => %(<img alt="Xml" src="/calloboration/hieraki/images/xml.png" />),
  27876. %(image_tag("rss", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/calloboration/hieraki/images/rss.png" />),
  27877. %(image_tag("gold", :size => "45x70")) => %(<img alt="Gold" height="70" src="/calloboration/hieraki/images/gold.png" width="45" />),
  27878. %(image_tag("http://www.example.com/images/icon.gif")) => %(<img alt="Icon" src="http://www.example.com/images/icon.gif" />),
  27879. %(image_tag("symbolize", "size" => "45x70")) => %(<img alt="Symbolize" height="70" src="/calloboration/hieraki/images/symbolize.png" width="45" />)
  27880. }
  27881. def test_auto_discovery
  27882. AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27883. end
  27884. def test_javascript_path
  27885. JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27886. end
  27887. def test_javascript_include
  27888. JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27889. end
  27890. def test_register_javascript_include_default
  27891. ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider'
  27892. assert_dom_equal %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/slider.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
  27893. ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
  27894. assert_dom_equal %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/slider.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/elsewhere/blub/lib2.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
  27895. end
  27896. def test_style_path
  27897. StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27898. end
  27899. def test_style_link
  27900. StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27901. end
  27902. def test_image_path
  27903. ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27904. end
  27905. def test_image_tag
  27906. ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  27907. # Assigning a default alt tag should not cause an exception to be raised
  27908. assert_nothing_raised { image_tag('') }
  27909. end
  27910. def test_stylesheet_with_asset_host_already_encoded
  27911. ActionController::Base.asset_host = "http://foo.example.com"
  27912. result = stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")
  27913. assert_dom_equal(
  27914. %(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
  27915. result)
  27916. ensure
  27917. ActionController::Base.asset_host = ""
  27918. end
  27919. end
  27920. require 'test/unit'
  27921. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/benchmark_helper'
  27922. class BenchmarkHelperTest < Test::Unit::TestCase
  27923. include ActionView::Helpers::BenchmarkHelper
  27924. class MockLogger
  27925. attr_reader :logged
  27926. def initialize
  27927. @logged = []
  27928. end
  27929. def method_missing(method, *args)
  27930. @logged << [method, args]
  27931. end
  27932. end
  27933. def setup
  27934. @logger = MockLogger.new
  27935. end
  27936. def test_without_logger_or_block
  27937. @logger = nil
  27938. assert_nothing_raised { benchmark }
  27939. end
  27940. def test_without_block
  27941. assert_raise(LocalJumpError) { benchmark }
  27942. assert @logger.logged.empty?
  27943. end
  27944. def test_without_logger
  27945. @logger = nil
  27946. i_was_run = false
  27947. benchmark { i_was_run = true }
  27948. assert !i_was_run
  27949. end
  27950. def test_defaults
  27951. i_was_run = false
  27952. benchmark { i_was_run = true }
  27953. assert i_was_run
  27954. assert 1, @logger.logged.size
  27955. assert_last_logged
  27956. end
  27957. def test_with_message
  27958. i_was_run = false
  27959. benchmark('test_run') { i_was_run = true }
  27960. assert i_was_run
  27961. assert 1, @logger.logged.size
  27962. assert_last_logged 'test_run'
  27963. end
  27964. def test_with_message_and_level
  27965. i_was_run = false
  27966. benchmark('debug_run', :debug) { i_was_run = true }
  27967. assert i_was_run
  27968. assert 1, @logger.logged.size
  27969. assert_last_logged 'debug_run', :debug
  27970. end
  27971. private
  27972. def assert_last_logged(message = 'Benchmarking', level = :info)
  27973. last = @logger.logged.last
  27974. assert 2, last.size
  27975. assert_equal level, last.first
  27976. assert 1, last[1].size
  27977. assert last[1][0] =~ /^#{message} \(.*\)$/
  27978. end
  27979. end
  27980. require 'test/unit'
  27981. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper'
  27982. require File.dirname(__FILE__) + "/../abstract_unit"
  27983. class CompiledTemplateTests < Test::Unit::TestCase
  27984. def setup
  27985. @ct = ActionView::CompiledTemplates.new
  27986. @v = Class.new
  27987. @v.send :include, @ct
  27988. @a = './test_compile_template_a.rhtml'
  27989. @b = './test_compile_template_b.rhtml'
  27990. @s = './test_compile_template_link.rhtml'
  27991. end
  27992. def teardown
  27993. [@a, @b, @s].each do |f|
  27994. `rm #{f}` if File.exist?(f) || File.symlink?(f)
  27995. end
  27996. end
  27997. attr_reader :ct, :v
  27998. def test_name_allocation
  27999. hi_world = ct.method_names['hi world']
  28000. hi_sexy = ct.method_names['hi sexy']
  28001. wish_upon_a_star = ct.method_names['I love seeing decent error messages']
  28002. assert_equal hi_world, ct.method_names['hi world']
  28003. assert_equal hi_sexy, ct.method_names['hi sexy']
  28004. assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages']
  28005. assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length
  28006. end
  28007. def test_wrap_source
  28008. assert_equal(
  28009. "def aliased_assignment(value)\nself.value = value\nend",
  28010. @ct.wrap_source(:aliased_assignment, [:value], 'self.value = value')
  28011. )
  28012. assert_equal(
  28013. "def simple()\nnil\nend",
  28014. @ct.wrap_source(:simple, [], 'nil')
  28015. )
  28016. end
  28017. def test_compile_source_single_method
  28018. selector = ct.compile_source('doubling method', [:a], 'a + a')
  28019. assert_equal 2, @v.new.send(selector, 1)
  28020. assert_equal 4, @v.new.send(selector, 2)
  28021. assert_equal -4, @v.new.send(selector, -2)
  28022. assert_equal 0, @v.new.send(selector, 0)
  28023. selector
  28024. end
  28025. def test_compile_source_two_method
  28026. sel1 = test_compile_source_single_method # compile the method in the other test
  28027. sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b')
  28028. assert_not_equal sel1, sel2
  28029. assert_equal 2, @v.new.send(sel1, 1)
  28030. assert_equal 4, @v.new.send(sel1, 2)
  28031. assert_equal 6, @v.new.send(sel2, 1, 2)
  28032. assert_equal 32, @v.new.send(sel2, 15, 1)
  28033. end
  28034. def test_mtime
  28035. t1 = Time.now
  28036. test_compile_source_single_method
  28037. assert (t1..Time.now).include?(ct.mtime('doubling method', [:a]))
  28038. end
  28039. def test_compile_time
  28040. `echo '#{@a}' > #{@a}; echo '#{@b}' > #{@b}; ln -s #{@a} #{@s}`
  28041. v = ActionView::Base.new
  28042. v.base_path = '.'
  28043. v.cache_template_loading = false;
  28044. sleep 1
  28045. t = Time.now
  28046. v.compile_and_render_template(:rhtml, '', @a)
  28047. v.compile_and_render_template(:rhtml, '', @b)
  28048. v.compile_and_render_template(:rhtml, '', @s)
  28049. a_n = v.method_names[@a]
  28050. b_n = v.method_names[@b]
  28051. s_n = v.method_names[@s]
  28052. # all of the files have changed since last compile
  28053. assert v.compile_time[a_n] > t
  28054. assert v.compile_time[b_n] > t
  28055. assert v.compile_time[s_n] > t
  28056. sleep 1
  28057. t = Time.now
  28058. v.compile_and_render_template(:rhtml, '', @a)
  28059. v.compile_and_render_template(:rhtml, '', @b)
  28060. v.compile_and_render_template(:rhtml, '', @s)
  28061. # none of the files have changed since last compile
  28062. assert v.compile_time[a_n] < t
  28063. assert v.compile_time[b_n] < t
  28064. assert v.compile_time[s_n] < t
  28065. `rm #{@s}; ln -s #{@b} #{@s}`
  28066. v.compile_and_render_template(:rhtml, '', @a)
  28067. v.compile_and_render_template(:rhtml, '', @b)
  28068. v.compile_and_render_template(:rhtml, '', @s)
  28069. # the symlink has changed since last compile
  28070. assert v.compile_time[a_n] < t
  28071. assert v.compile_time[b_n] < t
  28072. assert v.compile_time[s_n] > t
  28073. sleep 1
  28074. `touch #{@b}`
  28075. t = Time.now
  28076. v.compile_and_render_template(:rhtml, '', @a)
  28077. v.compile_and_render_template(:rhtml, '', @b)
  28078. v.compile_and_render_template(:rhtml, '', @s)
  28079. # the file at the end of the symlink has changed since last compile
  28080. # both the symlink and the file at the end of it should be recompiled
  28081. assert v.compile_time[a_n] < t
  28082. assert v.compile_time[b_n] > t
  28083. assert v.compile_time[s_n] > t
  28084. end
  28085. end
  28086. module ActionView
  28087. class Base
  28088. def compile_time
  28089. @@compile_time
  28090. end
  28091. def method_names
  28092. @@method_names
  28093. end
  28094. end
  28095. end
  28096. require 'test/unit'
  28097. require File.dirname(__FILE__) + "/../abstract_unit"
  28098. class DateHelperTest < Test::Unit::TestCase
  28099. include ActionView::Helpers::DateHelper
  28100. include ActionView::Helpers::FormHelper
  28101. silence_warnings do
  28102. Post = Struct.new("Post", :written_on, :updated_at)
  28103. end
  28104. def test_distance_in_words
  28105. from = Time.mktime(2004, 3, 6, 21, 41, 18)
  28106. assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 25))
  28107. assert_equal "5 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 46, 25))
  28108. assert_equal "about 1 hour", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 22, 47, 25))
  28109. assert_equal "about 3 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 0, 41))
  28110. assert_equal "about 4 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 1, 20))
  28111. assert_equal "2 days", distance_of_time_in_words(from, Time.mktime(2004, 3, 9, 15, 40))
  28112. # include seconds
  28113. assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 19), false)
  28114. assert_equal "less than 5 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 19), true)
  28115. assert_equal "less than 10 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 28), true)
  28116. assert_equal "less than 20 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 38), true)
  28117. assert_equal "half a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 48), true)
  28118. assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 17), true)
  28119. assert_equal "1 minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 18), true)
  28120. assert_equal "1 minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 28), true)
  28121. assert_equal "2 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 48), true)
  28122. # test to < from
  28123. assert_equal "about 4 hours", distance_of_time_in_words(Time.mktime(2004, 3, 7, 1, 20), from)
  28124. assert_equal "less than 20 seconds", distance_of_time_in_words(Time.mktime(2004, 3, 6, 21, 41, 38), from, true)
  28125. # test with integers
  28126. assert_equal "less than a minute", distance_of_time_in_words(50)
  28127. assert_equal "about 1 hour", distance_of_time_in_words(60*60)
  28128. # more cumbersome test with integers
  28129. assert_equal "less than a minute", distance_of_time_in_words(0, 50)
  28130. assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
  28131. end
  28132. def test_distance_in_words_date
  28133. start_date = Date.new 1975, 1, 31
  28134. end_date = Date.new 1977, 4, 17
  28135. assert_not_equal("13 minutes",
  28136. distance_of_time_in_words(start_date, end_date))
  28137. end
  28138. def test_select_day
  28139. expected = %(<select name="date[day]">\n)
  28140. expected <<
  28141. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28142. expected << "</select>\n"
  28143. assert_equal expected, select_day(Time.mktime(2003, 8, 16))
  28144. assert_equal expected, select_day(16)
  28145. end
  28146. def test_select_day_with_blank
  28147. expected = %(<select name="date[day]">\n)
  28148. expected <<
  28149. %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28150. expected << "</select>\n"
  28151. assert_equal expected, select_day(Time.mktime(2003, 8, 16), :include_blank => true)
  28152. assert_equal expected, select_day(16, :include_blank => true)
  28153. end
  28154. def test_select_day_nil_with_blank
  28155. expected = %(<select name="date[day]">\n)
  28156. expected <<
  28157. %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28158. expected << "</select>\n"
  28159. assert_equal expected, select_day(nil, :include_blank => true)
  28160. end
  28161. def test_select_month
  28162. expected = %(<select name="date[month]">\n)
  28163. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28164. expected << "</select>\n"
  28165. assert_equal expected, select_month(Time.mktime(2003, 8, 16))
  28166. assert_equal expected, select_month(8)
  28167. end
  28168. def test_select_month_with_disabled
  28169. expected = %(<select name="date[month]" disabled="disabled">\n)
  28170. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28171. expected << "</select>\n"
  28172. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :disabled => true)
  28173. assert_equal expected, select_month(8, :disabled => true)
  28174. end
  28175. def test_select_month_with_field_name_override
  28176. expected = %(<select name="date[mois]">\n)
  28177. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28178. expected << "</select>\n"
  28179. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :field_name => 'mois')
  28180. assert_equal expected, select_month(8, :field_name => 'mois')
  28181. end
  28182. def test_select_month_with_blank
  28183. expected = %(<select name="date[month]">\n)
  28184. expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28185. expected << "</select>\n"
  28186. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :include_blank => true)
  28187. assert_equal expected, select_month(8, :include_blank => true)
  28188. end
  28189. def test_select_month_nil_with_blank
  28190. expected = %(<select name="date[month]">\n)
  28191. expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28192. expected << "</select>\n"
  28193. assert_equal expected, select_month(nil, :include_blank => true)
  28194. end
  28195. def test_select_month_with_numbers
  28196. expected = %(<select name="date[month]">\n)
  28197. expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8" selected="selected">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n)
  28198. expected << "</select>\n"
  28199. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_numbers => true)
  28200. assert_equal expected, select_month(8, :use_month_numbers => true)
  28201. end
  28202. def test_select_month_with_numbers_and_names
  28203. expected = %(<select name="date[month]">\n)
  28204. expected << %(<option value="1">1 - January</option>\n<option value="2">2 - February</option>\n<option value="3">3 - March</option>\n<option value="4">4 - April</option>\n<option value="5">5 - May</option>\n<option value="6">6 - June</option>\n<option value="7">7 - July</option>\n<option value="8" selected="selected">8 - August</option>\n<option value="9">9 - September</option>\n<option value="10">10 - October</option>\n<option value="11">11 - November</option>\n<option value="12">12 - December</option>\n)
  28205. expected << "</select>\n"
  28206. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true)
  28207. assert_equal expected, select_month(8, :add_month_numbers => true)
  28208. end
  28209. def test_select_month_with_numbers_and_names_with_abbv
  28210. expected = %(<select name="date[month]">\n)
  28211. expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n)
  28212. expected << "</select>\n"
  28213. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true, :use_short_month => true)
  28214. assert_equal expected, select_month(8, :add_month_numbers => true, :use_short_month => true)
  28215. end
  28216. def test_select_month_with_abbv
  28217. expected = %(<select name="date[month]">\n)
  28218. expected << %(<option value="1">Jan</option>\n<option value="2">Feb</option>\n<option value="3">Mar</option>\n<option value="4">Apr</option>\n<option value="5">May</option>\n<option value="6">Jun</option>\n<option value="7">Jul</option>\n<option value="8" selected="selected">Aug</option>\n<option value="9">Sep</option>\n<option value="10">Oct</option>\n<option value="11">Nov</option>\n<option value="12">Dec</option>\n)
  28219. expected << "</select>\n"
  28220. assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_short_month => true)
  28221. assert_equal expected, select_month(8, :use_short_month => true)
  28222. end
  28223. def test_select_year
  28224. expected = %(<select name="date[year]">\n)
  28225. expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28226. expected << "</select>\n"
  28227. assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005)
  28228. assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005)
  28229. end
  28230. def test_select_year_with_disabled
  28231. expected = %(<select name="date[year]" disabled="disabled">\n)
  28232. expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28233. expected << "</select>\n"
  28234. assert_equal expected, select_year(Time.mktime(2003, 8, 16), :disabled => true, :start_year => 2003, :end_year => 2005)
  28235. assert_equal expected, select_year(2003, :disabled => true, :start_year => 2003, :end_year => 2005)
  28236. end
  28237. def test_select_year_with_field_name_override
  28238. expected = %(<select name="date[annee]">\n)
  28239. expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28240. expected << "</select>\n"
  28241. assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :field_name => 'annee')
  28242. assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005, :field_name => 'annee')
  28243. end
  28244. def test_select_year_with_type_discarding
  28245. expected = %(<select name="date_year">\n)
  28246. expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28247. expected << "</select>\n"
  28248. assert_equal expected, select_year(
  28249. Time.mktime(2003, 8, 16), :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
  28250. assert_equal expected, select_year(
  28251. 2003, :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
  28252. end
  28253. def test_select_year_descending
  28254. expected = %(<select name="date[year]">\n)
  28255. expected << %(<option value="2005" selected="selected">2005</option>\n<option value="2004">2004</option>\n<option value="2003">2003</option>\n)
  28256. expected << "</select>\n"
  28257. assert_equal expected, select_year(Time.mktime(2005, 8, 16), :start_year => 2005, :end_year => 2003)
  28258. assert_equal expected, select_year(2005, :start_year => 2005, :end_year => 2003)
  28259. end
  28260. def test_select_hour
  28261. expected = %(<select name="date[hour]">\n)
  28262. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28263. expected << "</select>\n"
  28264. assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18))
  28265. end
  28266. def test_select_hour_with_disabled
  28267. expected = %(<select name="date[hour]" disabled="disabled">\n)
  28268. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28269. expected << "</select>\n"
  28270. assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
  28271. end
  28272. def test_select_hour_with_field_name_override
  28273. expected = %(<select name="date[heure]">\n)
  28274. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28275. expected << "</select>\n"
  28276. assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'heure')
  28277. end
  28278. def test_select_hour_with_blank
  28279. expected = %(<select name="date[hour]">\n)
  28280. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28281. expected << "</select>\n"
  28282. assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
  28283. end
  28284. def test_select_hour_nil_with_blank
  28285. expected = %(<select name="date[hour]">\n)
  28286. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28287. expected << "</select>\n"
  28288. assert_equal expected, select_hour(nil, :include_blank => true)
  28289. end
  28290. def test_select_minute
  28291. expected = %(<select name="date[minute]">\n)
  28292. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28293. expected << "</select>\n"
  28294. assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18))
  28295. end
  28296. def test_select_minute_with_disabled
  28297. expected = %(<select name="date[minute]" disabled="disabled">\n)
  28298. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28299. expected << "</select>\n"
  28300. assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
  28301. end
  28302. def test_select_minute_with_field_name_override
  28303. expected = %(<select name="date[minuto]">\n)
  28304. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28305. expected << "</select>\n"
  28306. assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'minuto')
  28307. end
  28308. def test_select_minute_with_blank
  28309. expected = %(<select name="date[minute]">\n)
  28310. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28311. expected << "</select>\n"
  28312. assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
  28313. end
  28314. def test_select_minute_with_blank_and_step
  28315. expected = %(<select name="date[minute]">\n)
  28316. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
  28317. expected << "</select>\n"
  28318. assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), { :include_blank => true , :minute_step => 15 })
  28319. end
  28320. def test_select_minute_nil_with_blank
  28321. expected = %(<select name="date[minute]">\n)
  28322. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28323. expected << "</select>\n"
  28324. assert_equal expected, select_minute(nil, :include_blank => true)
  28325. end
  28326. def test_select_minute_nil_with_blank_and_step
  28327. expected = %(<select name="date[minute]">\n)
  28328. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
  28329. expected << "</select>\n"
  28330. assert_equal expected, select_minute(nil, { :include_blank => true , :minute_step => 15 })
  28331. end
  28332. def test_select_second
  28333. expected = %(<select name="date[second]">\n)
  28334. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28335. expected << "</select>\n"
  28336. assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18))
  28337. end
  28338. def test_select_second_with_disabled
  28339. expected = %(<select name="date[second]" disabled="disabled">\n)
  28340. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28341. expected << "</select>\n"
  28342. assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
  28343. end
  28344. def test_select_second_with_field_name_override
  28345. expected = %(<select name="date[segundo]">\n)
  28346. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28347. expected << "</select>\n"
  28348. assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'segundo')
  28349. end
  28350. def test_select_second_with_blank
  28351. expected = %(<select name="date[second]">\n)
  28352. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28353. expected << "</select>\n"
  28354. assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
  28355. end
  28356. def test_select_second_nil_with_blank
  28357. expected = %(<select name="date[second]">\n)
  28358. expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28359. expected << "</select>\n"
  28360. assert_equal expected, select_second(nil, :include_blank => true)
  28361. end
  28362. def test_select_date
  28363. expected = %(<select name="date[first][year]">\n)
  28364. expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28365. expected << "</select>\n"
  28366. expected << %(<select name="date[first][month]">\n)
  28367. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28368. expected << "</select>\n"
  28369. expected << %(<select name="date[first][day]">\n)
  28370. expected <<
  28371. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28372. expected << "</select>\n"
  28373. assert_equal expected, select_date(
  28374. Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]"
  28375. )
  28376. end
  28377. def test_select_date_with_disabled
  28378. expected = %(<select name="date[first][year]" disabled="disabled">\n)
  28379. expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28380. expected << "</select>\n"
  28381. expected << %(<select name="date[first][month]" disabled="disabled">\n)
  28382. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28383. expected << "</select>\n"
  28384. expected << %(<select name="date[first][day]" disabled="disabled">\n)
  28385. expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28386. expected << "</select>\n"
  28387. assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :disabled => true)
  28388. end
  28389. def test_select_date_with_no_start_year
  28390. expected = %(<select name="date[first][year]">\n)
  28391. (Date.today.year-5).upto(Date.today.year+1) do |y|
  28392. if y == Date.today.year
  28393. expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
  28394. else
  28395. expected << %(<option value="#{y}">#{y}</option>\n)
  28396. end
  28397. end
  28398. expected << "</select>\n"
  28399. expected << %(<select name="date[first][month]">\n)
  28400. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28401. expected << "</select>\n"
  28402. expected << %(<select name="date[first][day]">\n)
  28403. expected <<
  28404. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28405. expected << "</select>\n"
  28406. assert_equal expected, select_date(
  28407. Time.mktime(Date.today.year, 8, 16), :end_year => Date.today.year+1, :prefix => "date[first]"
  28408. )
  28409. end
  28410. def test_select_date_with_no_end_year
  28411. expected = %(<select name="date[first][year]">\n)
  28412. 2003.upto(2008) do |y|
  28413. if y == 2003
  28414. expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
  28415. else
  28416. expected << %(<option value="#{y}">#{y}</option>\n)
  28417. end
  28418. end
  28419. expected << "</select>\n"
  28420. expected << %(<select name="date[first][month]">\n)
  28421. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28422. expected << "</select>\n"
  28423. expected << %(<select name="date[first][day]">\n)
  28424. expected <<
  28425. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28426. expected << "</select>\n"
  28427. assert_equal expected, select_date(
  28428. Time.mktime(2003, 8, 16), :start_year => 2003, :prefix => "date[first]"
  28429. )
  28430. end
  28431. def test_select_date_with_no_start_or_end_year
  28432. expected = %(<select name="date[first][year]">\n)
  28433. (Date.today.year-5).upto(Date.today.year+5) do |y|
  28434. if y == Date.today.year
  28435. expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
  28436. else
  28437. expected << %(<option value="#{y}">#{y}</option>\n)
  28438. end
  28439. end
  28440. expected << "</select>\n"
  28441. expected << %(<select name="date[first][month]">\n)
  28442. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28443. expected << "</select>\n"
  28444. expected << %(<select name="date[first][day]">\n)
  28445. expected <<
  28446. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28447. expected << "</select>\n"
  28448. assert_equal expected, select_date(
  28449. Time.mktime(Date.today.year, 8, 16), :prefix => "date[first]"
  28450. )
  28451. end
  28452. def test_select_time_with_seconds
  28453. expected = %(<select name="date[hour]">\n)
  28454. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28455. expected << "</select>\n"
  28456. expected << %(<select name="date[minute]">\n)
  28457. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28458. expected << "</select>\n"
  28459. expected << %(<select name="date[second]">\n)
  28460. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28461. expected << "</select>\n"
  28462. assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true)
  28463. end
  28464. def test_select_time_without_seconds
  28465. expected = %(<select name="date[hour]">\n)
  28466. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28467. expected << "</select>\n"
  28468. expected << %(<select name="date[minute]">\n)
  28469. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28470. expected << "</select>\n"
  28471. assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18))
  28472. assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false)
  28473. end
  28474. def test_date_select_with_zero_value
  28475. expected = %(<select name="date[first][year]">\n)
  28476. expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
  28477. expected << "</select>\n"
  28478. expected << %(<select name="date[first][month]">\n)
  28479. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28480. expected << "</select>\n"
  28481. expected << %(<select name="date[first][day]">\n)
  28482. expected <<
  28483. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28484. expected << "</select>\n"
  28485. assert_equal expected, select_date(0, :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
  28486. end
  28487. def test_date_select_within_fields_for
  28488. @post = Post.new
  28489. @post.written_on = Date.new(2004, 6, 15)
  28490. _erbout = ''
  28491. fields_for :post, @post do |f|
  28492. _erbout.concat f.date_select(:written_on)
  28493. end
  28494. expected = "<select name='post[written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n" +
  28495. "<select name='post[written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n" +
  28496. "<select name='post[written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
  28497. assert_dom_equal(expected, _erbout)
  28498. end
  28499. def test_datetime_select_within_fields_for
  28500. @post = Post.new
  28501. @post.updated_at = Time.local(2004, 6, 15, 16, 35)
  28502. _erbout = ''
  28503. fields_for :post, @post do |f|
  28504. _erbout.concat f.datetime_select(:updated_at)
  28505. end
  28506. expected = "<select name='post[updated_at(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n<select name='post[updated_at(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n<select name='post[updated_at(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n &mdash; <select name='post[updated_at(4i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n : <select name='post[updated_at(5i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"
  28507. assert_dom_equal(expected, _erbout)
  28508. end
  28509. def test_date_select_with_zero_value_and_no_start_year
  28510. expected = %(<select name="date[first][year]">\n)
  28511. (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
  28512. expected << "</select>\n"
  28513. expected << %(<select name="date[first][month]">\n)
  28514. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28515. expected << "</select>\n"
  28516. expected << %(<select name="date[first][day]">\n)
  28517. expected <<
  28518. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28519. expected << "</select>\n"
  28520. assert_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]")
  28521. end
  28522. def test_date_select_with_zero_value_and_no_end_year
  28523. expected = %(<select name="date[first][year]">\n)
  28524. last_year = Time.now.year + 5
  28525. 2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
  28526. expected << "</select>\n"
  28527. expected << %(<select name="date[first][month]">\n)
  28528. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28529. expected << "</select>\n"
  28530. expected << %(<select name="date[first][day]">\n)
  28531. expected <<
  28532. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28533. expected << "</select>\n"
  28534. assert_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]")
  28535. end
  28536. def test_date_select_with_zero_value_and_no_start_and_end_year
  28537. expected = %(<select name="date[first][year]">\n)
  28538. (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
  28539. expected << "</select>\n"
  28540. expected << %(<select name="date[first][month]">\n)
  28541. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28542. expected << "</select>\n"
  28543. expected << %(<select name="date[first][day]">\n)
  28544. expected <<
  28545. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28546. expected << "</select>\n"
  28547. assert_equal expected, select_date(0, :prefix => "date[first]")
  28548. end
  28549. def test_date_select_with_nil_value_and_no_start_and_end_year
  28550. expected = %(<select name="date[first][year]">\n)
  28551. (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
  28552. expected << "</select>\n"
  28553. expected << %(<select name="date[first][month]">\n)
  28554. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28555. expected << "</select>\n"
  28556. expected << %(<select name="date[first][day]">\n)
  28557. expected <<
  28558. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28559. expected << "</select>\n"
  28560. assert_equal expected, select_date(nil, :prefix => "date[first]")
  28561. end
  28562. def test_datetime_select_with_nil_value_and_no_start_and_end_year
  28563. expected = %(<select name="date[first][year]">\n)
  28564. (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
  28565. expected << "</select>\n"
  28566. expected << %(<select name="date[first][month]">\n)
  28567. expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
  28568. expected << "</select>\n"
  28569. expected << %(<select name="date[first][day]">\n)
  28570. expected <<
  28571. %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
  28572. expected << "</select>\n"
  28573. expected << %(<select name="date[first][hour]">\n)
  28574. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
  28575. expected << "</select>\n"
  28576. expected << %(<select name="date[first][minute]">\n)
  28577. expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
  28578. expected << "</select>\n"
  28579. assert_equal expected, select_datetime(nil, :prefix => "date[first]")
  28580. end
  28581. end
  28582. require File.dirname(__FILE__) + '/../abstract_unit'
  28583. class FormHelperTest < Test::Unit::TestCase
  28584. include ActionView::Helpers::FormHelper
  28585. include ActionView::Helpers::FormTagHelper
  28586. include ActionView::Helpers::UrlHelper
  28587. include ActionView::Helpers::TagHelper
  28588. include ActionView::Helpers::TextHelper
  28589. silence_warnings do
  28590. Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on, :cost)
  28591. Post.class_eval do
  28592. alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
  28593. alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
  28594. alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
  28595. end
  28596. end
  28597. def setup
  28598. @post = Post.new
  28599. def @post.errors() Class.new{ def on(field) field == "author_name" end }.new end
  28600. def @post.id; 123; end
  28601. def @post.id_before_type_cast; 123; end
  28602. @post.title = "Hello World"
  28603. @post.author_name = ""
  28604. @post.body = "Back to the hill and over it again!"
  28605. @post.secret = 1
  28606. @post.written_on = Date.new(2004, 6, 15)
  28607. @controller = Class.new do
  28608. attr_reader :url_for_options
  28609. def url_for(options, *parameters_for_method_reference)
  28610. @url_for_options = options
  28611. "http://www.example.com"
  28612. end
  28613. end
  28614. @controller = @controller.new
  28615. end
  28616. def test_text_field
  28617. assert_dom_equal(
  28618. '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
  28619. )
  28620. assert_dom_equal(
  28621. '<input id="post_title" name="post[title]" size="30" type="password" value="Hello World" />', password_field("post", "title")
  28622. )
  28623. assert_dom_equal(
  28624. '<input id="person_name" name="person[name]" size="30" type="password" />', password_field("person", "name")
  28625. )
  28626. end
  28627. def test_text_field_with_escapes
  28628. @post.title = "<b>Hello World</b>"
  28629. assert_dom_equal(
  28630. '<input id="post_title" name="post[title]" size="30" type="text" value="&lt;b&gt;Hello World&lt;/b&gt;" />', text_field("post", "title")
  28631. )
  28632. end
  28633. def test_text_field_with_options
  28634. expected = '<input id="post_title" name="post[title]" size="35" type="text" value="Hello World" />'
  28635. assert_dom_equal expected, text_field("post", "title", "size" => 35)
  28636. assert_dom_equal expected, text_field("post", "title", :size => 35)
  28637. end
  28638. def test_text_field_assuming_size
  28639. expected = '<input id="post_title" maxlength="35" name="post[title]" size="35" type="text" value="Hello World" />'
  28640. assert_dom_equal expected, text_field("post", "title", "maxlength" => 35)
  28641. assert_dom_equal expected, text_field("post", "title", :maxlength => 35)
  28642. end
  28643. def test_text_field_doesnt_change_param_values
  28644. object_name = 'post[]'
  28645. expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />'
  28646. assert_equal expected, text_field(object_name, "title")
  28647. assert_equal object_name, "post[]"
  28648. end
  28649. def test_check_box
  28650. assert_dom_equal(
  28651. '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
  28652. check_box("post", "secret")
  28653. )
  28654. @post.secret = 0
  28655. assert_dom_equal(
  28656. '<input id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
  28657. check_box("post", "secret")
  28658. )
  28659. assert_dom_equal(
  28660. '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
  28661. check_box("post", "secret" ,{"checked"=>"checked"})
  28662. )
  28663. @post.secret = true
  28664. assert_dom_equal(
  28665. '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
  28666. check_box("post", "secret")
  28667. )
  28668. end
  28669. def test_check_box_with_explicit_checked_and_unchecked_values
  28670. @post.secret = "on"
  28671. assert_dom_equal(
  28672. '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" /><input name="post[secret]" type="hidden" value="off" />',
  28673. check_box("post", "secret", {}, "on", "off")
  28674. )
  28675. end
  28676. def test_radio_button
  28677. assert_dom_equal('<input checked="checked" id="post_title_hello_world" name="post[title]" type="radio" value="Hello World" />',
  28678. radio_button("post", "title", "Hello World")
  28679. )
  28680. assert_dom_equal('<input id="post_title_goodbye_world" name="post[title]" type="radio" value="Goodbye World" />',
  28681. radio_button("post", "title", "Goodbye World")
  28682. )
  28683. end
  28684. def test_radio_button_is_checked_with_integers
  28685. assert_dom_equal('<input checked="checked" id="post_secret_1" name="post[secret]" type="radio" value="1" />',
  28686. radio_button("post", "secret", "1")
  28687. )
  28688. end
  28689. def test_text_area
  28690. assert_dom_equal(
  28691. '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
  28692. text_area("post", "body")
  28693. )
  28694. end
  28695. def test_text_area_with_escapes
  28696. @post.body = "Back to <i>the</i> hill and over it again!"
  28697. assert_dom_equal(
  28698. '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>',
  28699. text_area("post", "body")
  28700. )
  28701. end
  28702. def test_text_area_with_alternate_value
  28703. assert_dom_equal(
  28704. '<textarea cols="40" id="post_body" name="post[body]" rows="20">Testing alternate values.</textarea>',
  28705. text_area("post", "body", :value => 'Testing alternate values.')
  28706. )
  28707. end
  28708. def test_date_selects
  28709. assert_dom_equal(
  28710. '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
  28711. text_area("post", "body")
  28712. )
  28713. end
  28714. def test_explicit_name
  28715. assert_dom_equal(
  28716. '<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
  28717. )
  28718. assert_dom_equal(
  28719. '<textarea cols="40" id="post_body" name="really!" rows="20">Back to the hill and over it again!</textarea>',
  28720. text_area("post", "body", "name" => "really!")
  28721. )
  28722. assert_dom_equal(
  28723. '<input checked="checked" id="post_secret" name="i mean it" type="checkbox" value="1" /><input name="i mean it" type="hidden" value="0" />',
  28724. check_box("post", "secret", "name" => "i mean it")
  28725. )
  28726. assert_dom_equal text_field("post", "title", "name" => "dont guess"),
  28727. text_field("post", "title", :name => "dont guess")
  28728. assert_dom_equal text_area("post", "body", "name" => "really!"),
  28729. text_area("post", "body", :name => "really!")
  28730. assert_dom_equal check_box("post", "secret", "name" => "i mean it"),
  28731. check_box("post", "secret", :name => "i mean it")
  28732. end
  28733. def test_explicit_id
  28734. assert_dom_equal(
  28735. '<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
  28736. )
  28737. assert_dom_equal(
  28738. '<textarea cols="40" id="really!" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
  28739. text_area("post", "body", "id" => "really!")
  28740. )
  28741. assert_dom_equal(
  28742. '<input checked="checked" id="i mean it" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
  28743. check_box("post", "secret", "id" => "i mean it")
  28744. )
  28745. assert_dom_equal text_field("post", "title", "id" => "dont guess"),
  28746. text_field("post", "title", :id => "dont guess")
  28747. assert_dom_equal text_area("post", "body", "id" => "really!"),
  28748. text_area("post", "body", :id => "really!")
  28749. assert_dom_equal check_box("post", "secret", "id" => "i mean it"),
  28750. check_box("post", "secret", :id => "i mean it")
  28751. end
  28752. def test_auto_index
  28753. pid = @post.id
  28754. assert_dom_equal(
  28755. "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title")
  28756. )
  28757. assert_dom_equal(
  28758. "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
  28759. text_area("post[]", "body")
  28760. )
  28761. assert_dom_equal(
  28762. "<input checked=\"checked\" id=\"post_#{pid}_secret\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" /><input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" />",
  28763. check_box("post[]", "secret")
  28764. )
  28765. assert_dom_equal(
  28766. "<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
  28767. radio_button("post[]", "title", "Hello World")
  28768. )
  28769. assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />",
  28770. radio_button("post[]", "title", "Goodbye World")
  28771. )
  28772. end
  28773. def test_form_for
  28774. _erbout = ''
  28775. form_for(:post, @post, :html => { :id => 'create-post' }) do |f|
  28776. _erbout.concat f.text_field(:title)
  28777. _erbout.concat f.text_area(:body)
  28778. _erbout.concat f.check_box(:secret)
  28779. end
  28780. expected =
  28781. "<form action='http://www.example.com' id='create-post' method='post'>" +
  28782. "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
  28783. "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
  28784. "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28785. "<input name='post[secret]' type='hidden' value='0' />" +
  28786. "</form>"
  28787. assert_dom_equal expected, _erbout
  28788. end
  28789. def test_form_for_without_object
  28790. _erbout = ''
  28791. form_for(:post, :html => { :id => 'create-post' }) do |f|
  28792. _erbout.concat f.text_field(:title)
  28793. _erbout.concat f.text_area(:body)
  28794. _erbout.concat f.check_box(:secret)
  28795. end
  28796. expected =
  28797. "<form action='http://www.example.com' id='create-post' method='post'>" +
  28798. "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
  28799. "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
  28800. "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28801. "<input name='post[secret]' type='hidden' value='0' />" +
  28802. "</form>"
  28803. assert_dom_equal expected, _erbout
  28804. end
  28805. def test_fields_for
  28806. _erbout = ''
  28807. fields_for(:post, @post) do |f|
  28808. _erbout.concat f.text_field(:title)
  28809. _erbout.concat f.text_area(:body)
  28810. _erbout.concat f.check_box(:secret)
  28811. end
  28812. expected =
  28813. "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
  28814. "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
  28815. "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28816. "<input name='post[secret]' type='hidden' value='0' />"
  28817. assert_dom_equal expected, _erbout
  28818. end
  28819. def test_fields_for_without_object
  28820. _erbout = ''
  28821. fields_for(:post) do |f|
  28822. _erbout.concat f.text_field(:title)
  28823. _erbout.concat f.text_area(:body)
  28824. _erbout.concat f.check_box(:secret)
  28825. end
  28826. expected =
  28827. "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
  28828. "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
  28829. "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28830. "<input name='post[secret]' type='hidden' value='0' />"
  28831. assert_dom_equal expected, _erbout
  28832. end
  28833. def test_form_builder_does_not_have_form_for_method
  28834. assert ! ActionView::Helpers::FormBuilder.instance_methods.include?('form_for')
  28835. end
  28836. def test_form_for_and_fields_for
  28837. _erbout = ''
  28838. form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form|
  28839. _erbout.concat post_form.text_field(:title)
  28840. _erbout.concat post_form.text_area(:body)
  28841. fields_for(:parent_post, @post) do |parent_fields|
  28842. _erbout.concat parent_fields.check_box(:secret)
  28843. end
  28844. end
  28845. expected =
  28846. "<form action='http://www.example.com' id='create-post' method='post'>" +
  28847. "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
  28848. "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
  28849. "<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />" +
  28850. "<input name='parent_post[secret]' type='hidden' value='0' />" +
  28851. "</form>"
  28852. assert_dom_equal expected, _erbout
  28853. end
  28854. class LabelledFormBuilder < ActionView::Helpers::FormBuilder
  28855. (field_helpers - %w(hidden_field)).each do |selector|
  28856. src = <<-END_SRC
  28857. def #{selector}(field, *args, &proc)
  28858. "<label for='\#{field}'>\#{field.to_s.humanize}:</label> " + super + "<br/>"
  28859. end
  28860. END_SRC
  28861. class_eval src, __FILE__, __LINE__
  28862. end
  28863. end
  28864. def test_form_for_with_labelled_builder
  28865. _erbout = ''
  28866. form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
  28867. _erbout.concat f.text_field(:title)
  28868. _erbout.concat f.text_area(:body)
  28869. _erbout.concat f.check_box(:secret)
  28870. end
  28871. expected =
  28872. "<form action='http://www.example.com' method='post'>" +
  28873. "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
  28874. "<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/>" +
  28875. "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28876. "<input name='post[secret]' type='hidden' value='0' /><br/>" +
  28877. "</form>"
  28878. assert_dom_equal expected, _erbout
  28879. end
  28880. # Perhaps this test should be moved to prototype helper tests.
  28881. def test_remote_form_for_with_labelled_builder
  28882. self.extend ActionView::Helpers::PrototypeHelper
  28883. _erbout = ''
  28884. remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
  28885. _erbout.concat f.text_field(:title)
  28886. _erbout.concat f.text_area(:body)
  28887. _erbout.concat f.check_box(:secret)
  28888. end
  28889. expected =
  28890. %(<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">) +
  28891. "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
  28892. "<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/>" +
  28893. "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28894. "<input name='post[secret]' type='hidden' value='0' /><br/>" +
  28895. "</form>"
  28896. assert_dom_equal expected, _erbout
  28897. end
  28898. def test_fields_for_with_labelled_builder
  28899. _erbout = ''
  28900. fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
  28901. _erbout.concat f.text_field(:title)
  28902. _erbout.concat f.text_area(:body)
  28903. _erbout.concat f.check_box(:secret)
  28904. end
  28905. expected =
  28906. "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
  28907. "<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/>" +
  28908. "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
  28909. "<input name='post[secret]' type='hidden' value='0' /><br/>"
  28910. assert_dom_equal expected, _erbout
  28911. end
  28912. def test_form_for_with_html_options_adds_options_to_form_tag
  28913. _erbout = ''
  28914. form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
  28915. expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>"
  28916. assert_dom_equal expected, _erbout
  28917. end
  28918. def test_form_for_with_string_url_option
  28919. _erbout = ''
  28920. form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end
  28921. assert_equal 'http://www.otherdomain.com', @controller.url_for_options
  28922. end
  28923. def test_form_for_with_hash_url_option
  28924. _erbout = ''
  28925. form_for(:post, @post, :url => {:controller => 'controller', :action => 'action'}) do |f| end
  28926. assert_equal 'controller', @controller.url_for_options[:controller]
  28927. assert_equal 'action', @controller.url_for_options[:action]
  28928. end
  28929. def test_remote_form_for_with_html_options_adds_options_to_form_tag
  28930. self.extend ActionView::Helpers::PrototypeHelper
  28931. _erbout = ''
  28932. remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
  28933. 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>"
  28934. assert_dom_equal expected, _erbout
  28935. end
  28936. end
  28937. require File.dirname(__FILE__) + '/../abstract_unit'
  28938. class MockTimeZone
  28939. attr_reader :name
  28940. def initialize( name )
  28941. @name = name
  28942. end
  28943. def self.all
  28944. [ "A", "B", "C", "D", "E" ].map { |s| new s }
  28945. end
  28946. def ==( z )
  28947. z && @name == z.name
  28948. end
  28949. def to_s
  28950. @name
  28951. end
  28952. end
  28953. ActionView::Helpers::FormOptionsHelper::TimeZone = MockTimeZone
  28954. class FormOptionsHelperTest < Test::Unit::TestCase
  28955. include ActionView::Helpers::FormHelper
  28956. include ActionView::Helpers::FormOptionsHelper
  28957. silence_warnings do
  28958. Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
  28959. Continent = Struct.new('Continent', :continent_name, :countries)
  28960. Country = Struct.new('Country', :country_id, :country_name)
  28961. Firm = Struct.new('Firm', :time_zone)
  28962. end
  28963. def test_collection_options
  28964. @posts = [
  28965. Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
  28966. Post.new("Babe went home", "Babe", "To a little house", "shh!"),
  28967. Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
  28968. ]
  28969. assert_dom_equal(
  28970. "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
  28971. options_from_collection_for_select(@posts, "author_name", "title")
  28972. )
  28973. end
  28974. def test_collection_options_with_preselected_value
  28975. @posts = [
  28976. Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
  28977. Post.new("Babe went home", "Babe", "To a little house", "shh!"),
  28978. Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
  28979. ]
  28980. assert_dom_equal(
  28981. "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
  28982. options_from_collection_for_select(@posts, "author_name", "title", "Babe")
  28983. )
  28984. end
  28985. def test_collection_options_with_preselected_value_array
  28986. @posts = [
  28987. Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
  28988. Post.new("Babe went home", "Babe", "To a little house", "shh!"),
  28989. Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
  28990. ]
  28991. assert_dom_equal(
  28992. "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>",
  28993. options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ])
  28994. )
  28995. end
  28996. def test_array_options_for_select
  28997. assert_dom_equal(
  28998. "<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\">USA</option>\n<option value=\"Sweden\">Sweden</option>",
  28999. options_for_select([ "<Denmark>", "USA", "Sweden" ])
  29000. )
  29001. end
  29002. def test_array_options_for_select_with_selection
  29003. assert_dom_equal(
  29004. "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\">Sweden</option>",
  29005. options_for_select([ "Denmark", "<USA>", "Sweden" ], "<USA>")
  29006. )
  29007. end
  29008. def test_array_options_for_select_with_selection_array
  29009. assert_dom_equal(
  29010. "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>",
  29011. options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ])
  29012. )
  29013. end
  29014. def test_array_options_for_string_include_in_other_string_bug_fix
  29015. assert_dom_equal(
  29016. "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
  29017. options_for_select([ "ruby", "rubyonrails" ], "rubyonrails")
  29018. )
  29019. assert_dom_equal(
  29020. "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>",
  29021. options_for_select([ "ruby", "rubyonrails" ], "ruby")
  29022. )
  29023. end
  29024. def test_hash_options_for_select
  29025. assert_dom_equal(
  29026. "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
  29027. options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" })
  29028. )
  29029. assert_dom_equal(
  29030. "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
  29031. options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar")
  29032. )
  29033. assert_dom_equal(
  29034. "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
  29035. options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ])
  29036. )
  29037. end
  29038. def test_ducktyped_options_for_select
  29039. quack = Struct.new(:first, :last)
  29040. assert_dom_equal(
  29041. "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
  29042. options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")])
  29043. )
  29044. assert_dom_equal(
  29045. "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
  29046. options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], "Dollar")
  29047. )
  29048. assert_dom_equal(
  29049. "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
  29050. options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], ["Dollar", "<Kroner>"])
  29051. )
  29052. end
  29053. def test_html_option_groups_from_collection
  29054. @continents = [
  29055. Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
  29056. Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
  29057. ]
  29058. assert_dom_equal(
  29059. "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
  29060. option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
  29061. )
  29062. end
  29063. def test_time_zone_options_no_parms
  29064. opts = time_zone_options_for_select
  29065. assert_dom_equal "<option value=\"A\">A</option>\n" +
  29066. "<option value=\"B\">B</option>\n" +
  29067. "<option value=\"C\">C</option>\n" +
  29068. "<option value=\"D\">D</option>\n" +
  29069. "<option value=\"E\">E</option>",
  29070. opts
  29071. end
  29072. def test_time_zone_options_with_selected
  29073. opts = time_zone_options_for_select( "D" )
  29074. assert_dom_equal "<option value=\"A\">A</option>\n" +
  29075. "<option value=\"B\">B</option>\n" +
  29076. "<option value=\"C\">C</option>\n" +
  29077. "<option value=\"D\" selected=\"selected\">D</option>\n" +
  29078. "<option value=\"E\">E</option>",
  29079. opts
  29080. end
  29081. def test_time_zone_options_with_unknown_selected
  29082. opts = time_zone_options_for_select( "K" )
  29083. assert_dom_equal "<option value=\"A\">A</option>\n" +
  29084. "<option value=\"B\">B</option>\n" +
  29085. "<option value=\"C\">C</option>\n" +
  29086. "<option value=\"D\">D</option>\n" +
  29087. "<option value=\"E\">E</option>",
  29088. opts
  29089. end
  29090. def test_time_zone_options_with_priority_zones
  29091. zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
  29092. opts = time_zone_options_for_select( nil, zones )
  29093. assert_dom_equal "<option value=\"B\">B</option>\n" +
  29094. "<option value=\"E\">E</option>" +
  29095. "<option value=\"\">-------------</option>\n" +
  29096. "<option value=\"A\">A</option>\n" +
  29097. "<option value=\"C\">C</option>\n" +
  29098. "<option value=\"D\">D</option>",
  29099. opts
  29100. end
  29101. def test_time_zone_options_with_selected_priority_zones
  29102. zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
  29103. opts = time_zone_options_for_select( "E", zones )
  29104. assert_dom_equal "<option value=\"B\">B</option>\n" +
  29105. "<option value=\"E\" selected=\"selected\">E</option>" +
  29106. "<option value=\"\">-------------</option>\n" +
  29107. "<option value=\"A\">A</option>\n" +
  29108. "<option value=\"C\">C</option>\n" +
  29109. "<option value=\"D\">D</option>",
  29110. opts
  29111. end
  29112. def test_time_zone_options_with_unselected_priority_zones
  29113. zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
  29114. opts = time_zone_options_for_select( "C", zones )
  29115. assert_dom_equal "<option value=\"B\">B</option>\n" +
  29116. "<option value=\"E\">E</option>" +
  29117. "<option value=\"\">-------------</option>\n" +
  29118. "<option value=\"A\">A</option>\n" +
  29119. "<option value=\"C\" selected=\"selected\">C</option>\n" +
  29120. "<option value=\"D\">D</option>",
  29121. opts
  29122. end
  29123. def test_select
  29124. @post = Post.new
  29125. @post.category = "<mus>"
  29126. assert_dom_equal(
  29127. "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29128. select("post", "category", %w( abe <mus> hest))
  29129. )
  29130. end
  29131. def test_select_under_fields_for
  29132. @post = Post.new
  29133. @post.category = "<mus>"
  29134. _erbout = ''
  29135. fields_for :post, @post do |f|
  29136. _erbout.concat f.select(:category, %w( abe <mus> hest))
  29137. end
  29138. assert_dom_equal(
  29139. "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29140. _erbout
  29141. )
  29142. end
  29143. def test_select_with_blank
  29144. @post = Post.new
  29145. @post.category = "<mus>"
  29146. assert_dom_equal(
  29147. "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29148. select("post", "category", %w( abe <mus> hest), :include_blank => true)
  29149. )
  29150. end
  29151. def test_select_with_default_prompt
  29152. @post = Post.new
  29153. @post.category = ""
  29154. assert_dom_equal(
  29155. "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29156. select("post", "category", %w( abe <mus> hest), :prompt => true)
  29157. )
  29158. end
  29159. def test_select_no_prompt_when_select_has_value
  29160. @post = Post.new
  29161. @post.category = "<mus>"
  29162. assert_dom_equal(
  29163. "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29164. select("post", "category", %w( abe <mus> hest), :prompt => true)
  29165. )
  29166. end
  29167. def test_select_with_given_prompt
  29168. @post = Post.new
  29169. @post.category = ""
  29170. assert_dom_equal(
  29171. "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">The prompt</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29172. select("post", "category", %w( abe <mus> hest), :prompt => 'The prompt')
  29173. )
  29174. end
  29175. def test_select_with_prompt_and_blank
  29176. @post = Post.new
  29177. @post.category = ""
  29178. assert_dom_equal(
  29179. "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29180. select("post", "category", %w( abe <mus> hest), :prompt => true, :include_blank => true)
  29181. )
  29182. end
  29183. def test_select_with_selected_value
  29184. @post = Post.new
  29185. @post.category = "<mus>"
  29186. assert_dom_equal(
  29187. "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" selected=\"selected\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29188. select("post", "category", %w( abe <mus> hest ), :selected => 'abe')
  29189. )
  29190. end
  29191. def test_select_with_selected_nil
  29192. @post = Post.new
  29193. @post.category = "<mus>"
  29194. assert_dom_equal(
  29195. "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
  29196. select("post", "category", %w( abe <mus> hest ), :selected => nil)
  29197. )
  29198. end
  29199. def test_collection_select
  29200. @posts = [
  29201. Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
  29202. Post.new("Babe went home", "Babe", "To a little house", "shh!"),
  29203. Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
  29204. ]
  29205. @post = Post.new
  29206. @post.author_name = "Babe"
  29207. assert_dom_equal(
  29208. "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
  29209. collection_select("post", "author_name", @posts, "author_name", "author_name")
  29210. )
  29211. end
  29212. def test_collection_select_under_fields_for
  29213. @posts = [
  29214. Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
  29215. Post.new("Babe went home", "Babe", "To a little house", "shh!"),
  29216. Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
  29217. ]
  29218. @post = Post.new
  29219. @post.author_name = "Babe"
  29220. _erbout = ''
  29221. fields_for :post, @post do |f|
  29222. _erbout.concat f.collection_select(:author_name, @posts, :author_name, :author_name)
  29223. end
  29224. assert_dom_equal(
  29225. "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
  29226. _erbout
  29227. )
  29228. end
  29229. def test_collection_select_with_blank_and_style
  29230. @posts = [
  29231. Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
  29232. Post.new("Babe went home", "Babe", "To a little house", "shh!"),
  29233. Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
  29234. ]
  29235. @post = Post.new
  29236. @post.author_name = "Babe"
  29237. assert_dom_equal(
  29238. "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
  29239. collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px")
  29240. )
  29241. end
  29242. def test_country_select
  29243. @post = Post.new
  29244. @post.origin = "Denmark"
  29245. assert_dom_equal(
  29246. "<select id=\"post_origin\" name=\"post[origin]\"><option value=\"Afghanistan\">Afghanistan</option>\n<option value=\"Albania\">Albania</option>\n<option value=\"Algeria\">Algeria</option>\n<option value=\"American Samoa\">American Samoa</option>\n<option value=\"Andorra\">Andorra</option>\n<option value=\"Angola\">Angola</option>\n<option value=\"Anguilla\">Anguilla</option>\n<option value=\"Antarctica\">Antarctica</option>\n<option value=\"Antigua And Barbuda\">Antigua And Barbuda</option>\n<option value=\"Argentina\">Argentina</option>\n<option value=\"Armenia\">Armenia</option>\n<option value=\"Aruba\">Aruba</option>\n<option value=\"Australia\">Australia</option>\n<option value=\"Austria\">Austria</option>\n<option value=\"Azerbaijan\">Azerbaijan</option>\n<option value=\"Bahamas\">Bahamas</option>\n<option value=\"Bahrain\">Bahrain</option>\n<option value=\"Bangladesh\">Bangladesh</option>\n<option value=\"Barbados\">Barbados</option>\n<option value=\"Belarus\">Belarus</option>\n<option value=\"Belgium\">Belgium</option>\n<option value=\"Belize\">Belize</option>\n<option value=\"Benin\">Benin</option>\n<option value=\"Bermuda\">Bermuda</option>\n<option value=\"Bhutan\">Bhutan</option>\n<option value=\"Bolivia\">Bolivia</option>\n<option value=\"Bosnia and Herzegowina\">Bosnia and Herzegowina</option>\n<option value=\"Botswana\">Botswana</option>\n<option value=\"Bouvet Island\">Bouvet Island</option>\n<option value=\"Brazil\">Brazil</option>\n<option value=\"British Indian Ocean Territory\">British Indian Ocean Territory</option>\n<option value=\"Brunei Darussalam\">Brunei Darussalam</option>\n<option value=\"Bulgaria\">Bulgaria</option>\n<option value=\"Burkina Faso\">Burkina Faso</option>\n<option value=\"Burma\">Burma</option>\n<option value=\"Burundi\">Burundi</option>\n<option value=\"Cambodia\">Cambodia</option>\n<option value=\"Cameroon\">Cameroon</option>\n<option value=\"Canada\">Canada</option>\n<option value=\"Cape Verde\">Cape Verde</option>\n<option value=\"Cayman Islands\">Cayman Islands</option>\n<option value=\"Central African Republic\">Central African Republic</option>\n<option value=\"Chad\">Chad</option>\n<option value=\"Chile\">Chile</option>\n<option value=\"China\">China</option>\n<option value=\"Christmas Island\">Christmas Island</option>\n<option value=\"Cocos (Keeling) Islands\">Cocos (Keeling) Islands</option>\n<option value=\"Colombia\">Colombia</option>\n<option value=\"Comoros\">Comoros</option>\n<option value=\"Congo\">Congo</option>\n<option value=\"Congo, the Democratic Republic of the\">Congo, the Democratic Republic of the</option>\n<option value=\"Cook Islands\">Cook Islands</option>\n<option value=\"Costa Rica\">Costa Rica</option>\n<option value=\"Cote d'Ivoire\">Cote d'Ivoire</option>\n<option value=\"Croatia\">Croatia</option>\n<option value=\"Cuba\">Cuba</option>\n<option value=\"Cyprus\">Cyprus</option>\n<option value=\"Czech Republic\">Czech Republic</option>\n<option value=\"Denmark\" selected=\"selected\">Denmark</option>\n<option value=\"Djibouti\">Djibouti</option>\n<option value=\"Dominica\">Dominica</option>\n<option value=\"Dominican Republic\">Dominican Republic</option>\n<option value=\"East Timor\">East Timor</option>\n<option value=\"Ecuador\">Ecuador</option>\n<option value=\"Egypt\">Egypt</option>\n<option value=\"El Salvador\">El Salvador</option>\n<option value=\"England\">England" +
  29247. "</option>\n<option value=\"Equatorial Guinea\">Equatorial Guinea</option>\n<option value=\"Eritrea\">Eritrea</option>\n<option value=\"Espana\">Espana</option>\n<option value=\"Estonia\">Estonia</option>\n<option value=\"Ethiopia\">Ethiopia</option>\n<option value=\"Falkland Islands\">Falkland Islands</option>\n<option value=\"Faroe Islands\">Faroe Islands</option>\n<option value=\"Fiji\">Fiji</option>\n<option value=\"Finland\">Finland</option>\n<option value=\"France\">France</option>\n<option value=\"French Guiana\">French Guiana</option>\n<option value=\"French Polynesia\">French Polynesia</option>\n<option value=\"French Southern Territories\">French Southern Territories</option>\n<option value=\"Gabon\">Gabon</option>\n<option value=\"Gambia\">Gambia</option>\n<option value=\"Georgia\">Georgia</option>\n<option value=\"Germany\">Germany</option>\n<option value=\"Ghana\">Ghana</option>\n<option value=\"Gibraltar\">Gibraltar</option>\n<option value=\"Great Britain\">Great Britain</option>\n<option value=\"Greece\">Greece</option>\n<option value=\"Greenland\">Greenland</option>\n<option value=\"Grenada\">Grenada</option>\n<option value=\"Guadeloupe\">Guadeloupe</option>\n<option value=\"Guam\">Guam</option>\n<option value=\"Guatemala\">Guatemala</option>\n<option value=\"Guinea\">Guinea</option>\n<option value=\"Guinea-Bissau\">Guinea-Bissau</option>\n<option value=\"Guyana\">Guyana</option>\n<option value=\"Haiti\">Haiti</option>\n<option value=\"Heard and Mc Donald Islands\">Heard and Mc Donald Islands</option>\n<option value=\"Honduras\">Honduras</option>\n<option value=\"Hong Kong\">Hong Kong</option>\n<option value=\"Hungary\">Hungary</option>\n<option value=\"Iceland\">Iceland</option>\n<option value=\"India\">India</option>\n<option value=\"Indonesia\">Indonesia</option>\n<option value=\"Ireland\">Ireland</option>\n<option value=\"Israel\">Israel</option>\n<option value=\"Italy\">Italy</option>\n<option value=\"Iran\">Iran</option>\n<option value=\"Iraq\">Iraq</option>\n<option value=\"Jamaica\">Jamaica</option>\n<option value=\"Japan\">Japan</option>\n<option value=\"Jordan\">Jordan</option>\n<option value=\"Kazakhstan\">Kazakhstan</option>\n<option value=\"Kenya\">Kenya</option>\n<option value=\"Kiribati\">Kiribati</option>\n<option value=\"Korea, Republic of\">Korea, Republic of</option>\n<option value=\"Korea (South)\">Korea (South)</option>\n<option value=\"Kuwait\">Kuwait</option>\n<option value=\"Kyrgyzstan\">Kyrgyzstan</option>\n<option value=\"Lao People's Democratic Republic\">Lao People's Democratic Republic</option>\n<option value=\"Latvia\">Latvia</option>\n<option value=\"Lebanon\">Lebanon</option>\n<option value=\"Lesotho\">Lesotho</option>\n<option value=\"Liberia\">Liberia</option>\n<option value=\"Liechtenstein\">Liechtenstein</option>\n<option value=\"Lithuania\">Lithuania</option>\n<option value=\"Luxembourg\">Luxembourg</option>\n<option value=\"Macau\">Macau</option>\n<option value=\"Macedonia\">Macedonia</option>\n<option value=\"Madagascar\">Madagascar</option>\n<option value=\"Malawi\">Malawi</option>\n<option value=\"Malaysia\">Malaysia</option>\n<option value=\"Maldives\">Maldives</option>\n<option value=\"Mali\">Mali</option>\n<option value=\"Malta\">Malta</option>\n<option value=\"Marshall Islands\">Marshall Islands</option>\n<option value=\"Martinique\">Martinique</option>\n<option value=\"Mauritania\">Mauritania</option>\n<option value=\"Mauritius\">Mauritius</option>\n<option value=\"Mayotte\">Mayotte</option>\n<option value=\"Mexico\">Mexico</option>\n<option value=\"Micronesia, Federated States of\">Micronesia, Federated States of</option>\n<option value=\"Moldova, Republic of\">Moldova, Republic of</option>\n<option value=\"Monaco\">Monaco</option>\n<option value=\"Mongolia\">Mongolia</option>\n<option value=\"Montserrat\">Montserrat</option>\n<option value=\"Morocco\">Morocco</option>\n<option value=\"Mozambique\">Mozambique</option>\n<option value=\"Myanmar\">Myanmar</option>\n<option value=\"Namibia\">Namibia</option>\n<option value=\"Nauru\">Nauru</option>\n<option value=\"Nepal\">Nepal</option>\n<option value=\"Netherlands\">Netherlands</option>\n<option value=\"Netherlands Antilles\">Netherlands Antilles</option>\n<option value=\"New Caledonia\">New Caledonia</option>" +
  29248. "\n<option value=\"New Zealand\">New Zealand</option>\n<option value=\"Nicaragua\">Nicaragua</option>\n<option value=\"Niger\">Niger</option>\n<option value=\"Nigeria\">Nigeria</option>\n<option value=\"Niue\">Niue</option>\n<option value=\"Norfolk Island\">Norfolk Island</option>\n<option value=\"Northern Ireland\">Northern Ireland</option>\n<option value=\"Northern Mariana Islands\">Northern Mariana Islands</option>\n<option value=\"Norway\">Norway</option>\n<option value=\"Oman\">Oman</option>\n<option value=\"Pakistan\">Pakistan</option>\n<option value=\"Palau\">Palau</option>\n<option value=\"Panama\">Panama</option>\n<option value=\"Papua New Guinea\">Papua New Guinea</option>\n<option value=\"Paraguay\">Paraguay</option>\n<option value=\"Peru\">Peru</option>\n<option value=\"Philippines\">Philippines</option>\n<option value=\"Pitcairn\">Pitcairn</option>\n<option value=\"Poland\">Poland</option>\n<option value=\"Portugal\">Portugal</option>\n<option value=\"Puerto Rico\">Puerto Rico</option>\n<option value=\"Qatar\">Qatar</option>\n<option value=\"Reunion\">Reunion</option>\n<option value=\"Romania\">Romania</option>\n<option value=\"Russia\">Russia</option>\n<option value=\"Rwanda\">Rwanda</option>\n<option value=\"Saint Kitts and Nevis\">Saint Kitts and Nevis</option>\n<option value=\"Saint Lucia\">Saint Lucia</option>\n<option value=\"Saint Vincent and the Grenadines\">Saint Vincent and the Grenadines</option>\n<option value=\"Samoa (Independent)\">Samoa (Independent)</option>\n<option value=\"San Marino\">San Marino</option>\n<option value=\"Sao Tome and Principe\">Sao Tome and Principe</option>\n<option value=\"Saudi Arabia\">Saudi Arabia</option>\n<option value=\"Scotland\">Scotland</option>\n<option value=\"Senegal\">Senegal</option>\n<option value=\"Serbia and Montenegro\">Serbia and Montenegro</option>\n<option value=\"Seychelles\">Seychelles</option>\n<option value=\"Sierra Leone\">Sierra Leone</option>\n<option value=\"Singapore\">Singapore</option>\n<option value=\"Slovakia\">Slovakia</option>\n<option value=\"Slovenia\">Slovenia</option>\n<option value=\"Solomon Islands\">Solomon Islands</option>\n<option value=\"Somalia\">Somalia</option>\n<option value=\"South Africa\">South Africa</option>\n<option value=\"South Georgia and the South Sandwich Islands\">South Georgia and the South Sandwich Islands</option>\n<option value=\"South Korea\">South Korea</option>\n<option value=\"Spain\">Spain</option>\n<option value=\"Sri Lanka\">Sri Lanka</option>\n<option value=\"St. Helena\">St. Helena</option>\n<option value=\"St. Pierre and Miquelon\">St. Pierre and Miquelon</option>\n<option value=\"Suriname\">Suriname</option>\n<option value=\"Svalbard and Jan Mayen Islands\">Svalbard and Jan Mayen Islands</option>\n<option value=\"Swaziland\">Swaziland</option>\n<option value=\"Sweden\">Sweden</option>\n<option value=\"Switzerland\">Switzerland</option>\n<option value=\"Taiwan\">Taiwan</option>\n<option value=\"Tajikistan\">Tajikistan</option>\n<option value=\"Tanzania\">Tanzania</option>\n<option value=\"Thailand\">Thailand</option>\n<option value=\"Togo\">Togo</option>\n<option value=\"Tokelau\">Tokelau</option>\n<option value=\"Tonga\">Tonga</option>\n<option value=\"Trinidad\">Trinidad</option>\n<option value=\"Trinidad and Tobago\">Trinidad and Tobago</option>\n<option value=\"Tunisia\">Tunisia</option>\n<option value=\"Turkey\">Turkey</option>\n<option value=\"Turkmenistan\">" +
  29249. "Turkmenistan</option>\n<option value=\"Turks and Caicos Islands\">Turks and Caicos Islands</option>\n<option value=\"Tuvalu\">Tuvalu</option>\n<option value=\"Uganda\">Uganda</option>\n<option value=\"Ukraine\">Ukraine</option>\n<option value=\"United Arab Emirates\">United Arab Emirates</option>\n<option value=\"United Kingdom\">United Kingdom</option>\n<option value=\"United States\">United States</option>\n<option value=\"United States Minor Outlying Islands\">United States Minor Outlying Islands</option>\n<option value=\"Uruguay\">Uruguay</option>\n<option value=\"Uzbekistan\">Uzbekistan</option>\n<option value=\"Vanuatu\">Vanuatu</option>\n<option value=\"Vatican City State (Holy See)\">Vatican City State (Holy See)</option>\n<option value=\"Venezuela\">Venezuela</option>\n<option value=\"Viet Nam\">Viet Nam</option>\n<option value=\"Virgin Islands (British)\">Virgin Islands (British)</option>\n<option value=\"Virgin Islands (U.S.)\">Virgin Islands (U.S.)</option>\n<option value=\"Wales\">Wales</option>\n<option value=\"Wallis and Futuna Islands\">Wallis and Futuna Islands</option>\n<option value=\"Western Sahara\">Western Sahara</option>\n<option value=\"Yemen\">Yemen</option>\n<option value=\"Zambia\">Zambia</option>\n<option value=\"Zimbabwe\">Zimbabwe</option></select>",
  29250. country_select("post", "origin")
  29251. )
  29252. end
  29253. def test_time_zone_select
  29254. @firm = Firm.new("D")
  29255. html = time_zone_select( "firm", "time_zone" )
  29256. assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
  29257. "<option value=\"A\">A</option>\n" +
  29258. "<option value=\"B\">B</option>\n" +
  29259. "<option value=\"C\">C</option>\n" +
  29260. "<option value=\"D\" selected=\"selected\">D</option>\n" +
  29261. "<option value=\"E\">E</option>" +
  29262. "</select>",
  29263. html
  29264. end
  29265. def test_time_zone_select_under_fields_for
  29266. @firm = Firm.new("D")
  29267. _erbout = ''
  29268. fields_for :firm, @firm do |f|
  29269. _erbout.concat f.time_zone_select(:time_zone)
  29270. end
  29271. assert_dom_equal(
  29272. "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
  29273. "<option value=\"A\">A</option>\n" +
  29274. "<option value=\"B\">B</option>\n" +
  29275. "<option value=\"C\">C</option>\n" +
  29276. "<option value=\"D\" selected=\"selected\">D</option>\n" +
  29277. "<option value=\"E\">E</option>" +
  29278. "</select>",
  29279. _erbout
  29280. )
  29281. end
  29282. def test_time_zone_select_with_blank
  29283. @firm = Firm.new("D")
  29284. html = time_zone_select("firm", "time_zone", nil, :include_blank => true)
  29285. assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
  29286. "<option value=\"\"></option>\n" +
  29287. "<option value=\"A\">A</option>\n" +
  29288. "<option value=\"B\">B</option>\n" +
  29289. "<option value=\"C\">C</option>\n" +
  29290. "<option value=\"D\" selected=\"selected\">D</option>\n" +
  29291. "<option value=\"E\">E</option>" +
  29292. "</select>",
  29293. html
  29294. end
  29295. def test_time_zone_select_with_style
  29296. @firm = Firm.new("D")
  29297. html = time_zone_select("firm", "time_zone", nil, {},
  29298. "style" => "color: red")
  29299. assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
  29300. "<option value=\"A\">A</option>\n" +
  29301. "<option value=\"B\">B</option>\n" +
  29302. "<option value=\"C\">C</option>\n" +
  29303. "<option value=\"D\" selected=\"selected\">D</option>\n" +
  29304. "<option value=\"E\">E</option>" +
  29305. "</select>",
  29306. html
  29307. assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {},
  29308. :style => "color: red")
  29309. end
  29310. def test_time_zone_select_with_blank_and_style
  29311. @firm = Firm.new("D")
  29312. html = time_zone_select("firm", "time_zone", nil,
  29313. { :include_blank => true }, "style" => "color: red")
  29314. assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
  29315. "<option value=\"\"></option>\n" +
  29316. "<option value=\"A\">A</option>\n" +
  29317. "<option value=\"B\">B</option>\n" +
  29318. "<option value=\"C\">C</option>\n" +
  29319. "<option value=\"D\" selected=\"selected\">D</option>\n" +
  29320. "<option value=\"E\">E</option>" +
  29321. "</select>",
  29322. html
  29323. assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
  29324. { :include_blank => true }, :style => "color: red")
  29325. end
  29326. def test_time_zone_select_with_priority_zones
  29327. @firm = Firm.new("D")
  29328. zones = [ TimeZone.new("A"), TimeZone.new("D") ]
  29329. html = time_zone_select("firm", "time_zone", zones )
  29330. assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
  29331. "<option value=\"A\">A</option>\n" +
  29332. "<option value=\"D\" selected=\"selected\">D</option>" +
  29333. "<option value=\"\">-------------</option>\n" +
  29334. "<option value=\"B\">B</option>\n" +
  29335. "<option value=\"C\">C</option>\n" +
  29336. "<option value=\"E\">E</option>" +
  29337. "</select>",
  29338. html
  29339. end
  29340. end
  29341. require File.dirname(__FILE__) + '/../abstract_unit'
  29342. class FormTagHelperTest < Test::Unit::TestCase
  29343. include ActionView::Helpers::UrlHelper
  29344. include ActionView::Helpers::TagHelper
  29345. include ActionView::Helpers::FormTagHelper
  29346. def setup
  29347. @controller = Class.new do
  29348. def url_for(options, *parameters_for_method_reference)
  29349. "http://www.example.com"
  29350. end
  29351. end
  29352. @controller = @controller.new
  29353. end
  29354. def test_check_box_tag
  29355. actual = check_box_tag "admin"
  29356. expected = %(<input id="admin" name="admin" type="checkbox" value="1" />)
  29357. assert_dom_equal expected, actual
  29358. end
  29359. def test_form_tag
  29360. actual = form_tag
  29361. expected = %(<form action="http://www.example.com" method="post">)
  29362. assert_dom_equal expected, actual
  29363. end
  29364. def test_form_tag_multipart
  29365. actual = form_tag({}, { 'multipart' => true })
  29366. expected = %(<form action="http://www.example.com" enctype="multipart/form-data" method="post">)
  29367. assert_dom_equal expected, actual
  29368. end
  29369. def test_hidden_field_tag
  29370. actual = hidden_field_tag "id", 3
  29371. expected = %(<input id="id" name="id" type="hidden" value="3" />)
  29372. assert_dom_equal expected, actual
  29373. end
  29374. def test_password_field_tag
  29375. actual = password_field_tag
  29376. expected = %(<input id="password" name="password" type="password" />)
  29377. assert_dom_equal expected, actual
  29378. end
  29379. def test_radio_button_tag
  29380. actual = radio_button_tag "people", "david"
  29381. expected = %(<input id="people" name="people" type="radio" value="david" />)
  29382. assert_dom_equal expected, actual
  29383. end
  29384. def test_select_tag
  29385. actual = select_tag "people", "<option>david</option>"
  29386. expected = %(<select id="people" name="people"><option>david</option></select>)
  29387. assert_dom_equal expected, actual
  29388. end
  29389. def test_text_area_tag_size_string
  29390. actual = text_area_tag "body", "hello world", "size" => "20x40"
  29391. expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
  29392. assert_dom_equal expected, actual
  29393. end
  29394. def test_text_area_tag_size_symbol
  29395. actual = text_area_tag "body", "hello world", :size => "20x40"
  29396. expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
  29397. assert_dom_equal expected, actual
  29398. end
  29399. def test_text_field_tag
  29400. actual = text_field_tag "title", "Hello!"
  29401. expected = %(<input id="title" name="title" type="text" value="Hello!" />)
  29402. assert_dom_equal expected, actual
  29403. end
  29404. def test_text_field_tag_class_string
  29405. actual = text_field_tag "title", "Hello!", "class" => "admin"
  29406. expected = %(<input class="admin" id="title" name="title" type="text" value="Hello!" />)
  29407. assert_dom_equal expected, actual
  29408. end
  29409. def test_boolean_optios
  29410. assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
  29411. assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)
  29412. assert_dom_equal %(<select id="people" multiple="multiple" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true)
  29413. assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil)
  29414. end
  29415. def test_stringify_symbol_keys
  29416. actual = text_field_tag "title", "Hello!", :id => "admin"
  29417. expected = %(<input id="admin" name="title" type="text" value="Hello!" />)
  29418. assert_dom_equal expected, actual
  29419. end
  29420. def test_submit_tag
  29421. assert_dom_equal(
  29422. %(<input name='commit' type='submit' value='Save' onclick="this.disabled=true;this.value='Saving...';this.form.submit();alert('hello!')" />),
  29423. submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
  29424. )
  29425. end
  29426. def test_pass
  29427. assert_equal 1, 1
  29428. end
  29429. end
  29430. require File.dirname(__FILE__) + '/../abstract_unit'
  29431. class JavaScriptMacrosHelperTest < Test::Unit::TestCase
  29432. include ActionView::Helpers::JavaScriptHelper
  29433. include ActionView::Helpers::JavaScriptMacrosHelper
  29434. include ActionView::Helpers::UrlHelper
  29435. include ActionView::Helpers::TagHelper
  29436. include ActionView::Helpers::TextHelper
  29437. include ActionView::Helpers::FormHelper
  29438. include ActionView::Helpers::CaptureHelper
  29439. def setup
  29440. @controller = Class.new do
  29441. def url_for(options, *parameters_for_method_reference)
  29442. url = "http://www.example.com/"
  29443. url << options[:action].to_s if options and options[:action]
  29444. url
  29445. end
  29446. end
  29447. @controller = @controller.new
  29448. end
  29449. def test_auto_complete_field
  29450. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {})\n//]]>\n</script>),
  29451. auto_complete_field("some_input", :url => { :action => "autocomplete" });
  29452. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:','})\n//]]>\n</script>),
  29453. auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ',');
  29454. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:[',']})\n//]]>\n</script>),
  29455. auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']);
  29456. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {minChars:3})\n//]]>\n</script>),
  29457. auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3);
  29458. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {onHide:function(element, update){alert('me');}})\n//]]>\n</script>),
  29459. auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}");
  29460. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {frequency:2})\n//]]>\n</script>),
  29461. auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2);
  29462. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {afterUpdateElement:function(element,value){alert('You have chosen: '+value)}})\n//]]>\n</script>),
  29463. auto_complete_field("some_input", :url => { :action => "autocomplete" },
  29464. :after_update_element => "function(element,value){alert('You have chosen: '+value)}");
  29465. end
  29466. def test_auto_complete_result
  29467. result = [ { :title => 'test1' }, { :title => 'test2' } ]
  29468. assert_equal %(<ul><li>test1</li><li>test2</li></ul>),
  29469. auto_complete_result(result, :title)
  29470. assert_equal %(<ul><li>t<strong class=\"highlight\">est</strong>1</li><li>t<strong class=\"highlight\">est</strong>2</li></ul>),
  29471. auto_complete_result(result, :title, "est")
  29472. resultuniq = [ { :title => 'test1' }, { :title => 'test1' } ]
  29473. assert_equal %(<ul><li>t<strong class=\"highlight\">est</strong>1</li></ul>),
  29474. auto_complete_result(resultuniq, :title, "est")
  29475. end
  29476. def test_text_field_with_auto_complete
  29477. assert_match "<style>",
  29478. text_field_with_auto_complete(:message, :recipient)
  29479. assert_dom_equal %(<input id=\"message_recipient\" name=\"message[recipient]\" size=\"30\" type=\"text\" /><div class=\"auto_complete\" id=\"message_recipient_auto_complete\"></div><script type=\"text/javascript\">\n//<![CDATA[\nvar message_recipient_auto_completer = new Ajax.Autocompleter('message_recipient', 'message_recipient_auto_complete', 'http://www.example.com/auto_complete_for_message_recipient', {})\n//]]>\n</script>),
  29480. text_field_with_auto_complete(:message, :recipient, {}, :skip_style => true)
  29481. end
  29482. def test_in_place_editor_external_control
  29483. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {externalControl:'blah'})\n//]]>\n</script>),
  29484. in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :external_control => 'blah'})
  29485. end
  29486. def test_in_place_editor_size
  29487. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {size:4})\n//]]>\n</script>),
  29488. in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :size => 4})
  29489. end
  29490. def test_in_place_editor_cols_no_rows
  29491. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {cols:4})\n//]]>\n</script>),
  29492. in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :cols => 4})
  29493. end
  29494. def test_in_place_editor_cols_with_rows
  29495. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {cols:40, rows:5})\n//]]>\n</script>),
  29496. in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :rows => 5, :cols => 40})
  29497. end
  29498. def test_inplace_editor_loading_text
  29499. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {loadingText:'Why are we waiting?'})\n//]]>\n</script>),
  29500. in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :loading_text => 'Why are we waiting?'})
  29501. end
  29502. def test_in_place_editor_url
  29503. assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value')",
  29504. in_place_editor( 'id-goes-here', :url => { :action => "action_to_set_value" })
  29505. end
  29506. def test_in_place_editor_load_text_url
  29507. assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {loadTextURL:'http://www.example.com/action_to_get_value'})",
  29508. in_place_editor( 'id-goes-here',
  29509. :url => { :action => "action_to_set_value" },
  29510. :load_text_url => { :action => "action_to_get_value" })
  29511. end
  29512. def test_in_place_editor_eval_scripts
  29513. assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {evalScripts:true})",
  29514. in_place_editor( 'id-goes-here',
  29515. :url => { :action => "action_to_set_value" },
  29516. :script => true )
  29517. end
  29518. end
  29519. require File.dirname(__FILE__) + '/../abstract_unit'
  29520. class JavaScriptHelperTest < Test::Unit::TestCase
  29521. include ActionView::Helpers::JavaScriptHelper
  29522. include ActionView::Helpers::UrlHelper
  29523. include ActionView::Helpers::TagHelper
  29524. include ActionView::Helpers::TextHelper
  29525. include ActionView::Helpers::FormHelper
  29526. include ActionView::Helpers::CaptureHelper
  29527. def test_define_javascript_functions
  29528. # check if prototype.js is included first
  29529. assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
  29530. # check that scriptaculous.js is not in here, only needed if loaded remotely
  29531. assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
  29532. end
  29533. def test_escape_javascript
  29534. assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
  29535. end
  29536. def test_link_to_function
  29537. assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
  29538. link_to_function("Greeting", "alert('Hello world!')")
  29539. end
  29540. def test_link_to_function_with_existing_onclick
  29541. assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>),
  29542. link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
  29543. end
  29544. def test_button_to_function
  29545. assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
  29546. button_to_function("Greeting", "alert('Hello world!')")
  29547. end
  29548. end
  29549. require 'test/unit'
  29550. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/number_helper'
  29551. require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/hash' # for stringify_keys
  29552. require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/numeric' # for human_size
  29553. class NumberHelperTest < Test::Unit::TestCase
  29554. include ActionView::Helpers::NumberHelper
  29555. include ActiveSupport::CoreExtensions::Hash
  29556. def test_number_to_phone
  29557. assert_equal("123-555-1234", number_to_phone(1235551234))
  29558. assert_equal("(123) 555-1234", number_to_phone(1235551234, {:area_code => true}))
  29559. assert_equal("123 555 1234", number_to_phone(1235551234, {:delimiter => " "}))
  29560. assert_equal("(123) 555-1234 x 555", number_to_phone(1235551234, {:area_code => true, :extension => 555}))
  29561. assert_equal("123-555-1234", number_to_phone(1235551234, :extension => " "))
  29562. end
  29563. def test_number_to_currency
  29564. assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50))
  29565. assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
  29566. assert_equal("$1,234,567,890", number_to_currency(1234567890.50, {:precision => 0}))
  29567. assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
  29568. assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
  29569. end
  29570. def test_number_to_percentage
  29571. assert_equal("100.000%", number_to_percentage(100))
  29572. assert_equal("100%", number_to_percentage(100, {:precision => 0}))
  29573. assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2}))
  29574. end
  29575. def test_number_with_delimiter
  29576. assert_equal("12,345,678", number_with_delimiter(12345678))
  29577. end
  29578. def test_number_to_human_size
  29579. assert_equal '0 Bytes', human_size(0)
  29580. assert_equal '3 Bytes', human_size(3.14159265)
  29581. assert_equal '123 Bytes', human_size(123.0)
  29582. assert_equal '123 Bytes', human_size(123)
  29583. assert_equal '1.2 KB', human_size(1234)
  29584. assert_equal '12.1 KB', human_size(12345)
  29585. assert_equal '1.2 MB', human_size(1234567)
  29586. assert_equal '1.1 GB', human_size(1234567890)
  29587. assert_equal '1.1 TB', human_size(1234567890123)
  29588. assert_equal '444 KB', human_size(444.kilobytes)
  29589. assert_equal '1023 MB', human_size(1023.megabytes)
  29590. assert_equal '3 TB', human_size(3.terabytes)
  29591. assert_nil human_size('x')
  29592. assert_nil human_size(nil)
  29593. end
  29594. def test_number_with_precision
  29595. assert_equal("111.235", number_with_precision(111.2346))
  29596. end
  29597. end
  29598. require File.dirname(__FILE__) + '/../abstract_unit'
  29599. module BaseTest
  29600. include ActionView::Helpers::JavaScriptHelper
  29601. include ActionView::Helpers::PrototypeHelper
  29602. include ActionView::Helpers::ScriptaculousHelper
  29603. include ActionView::Helpers::UrlHelper
  29604. include ActionView::Helpers::TagHelper
  29605. include ActionView::Helpers::TextHelper
  29606. include ActionView::Helpers::FormHelper
  29607. include ActionView::Helpers::CaptureHelper
  29608. def setup
  29609. @controller = Class.new do
  29610. def url_for(options, *parameters_for_method_reference)
  29611. url = "http://www.example.com/"
  29612. url << options[:action].to_s if options and options[:action]
  29613. url << "?a=#{options[:a]}" if options && options[:a]
  29614. url << "&b=#{options[:b]}" if options && options[:a] && options[:b]
  29615. url
  29616. end
  29617. end.new
  29618. end
  29619. protected
  29620. def create_generator
  29621. block = Proc.new { |*args| yield *args if block_given? }
  29622. JavaScriptGenerator.new self, &block
  29623. end
  29624. end
  29625. class PrototypeHelperTest < Test::Unit::TestCase
  29626. include BaseTest
  29627. def test_link_to_remote
  29628. assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outpost</a>),
  29629. link_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
  29630. assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
  29631. link_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" })
  29632. assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
  29633. link_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" })
  29634. assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
  29635. link_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" })
  29636. assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&amp;b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
  29637. link_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
  29638. end
  29639. def test_periodically_call_remote
  29640. 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>),
  29641. periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
  29642. end
  29643. def test_form_remote_tag
  29644. 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;\">),
  29645. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast })
  29646. 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;\">),
  29647. form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast })
  29648. 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;\">),
  29649. form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast })
  29650. 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;\">),
  29651. form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast })
  29652. end
  29653. def test_on_callbacks
  29654. callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
  29655. callbacks.each do |callback|
  29656. 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;">),
  29657. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
  29658. 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;">),
  29659. form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
  29660. 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;">),
  29661. form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
  29662. 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;">),
  29663. form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
  29664. end
  29665. #HTTP status codes 200 up to 599 have callbacks
  29666. #these should work
  29667. 100.upto(599) do |callback|
  29668. 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;">),
  29669. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
  29670. end
  29671. #test 200 and 404
  29672. 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;">),
  29673. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
  29674. #these shouldn't
  29675. 1.upto(99) do |callback|
  29676. 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;">),
  29677. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
  29678. end
  29679. 600.upto(999) do |callback|
  29680. 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;">),
  29681. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
  29682. end
  29683. #test ultimate combo
  29684. 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;\">),
  29685. form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
  29686. end
  29687. def test_submit_to_remote
  29688. 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)}); return false;\" type=\"button\" value=\"1000000\" />),
  29689. submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
  29690. end
  29691. def test_observe_field
  29692. 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})})\n//]]>\n</script>),
  29693. observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
  29694. end
  29695. def test_observe_field_using_function_for_callback
  29696. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {alert('Element changed')})\n//]]>\n</script>),
  29697. observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')")
  29698. end
  29699. def test_observe_form
  29700. 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})})\n//]]>\n</script>),
  29701. observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
  29702. end
  29703. def test_observe_form_using_function_for_callback
  29704. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>),
  29705. observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
  29706. end
  29707. def test_update_element_function
  29708. assert_equal %($('myelement').innerHTML = 'blub';\n),
  29709. update_element_function('myelement', :content => 'blub')
  29710. assert_equal %($('myelement').innerHTML = 'blub';\n),
  29711. update_element_function('myelement', :action => :update, :content => 'blub')
  29712. assert_equal %($('myelement').innerHTML = '';\n),
  29713. update_element_function('myelement', :action => :empty)
  29714. assert_equal %(Element.remove('myelement');\n),
  29715. update_element_function('myelement', :action => :remove)
  29716. assert_equal %(new Insertion.Bottom('myelement','blub');\n),
  29717. update_element_function('myelement', :position => 'bottom', :content => 'blub')
  29718. assert_equal %(new Insertion.Bottom('myelement','blub');\n),
  29719. update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
  29720. _erbout = ""
  29721. assert_equal %($('myelement').innerHTML = 'test';\n),
  29722. update_element_function('myelement') { _erbout << "test" }
  29723. _erbout = ""
  29724. assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
  29725. update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
  29726. end
  29727. def test_update_page
  29728. block = Proc.new { |page| page.replace_html('foo', 'bar') }
  29729. assert_equal create_generator(&block).to_s, update_page(&block)
  29730. end
  29731. def test_update_page_tag
  29732. block = Proc.new { |page| page.replace_html('foo', 'bar') }
  29733. assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
  29734. end
  29735. end
  29736. class JavaScriptGeneratorTest < Test::Unit::TestCase
  29737. include BaseTest
  29738. def setup
  29739. super
  29740. @generator = create_generator
  29741. end
  29742. def test_insert_html_with_string
  29743. assert_equal 'new Insertion.Top("element", "<p>This is a test</p>");',
  29744. @generator.insert_html(:top, 'element', '<p>This is a test</p>')
  29745. assert_equal 'new Insertion.Bottom("element", "<p>This is a test</p>");',
  29746. @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
  29747. assert_equal 'new Insertion.Before("element", "<p>This is a test</p>");',
  29748. @generator.insert_html(:before, 'element', '<p>This is a test</p>')
  29749. assert_equal 'new Insertion.After("element", "<p>This is a test</p>");',
  29750. @generator.insert_html(:after, 'element', '<p>This is a test</p>')
  29751. end
  29752. def test_replace_html_with_string
  29753. assert_equal 'Element.update("element", "<p>This is a test</p>");',
  29754. @generator.replace_html('element', '<p>This is a test</p>')
  29755. end
  29756. def test_replace_element_with_string
  29757. assert_equal 'Element.replace("element", "<div id=\"element\"><p>This is a test</p></div>");',
  29758. @generator.replace('element', '<div id="element"><p>This is a test</p></div>')
  29759. end
  29760. def test_remove
  29761. assert_equal '["foo"].each(Element.remove);',
  29762. @generator.remove('foo')
  29763. assert_equal '["foo", "bar", "baz"].each(Element.remove);',
  29764. @generator.remove('foo', 'bar', 'baz')
  29765. end
  29766. def test_show
  29767. assert_equal 'Element.show("foo");',
  29768. @generator.show('foo')
  29769. assert_equal 'Element.show("foo", "bar", "baz");',
  29770. @generator.show('foo', 'bar', 'baz')
  29771. end
  29772. def test_hide
  29773. assert_equal 'Element.hide("foo");',
  29774. @generator.hide('foo')
  29775. assert_equal 'Element.hide("foo", "bar", "baz");',
  29776. @generator.hide('foo', 'bar', 'baz')
  29777. end
  29778. def test_alert
  29779. assert_equal 'alert("hello");', @generator.alert('hello')
  29780. end
  29781. def test_redirect_to
  29782. assert_equal 'window.location.href = "http://www.example.com/welcome";',
  29783. @generator.redirect_to(:action => 'welcome')
  29784. end
  29785. def test_delay
  29786. @generator.delay(20) do
  29787. @generator.hide('foo')
  29788. end
  29789. assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
  29790. end
  29791. def test_to_s
  29792. @generator.insert_html(:top, 'element', '<p>This is a test</p>')
  29793. @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
  29794. @generator.remove('foo', 'bar')
  29795. @generator.replace_html('baz', '<p>This is a test</p>')
  29796. assert_equal <<-EOS.chomp, @generator.to_s
  29797. new Insertion.Top("element", "<p>This is a test</p>");
  29798. new Insertion.Bottom("element", "<p>This is a test</p>");
  29799. ["foo", "bar"].each(Element.remove);
  29800. Element.update("baz", "<p>This is a test</p>");
  29801. EOS
  29802. end
  29803. def test_element_access
  29804. assert_equal %($("hello");), @generator['hello']
  29805. end
  29806. def test_element_proxy_one_deep
  29807. @generator['hello'].hide
  29808. assert_equal %($("hello").hide();), @generator.to_s
  29809. end
  29810. def test_element_proxy_assignment
  29811. @generator['hello'].width = 400
  29812. assert_equal %($("hello").width = 400;), @generator.to_s
  29813. end
  29814. def test_element_proxy_two_deep
  29815. @generator['hello'].hide("first").clean_whitespace
  29816. assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
  29817. end
  29818. def test_select_access
  29819. assert_equal %($$("div.hello");), @generator.select('div.hello')
  29820. end
  29821. def test_select_proxy_one_deep
  29822. @generator.select('p.welcome b').first.hide
  29823. assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
  29824. end
  29825. def test_visual_effect
  29826. assert_equal %(new Effect.Puff("blah",{});),
  29827. @generator.visual_effect(:puff,'blah')
  29828. end
  29829. def test_visual_effect_toggle
  29830. assert_equal %(Effect.toggle("blah",'appear',{});),
  29831. @generator.visual_effect(:toggle_appear,'blah')
  29832. end
  29833. def test_sortable
  29834. assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
  29835. @generator.sortable('blah', :url => { :action => "order" })
  29836. end
  29837. def test_draggable
  29838. assert_equal %(new Draggable("blah", {});),
  29839. @generator.draggable('blah')
  29840. end
  29841. def test_drop_receiving
  29842. 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)})}});),
  29843. @generator.drop_receiving('blah', :url => { :action => "order" })
  29844. end
  29845. def test_collection_first_and_last
  29846. @generator.select('p.welcome b').first.hide()
  29847. @generator.select('p.welcome b').last.show()
  29848. assert_equal <<-EOS.strip, @generator.to_s
  29849. $$("p.welcome b").first().hide();
  29850. $$("p.welcome b").last().show();
  29851. EOS
  29852. end
  29853. def test_collection_proxy_with_each
  29854. @generator.select('p.welcome b').each do |value|
  29855. value.remove_class_name 'selected'
  29856. end
  29857. @generator.select('p.welcome b').each do |value, index|
  29858. @generator.visual_effect :highlight, value
  29859. end
  29860. assert_equal <<-EOS.strip, @generator.to_s
  29861. $$("p.welcome b").each(function(value, index) {
  29862. value.removeClassName("selected");
  29863. });
  29864. $$("p.welcome b").each(function(value, index) {
  29865. new Effect.Highlight(value,{});
  29866. });
  29867. EOS
  29868. end
  29869. def test_collection_proxy_on_collect
  29870. @generator.select('p').collect('a') { |para| para.show }
  29871. @generator.select('p').collect { |para| para.hide }
  29872. assert_equal <<-EOS.strip, @generator.to_s
  29873. var a = $$("p").collect(function(value, index) {
  29874. return value.show();
  29875. });
  29876. $$("p").collect(function(value, index) {
  29877. return value.hide();
  29878. });
  29879. EOS
  29880. @generator = create_generator
  29881. end
  29882. def test_collection_proxy_with_grep
  29883. @generator.select('p').grep 'a', /^a/ do |value|
  29884. @generator << '(value.className == "welcome")'
  29885. end
  29886. @generator.select('p').grep 'b', /b$/ do |value, index|
  29887. @generator.call 'alert', value
  29888. @generator << '(value.className == "welcome")'
  29889. end
  29890. assert_equal <<-EOS.strip, @generator.to_s
  29891. var a = $$("p").grep(/^a/, function(value, index) {
  29892. return (value.className == "welcome");
  29893. });
  29894. var b = $$("p").grep(/b$/, function(value, index) {
  29895. alert(value);
  29896. return (value.className == "welcome");
  29897. });
  29898. EOS
  29899. end
  29900. def test_collection_proxy_with_inject
  29901. @generator.select('p').inject 'a', [] do |memo, value|
  29902. @generator << '(value.className == "welcome")'
  29903. end
  29904. @generator.select('p').inject 'b', nil do |memo, value, index|
  29905. @generator.call 'alert', memo
  29906. @generator << '(value.className == "welcome")'
  29907. end
  29908. assert_equal <<-EOS.strip, @generator.to_s
  29909. var a = $$("p").inject([], function(memo, value, index) {
  29910. return (value.className == "welcome");
  29911. });
  29912. var b = $$("p").inject(null, function(memo, value, index) {
  29913. alert(memo);
  29914. return (value.className == "welcome");
  29915. });
  29916. EOS
  29917. end
  29918. def test_collection_proxy_with_pluck
  29919. @generator.select('p').pluck('a', 'className')
  29920. assert_equal %(var a = $$("p").pluck("className");), @generator.to_s
  29921. end
  29922. def test_collection_proxy_with_zip
  29923. ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
  29924. ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |array|
  29925. @generator.call 'array.reverse'
  29926. end
  29927. assert_equal <<-EOS.strip, @generator.to_s
  29928. var a = [1, 2, 3].zip([4, 5, 6], [7, 8, 9]);
  29929. var b = [1, 2, 3].zip([4, 5, 6], [7, 8, 9], function(array) {
  29930. return array.reverse();
  29931. });
  29932. EOS
  29933. end
  29934. def test_collection_proxy_with_find_all
  29935. @generator.select('p').find_all 'a' do |value, index|
  29936. @generator << '(value.className == "welcome")'
  29937. end
  29938. assert_equal <<-EOS.strip, @generator.to_s
  29939. var a = $$("p").findAll(function(value, index) {
  29940. return (value.className == "welcome");
  29941. });
  29942. EOS
  29943. end
  29944. def test_debug_rjs
  29945. ActionView::Base.debug_rjs = true
  29946. @generator['welcome'].replace_html 'Welcome'
  29947. 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
  29948. ensure
  29949. ActionView::Base.debug_rjs = false
  29950. end
  29951. def test_class_proxy
  29952. @generator.form.focus('my_field')
  29953. assert_equal "Form.focus(\"my_field\");", @generator.to_s
  29954. end
  29955. end
  29956. require File.dirname(__FILE__) + '/../abstract_unit'
  29957. class ScriptaculousHelperTest < Test::Unit::TestCase
  29958. include ActionView::Helpers::JavaScriptHelper
  29959. include ActionView::Helpers::PrototypeHelper
  29960. include ActionView::Helpers::ScriptaculousHelper
  29961. include ActionView::Helpers::UrlHelper
  29962. include ActionView::Helpers::TagHelper
  29963. include ActionView::Helpers::TextHelper
  29964. include ActionView::Helpers::FormHelper
  29965. include ActionView::Helpers::CaptureHelper
  29966. def setup
  29967. @controller = Class.new do
  29968. def url_for(options, *parameters_for_method_reference)
  29969. url = "http://www.example.com/"
  29970. url << options[:action].to_s if options and options[:action]
  29971. url
  29972. end
  29973. end.new
  29974. end
  29975. def test_effect
  29976. assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, "posts")
  29977. assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect("highlight", :posts)
  29978. assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, :posts)
  29979. assert_equal "new Effect.Fade(\"fademe\",{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
  29980. assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
  29981. assert_equal "new Effect.DropOut(\"dropme\",{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
  29982. # chop the queue params into a comma separated list
  29983. beginning, ending = 'new Effect.DropOut("dropme",{queue:{', '}});'
  29984. ve = [
  29985. visual_effect(:drop_out, 'dropme', :queue => {:position => "end", :scope => "test", :limit => 2}),
  29986. visual_effect(:drop_out, 'dropme', :queue => {:scope => :list, :limit => 2}),
  29987. visual_effect(:drop_out, 'dropme', :queue => {:position => :end, :scope => :test, :limit => 2})
  29988. ].collect { |v| v[beginning.length..-ending.length-1].split(',') }
  29989. assert ve[0].include?("limit:2")
  29990. assert ve[0].include?("scope:'test'")
  29991. assert ve[0].include?("position:'end'")
  29992. assert ve[1].include?("limit:2")
  29993. assert ve[1].include?("scope:'list'")
  29994. assert ve[2].include?("limit:2")
  29995. assert ve[2].include?("scope:'test'")
  29996. assert ve[2].include?("position:'end'")
  29997. end
  29998. def test_toggle_effects
  29999. assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect(:toggle_appear, "posts")
  30000. assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect(:toggle_slide, "posts")
  30001. assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect(:toggle_blind, "posts")
  30002. assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect("toggle_appear", "posts")
  30003. assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect("toggle_slide", "posts")
  30004. assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect("toggle_blind", "posts")
  30005. end
  30006. def test_sortable_element
  30007. 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>),
  30008. sortable_element("mylist", :url => { :action => "order" })
  30009. 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>),
  30010. sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
  30011. 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>|,
  30012. sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
  30013. 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>),
  30014. sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
  30015. end
  30016. def test_draggable_element
  30017. assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {})\n//]]>\n</script>),
  30018. draggable_element("product_13")
  30019. assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {revert:true})\n//]]>\n</script>),
  30020. draggable_element("product_13", :revert => true)
  30021. end
  30022. def test_drop_receiving_element
  30023. 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>),
  30024. drop_receiving_element("droptarget1")
  30025. 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>),
  30026. drop_receiving_element("droptarget1", :accept => 'products')
  30027. 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>),
  30028. drop_receiving_element("droptarget1", :accept => 'products', :update => 'infobox')
  30029. 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>),
  30030. drop_receiving_element("droptarget1", :accept => ['tshirts','mugs'], :update => 'infobox')
  30031. end
  30032. endrequire File.dirname(__FILE__) + '/../abstract_unit'
  30033. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
  30034. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
  30035. class TagHelperTest < Test::Unit::TestCase
  30036. include ActionView::Helpers::TagHelper
  30037. include ActionView::Helpers::UrlHelper
  30038. def test_tag
  30039. assert_equal "<p class=\"show\" />", tag("p", "class" => "show")
  30040. assert_equal tag("p", "class" => "show"), tag("p", :class => "show")
  30041. end
  30042. def test_tag_options
  30043. assert_equal "<p class=\"elsewhere\" />", tag("p", "class" => "show", :class => "elsewhere")
  30044. end
  30045. def test_tag_options_rejects_nil_option
  30046. assert_equal "<p />", tag("p", :ignored => nil)
  30047. end
  30048. def test_tag_options_accepts_blank_option
  30049. assert_equal "<p included=\"\" />", tag("p", :included => '')
  30050. end
  30051. def test_tag_options_converts_boolean_option
  30052. assert_equal '<p disabled="disabled" multiple="multiple" readonly="readonly" />',
  30053. tag("p", :disabled => true, :multiple => true, :readonly => true)
  30054. end
  30055. def test_content_tag
  30056. assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
  30057. assert_equal content_tag("a", "Create", "href" => "create"),
  30058. content_tag("a", "Create", :href => "create")
  30059. end
  30060. def test_cdata_section
  30061. assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
  30062. end
  30063. end
  30064. require File.dirname(__FILE__) + '/../abstract_unit'
  30065. require "#{File.dirname(__FILE__)}/../testing_sandbox"
  30066. class TextHelperTest < Test::Unit::TestCase
  30067. include ActionView::Helpers::TextHelper
  30068. include ActionView::Helpers::TagHelper
  30069. include TestingSandbox
  30070. def setup
  30071. # This simulates the fact that instance variables are reset every time
  30072. # a view is rendered. The cycle helper depends on this behavior.
  30073. @_cycles = nil if (defined? @_cycles)
  30074. end
  30075. def test_simple_format
  30076. assert_equal "<p>crazy\n<br /> cross\n<br /> platform linebreaks</p>", simple_format("crazy\r\n cross\r platform linebreaks")
  30077. assert_equal "<p>A paragraph</p>\n\n<p>and another one!</p>", simple_format("A paragraph\n\nand another one!")
  30078. assert_equal "<p>A paragraph\n<br /> With a newline</p>", simple_format("A paragraph\n With a newline")
  30079. end
  30080. def test_truncate
  30081. assert_equal "Hello World!", truncate("Hello World!", 12)
  30082. assert_equal "Hello Wor...", truncate("Hello World!!", 12)
  30083. end
  30084. def test_truncate_multibyte_without_kcode
  30085. result = execute_in_sandbox(<<-'CODE')
  30086. require File.dirname(__FILE__) + '/../../activesupport/lib/active_support/core_ext/kernel'
  30087. require "#{File.dirname(__FILE__)}/../lib/action_view/helpers/text_helper"
  30088. include ActionView::Helpers::TextHelper
  30089. truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10)
  30090. CODE
  30091. assert_equal "\354\225\210\353\205\225\355...", result
  30092. end
  30093. def test_truncate_multibyte_with_kcode
  30094. result = execute_in_sandbox(<<-'CODE')
  30095. $KCODE = "u"
  30096. require 'jcode'
  30097. require File.dirname(__FILE__) + '/../../activesupport/lib/active_support/core_ext/kernel'
  30098. require "#{File.dirname(__FILE__)}/../lib/action_view/helpers/text_helper"
  30099. include ActionView::Helpers::TextHelper
  30100. truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254\353\236 \354\225\204\353\235\274\353\246\254\354\230\244", 10)
  30101. CODE
  30102. assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254\353\236 ...", result
  30103. end
  30104. def test_strip_links
  30105. assert_equal "on my mind", strip_links("<a href='almost'>on my mind</a>")
  30106. end
  30107. def test_highlighter
  30108. assert_equal(
  30109. "This is a <strong class=\"highlight\">beautiful</strong> morning",
  30110. highlight("This is a beautiful morning", "beautiful")
  30111. )
  30112. assert_equal(
  30113. "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
  30114. highlight("This is a beautiful morning, but also a beautiful day", "beautiful")
  30115. )
  30116. assert_equal(
  30117. "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
  30118. highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
  30119. )
  30120. assert_equal(
  30121. "This text is not changed because we supplied an empty phrase",
  30122. highlight("This text is not changed because we supplied an empty phrase", nil)
  30123. )
  30124. end
  30125. def test_highlighter_with_regexp
  30126. assert_equal(
  30127. "This is a <strong class=\"highlight\">beautiful!</strong> morning",
  30128. highlight("This is a beautiful! morning", "beautiful!")
  30129. )
  30130. assert_equal(
  30131. "This is a <strong class=\"highlight\">beautiful! morning</strong>",
  30132. highlight("This is a beautiful! morning", "beautiful! morning")
  30133. )
  30134. assert_equal(
  30135. "This is a <strong class=\"highlight\">beautiful? morning</strong>",
  30136. highlight("This is a beautiful? morning", "beautiful? morning")
  30137. )
  30138. end
  30139. def test_excerpt
  30140. assert_equal("...is a beautiful morni...", excerpt("This is a beautiful morning", "beautiful", 5))
  30141. assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
  30142. assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
  30143. assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
  30144. assert_nil excerpt("This is a beautiful morning", "day")
  30145. end
  30146. def test_excerpt_with_regex
  30147. assert_equal('...is a beautiful! morn...', excerpt('This is a beautiful! morning', 'beautiful', 5))
  30148. assert_equal('...is a beautiful? morn...', excerpt('This is a beautiful? morning', 'beautiful', 5))
  30149. end
  30150. def test_word_wrap
  30151. assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", 15))
  30152. end
  30153. def test_pluralization
  30154. assert_equal("1 count", pluralize(1, "count"))
  30155. assert_equal("2 counts", pluralize(2, "count"))
  30156. end
  30157. def test_auto_linking
  30158. email_raw = 'david@loudthinking.com'
  30159. email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
  30160. link_raw = 'http://www.rubyonrails.com'
  30161. link_result = %{<a href="#{link_raw}">#{link_raw}</a>}
  30162. link_result_with_options = %{<a href="#{link_raw}" target="_blank">#{link_raw}</a>}
  30163. link2_raw = 'www.rubyonrails.com'
  30164. link2_result = %{<a href="http://#{link2_raw}">#{link2_raw}</a>}
  30165. link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281'
  30166. link3_result = %{<a href="#{link3_raw}">#{link3_raw}</a>}
  30167. link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123'
  30168. link4_result = %{<a href="#{link4_raw}">#{link4_raw}</a>}
  30169. link5_raw = 'http://foo.example.com:3000/controller/action'
  30170. link5_result = %{<a href="#{link5_raw}">#{link5_raw}</a>}
  30171. assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses)
  30172. assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls)
  30173. assert_equal %(Go to #{link_raw}), auto_link("Go to #{link_raw}", :email_addresses)
  30174. assert_equal %(Go to #{link_result} and say hello to #{email_result}), auto_link("Go to #{link_raw} and say hello to #{email_raw}")
  30175. assert_equal %(<p>Link #{link_result}</p>), auto_link("<p>Link #{link_raw}</p>")
  30176. assert_equal %(<p>#{link_result} Link</p>), auto_link("<p>#{link_raw} Link</p>")
  30177. assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"})
  30178. assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.))
  30179. assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>))
  30180. assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls)
  30181. assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses)
  30182. assert_equal %(<p>Link #{link2_result}</p>), auto_link("<p>Link #{link2_raw}</p>")
  30183. assert_equal %(<p>#{link2_result} Link</p>), auto_link("<p>#{link2_raw} Link</p>")
  30184. assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.))
  30185. assert_equal %(<p>Say hello to #{email_result}, then go to #{link2_result}.</p>), auto_link(%(<p>Say hello to #{email_raw}, then go to #{link2_raw}.</p>))
  30186. assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls)
  30187. assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses)
  30188. assert_equal %(<p>Link #{link3_result}</p>), auto_link("<p>Link #{link3_raw}</p>")
  30189. assert_equal %(<p>#{link3_result} Link</p>), auto_link("<p>#{link3_raw} Link</p>")
  30190. assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.))
  30191. assert_equal %(<p>Go to #{link3_result}. seriously, #{link3_result}? i think I'll say hello to #{email_result}. instead.</p>), auto_link(%(<p>Go to #{link3_raw}. seriously, #{link3_raw}? i think I'll say hello to #{email_raw}. instead.</p>))
  30192. assert_equal %(<p>Link #{link4_result}</p>), auto_link("<p>Link #{link4_raw}</p>")
  30193. assert_equal %(<p>#{link4_result} Link</p>), auto_link("<p>#{link4_raw} Link</p>")
  30194. assert_equal %(<p>#{link5_result} Link</p>), auto_link("<p>#{link5_raw} Link</p>")
  30195. assert_equal '', auto_link(nil)
  30196. assert_equal '', auto_link('')
  30197. end
  30198. def test_auto_link_at_eol
  30199. url1 = "http://api.rubyonrails.com/Foo.html"
  30200. url2 = "http://www.ruby-doc.org/core/Bar.html"
  30201. assert_equal %(<p><a href="#{url1}">#{url1}</a><br /><a href="#{url2}">#{url2}</a><br /></p>), auto_link("<p>#{url1}<br />#{url2}<br /></p>")
  30202. end
  30203. def test_auto_link_with_block
  30204. url = "http://api.rubyonrails.com/Foo.html"
  30205. email = "fantabulous@shiznadel.ic"
  30206. assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, 10) }
  30207. end
  30208. def test_sanitize_form
  30209. raw = "<form action=\"/foo/bar\" method=\"post\"><input></form>"
  30210. result = sanitize(raw)
  30211. assert_equal "&lt;form action='/foo/bar' method='post'><input>&lt;/form>", result
  30212. end
  30213. def test_sanitize_script
  30214. raw = "<script language=\"Javascript\">blah blah blah</script>"
  30215. result = sanitize(raw)
  30216. assert_equal "&lt;script language='Javascript'>blah blah blah&lt;/script>", result
  30217. end
  30218. def test_sanitize_js_handlers
  30219. raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
  30220. result = sanitize(raw)
  30221. assert_equal %{onthis="do that" <a name='foo' href='#'>hello</a>}, result
  30222. end
  30223. def test_sanitize_javascript_href
  30224. raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>}
  30225. result = sanitize(raw)
  30226. assert_equal %{href="javascript:bang" <a name='hello'>foo</a>, <span>bar</span>}, result
  30227. end
  30228. def test_cycle_class
  30229. value = Cycle.new("one", 2, "3")
  30230. assert_equal("one", value.to_s)
  30231. assert_equal("2", value.to_s)
  30232. assert_equal("3", value.to_s)
  30233. assert_equal("one", value.to_s)
  30234. value.reset
  30235. assert_equal("one", value.to_s)
  30236. assert_equal("2", value.to_s)
  30237. assert_equal("3", value.to_s)
  30238. end
  30239. def test_cycle_class_with_no_arguments
  30240. assert_raise(ArgumentError) { value = Cycle.new() }
  30241. end
  30242. def test_cycle
  30243. assert_equal("one", cycle("one", 2, "3"))
  30244. assert_equal("2", cycle("one", 2, "3"))
  30245. assert_equal("3", cycle("one", 2, "3"))
  30246. assert_equal("one", cycle("one", 2, "3"))
  30247. assert_equal("2", cycle("one", 2, "3"))
  30248. assert_equal("3", cycle("one", 2, "3"))
  30249. end
  30250. def test_cycle_with_no_arguments
  30251. assert_raise(ArgumentError) { value = cycle() }
  30252. end
  30253. def test_cycle_resets_with_new_values
  30254. assert_equal("even", cycle("even", "odd"))
  30255. assert_equal("odd", cycle("even", "odd"))
  30256. assert_equal("even", cycle("even", "odd"))
  30257. assert_equal("1", cycle(1, 2, 3))
  30258. assert_equal("2", cycle(1, 2, 3))
  30259. assert_equal("3", cycle(1, 2, 3))
  30260. assert_equal("1", cycle(1, 2, 3))
  30261. end
  30262. def test_named_cycles
  30263. assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
  30264. assert_equal("red", cycle("red", "blue", :name => "colors"))
  30265. assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
  30266. assert_equal("blue", cycle("red", "blue", :name => "colors"))
  30267. assert_equal("3", cycle(1, 2, 3, :name => "numbers"))
  30268. assert_equal("red", cycle("red", "blue", :name => "colors"))
  30269. end
  30270. def test_default_named_cycle
  30271. assert_equal("1", cycle(1, 2, 3))
  30272. assert_equal("2", cycle(1, 2, 3, :name => "default"))
  30273. assert_equal("3", cycle(1, 2, 3))
  30274. end
  30275. def test_reset_cycle
  30276. assert_equal("1", cycle(1, 2, 3))
  30277. assert_equal("2", cycle(1, 2, 3))
  30278. reset_cycle
  30279. assert_equal("1", cycle(1, 2, 3))
  30280. end
  30281. def test_reset_unknown_cycle
  30282. reset_cycle("colors")
  30283. end
  30284. def test_recet_named_cycle
  30285. assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
  30286. assert_equal("red", cycle("red", "blue", :name => "colors"))
  30287. reset_cycle("numbers")
  30288. assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
  30289. assert_equal("blue", cycle("red", "blue", :name => "colors"))
  30290. assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
  30291. assert_equal("red", cycle("red", "blue", :name => "colors"))
  30292. end
  30293. def test_cycle_no_instance_variable_clashes
  30294. @cycles = %w{Specialized Fuji Giant}
  30295. assert_equal("red", cycle("red", "blue"))
  30296. assert_equal("blue", cycle("red", "blue"))
  30297. assert_equal("red", cycle("red", "blue"))
  30298. assert_equal(%w{Specialized Fuji Giant}, @cycles)
  30299. end
  30300. def test_strip_tags
  30301. assert_equal("This is a test.", strip_tags("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
  30302. assert_equal("This is a test.", strip_tags("This is a test."))
  30303. assert_equal(
  30304. %{This is a test.\n\n\nIt no longer contains any HTML.\n}, strip_tags(
  30305. %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
  30306. assert_equal("This has a here.", strip_tags("This has a <!-- comment --> here."))
  30307. end
  30308. end
  30309. require File.dirname(__FILE__) + '/../abstract_unit'
  30310. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
  30311. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/asset_tag_helper'
  30312. require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
  30313. RequestMock = Struct.new("Request", :request_uri)
  30314. class UrlHelperTest < Test::Unit::TestCase
  30315. include ActionView::Helpers::AssetTagHelper
  30316. include ActionView::Helpers::UrlHelper
  30317. include ActionView::Helpers::TagHelper
  30318. def setup
  30319. @controller = Class.new do
  30320. attr_accessor :url
  30321. def url_for(options, *parameters_for_method_reference)
  30322. url
  30323. end
  30324. end
  30325. @controller = @controller.new
  30326. @controller.url = "http://www.example.com"
  30327. end
  30328. def test_url_for_escapes_urls
  30329. @controller.url = "http://www.example.com?a=b&c=d"
  30330. assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd')
  30331. assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd', :escape => true)
  30332. assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false)
  30333. end
  30334. # todo: missing test cases
  30335. def test_button_to_with_straight_url
  30336. assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
  30337. end
  30338. def test_button_to_with_query
  30339. assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
  30340. end
  30341. def test_button_to_with_query_and_no_name
  30342. assert_dom_equal "<form method=\"post\" action=\"http://www.example.com?q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"http://www.example.com?q1=v1&amp;q2=v2\" /></div></form>", button_to(nil, "http://www.example.com?q1=v1&q2=v2")
  30343. end
  30344. def test_button_to_with_javascript_confirm
  30345. assert_dom_equal(
  30346. "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input onclick=\"return confirm('Are you sure?');\" type=\"submit\" value=\"Hello\" /></div></form>",
  30347. button_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
  30348. )
  30349. end
  30350. def test_button_to_enabled_disabled
  30351. assert_dom_equal(
  30352. "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>",
  30353. button_to("Hello", "http://www.example.com", :disabled => false)
  30354. )
  30355. assert_dom_equal(
  30356. "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input disabled=\"disabled\" type=\"submit\" value=\"Hello\" /></div></form>",
  30357. button_to("Hello", "http://www.example.com", :disabled => true)
  30358. )
  30359. end
  30360. def test_link_tag_with_straight_url
  30361. assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
  30362. end
  30363. def test_link_tag_with_query
  30364. assert_dom_equal "<a href=\"http://www.example.com?q1=v1&amp;q2=v2\">Hello</a>", link_to("Hello", "http://www.example.com?q1=v1&amp;q2=v2")
  30365. end
  30366. def test_link_tag_with_query_and_no_name
  30367. assert_dom_equal "<a href=\"http://www.example.com?q1=v1&amp;q2=v2\">http://www.example.com?q1=v1&amp;q2=v2</a>", link_to(nil, "http://www.example.com?q1=v1&amp;q2=v2")
  30368. end
  30369. def test_link_tag_with_img
  30370. assert_dom_equal "<a href=\"http://www.example.com\"><img src='/favicon.jpg' /></a>", link_to("<img src='/favicon.jpg' />", "http://www.example.com")
  30371. end
  30372. def test_link_with_nil_html_options
  30373. assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", {:action => 'myaction'}, nil)
  30374. end
  30375. def test_link_tag_with_custom_onclick
  30376. assert_dom_equal "<a href=\"http://www.example.com\" onclick=\"alert('yay!')\">Hello</a>", link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')")
  30377. end
  30378. def test_link_tag_with_javascript_confirm
  30379. assert_dom_equal(
  30380. "<a href=\"http://www.example.com\" onclick=\"return confirm('Are you sure?');\">Hello</a>",
  30381. link_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
  30382. )
  30383. assert_dom_equal(
  30384. "<a href=\"http://www.example.com\" onclick=\"return confirm('You can\\'t possibly be sure, can you?');\">Hello</a>",
  30385. link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure, can you?")
  30386. )
  30387. assert_dom_equal(
  30388. "<a href=\"http://www.example.com\" onclick=\"return confirm('You can\\'t possibly be sure,\\n can you?');\">Hello</a>",
  30389. link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure,\n can you?")
  30390. )
  30391. end
  30392. def test_link_tag_with_popup
  30393. assert_dom_equal(
  30394. "<a href=\"http://www.example.com\" onclick=\"window.open(this.href);return false;\">Hello</a>",
  30395. link_to("Hello", "http://www.example.com", :popup => true)
  30396. )
  30397. assert_dom_equal(
  30398. "<a href=\"http://www.example.com\" onclick=\"window.open(this.href);return false;\">Hello</a>",
  30399. link_to("Hello", "http://www.example.com", :popup => 'true')
  30400. )
  30401. assert_dom_equal(
  30402. "<a href=\"http://www.example.com\" onclick=\"window.open(this.href,'window_name','width=300,height=300');return false;\">Hello</a>",
  30403. link_to("Hello", "http://www.example.com", :popup => ['window_name', 'width=300,height=300'])
  30404. )
  30405. end
  30406. def test_link_tag_with_popup_and_javascript_confirm
  30407. assert_dom_equal(
  30408. "<a href=\"http://www.example.com\" onclick=\"if (confirm('Fo\\' sho\\'?')) { window.open(this.href); };return false;\">Hello</a>",
  30409. link_to("Hello", "http://www.example.com", { :popup => true, :confirm => "Fo' sho'?" })
  30410. )
  30411. assert_dom_equal(
  30412. "<a href=\"http://www.example.com\" onclick=\"if (confirm('Are you serious?')) { window.open(this.href,'window_name','width=300,height=300'); };return false;\">Hello</a>",
  30413. link_to("Hello", "http://www.example.com", { :popup => ['window_name', 'width=300,height=300'], :confirm => "Are you serious?" })
  30414. )
  30415. end
  30416. def test_link_tag_using_post_javascript
  30417. assert_dom_equal(
  30418. "<a href=\"http://www.example.com\" onclick=\"var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();return false;\">Hello</a>",
  30419. link_to("Hello", "http://www.example.com", :post => true)
  30420. )
  30421. end
  30422. def test_link_tag_using_post_javascript_and_confirm
  30423. assert_dom_equal(
  30424. "<a href=\"http://www.example.com\" onclick=\"if (confirm('Are you serious?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;\">Hello</a>",
  30425. link_to("Hello", "http://www.example.com", :post => true, :confirm => "Are you serious?")
  30426. )
  30427. end
  30428. def test_link_tag_using_post_javascript_and_popup
  30429. assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :post => true, :confirm => "Are you serious?") }
  30430. end
  30431. def test_link_to_unless
  30432. assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog")
  30433. assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_unless(false, "Listing", :action => "list", :controller => "weblog")
  30434. assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1)
  30435. assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name, options, html_options, *parameters_for_method_reference|
  30436. "<strong>#{name}</strong>"
  30437. }
  30438. assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name|
  30439. "<strong>#{name}</strong>"
  30440. }
  30441. assert_equal "test", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) {
  30442. "test"
  30443. }
  30444. end
  30445. def test_link_to_if
  30446. assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog")
  30447. assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_if(true, "Listing", :action => "list", :controller => "weblog")
  30448. assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog", :id => 1)
  30449. end
  30450. def xtest_link_unless_current
  30451. @request = RequestMock.new("http://www.example.com")
  30452. assert_equal "Showing", link_to_unless_current("Showing", :action => "show", :controller => "weblog")
  30453. @request = RequestMock.new("http://www.example.org")
  30454. assert "<a href=\"http://www.example.com\">Listing</a>", link_to_unless_current("Listing", :action => "list", :controller => "weblog")
  30455. @request = RequestMock.new("http://www.example.com")
  30456. assert_equal "Showing", link_to_unless_current("Showing", :action => "show", :controller => "weblog", :id => 1)
  30457. end
  30458. def test_mail_to
  30459. assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>", mail_to("david@loudthinking.com")
  30460. assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", mail_to("david@loudthinking.com", "David Heinemeier Hansson")
  30461. assert_dom_equal(
  30462. "<a class=\"admin\" href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>",
  30463. mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin")
  30464. )
  30465. assert_equal mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin"),
  30466. mail_to("david@loudthinking.com", "David Heinemeier Hansson", :class => "admin")
  30467. end
  30468. def test_mail_to_with_javascript
  30469. assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
  30470. end
  30471. def test_mail_with_options
  30472. assert_dom_equal(
  30473. %(<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email">My email</a>),
  30474. mail_to("me@example.com", "My email", :cc => "ccaddress@example.com", :bcc => "bccaddress@example.com", :subject => "This is an example email", :body => "This is the body of the message.")
  30475. )
  30476. end
  30477. def test_mail_to_with_img
  30478. assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), mail_to('feedback@example.com', '<img src="/feedback.png" />')
  30479. end
  30480. def test_mail_to_with_hex
  30481. assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex")
  30482. end
  30483. def test_mail_to_with_replace_options
  30484. assert_dom_equal "<a href=\"mailto:wolfgang@stufenlos.net\">wolfgang(at)stufenlos(dot)net</a>", mail_to("wolfgang@stufenlos.net", nil, :replace_at => "(at)", :replace_dot => "(dot)")
  30485. assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
  30486. assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
  30487. assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
  30488. assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
  30489. end
  30490. end
  30491. module TestingSandbox
  30492. # This whole thing *could* be much simpler, but I don't think Tempfile,
  30493. # popen and others exist on all platforms (like Windows).
  30494. def execute_in_sandbox(code)
  30495. test_name = "#{File.dirname(__FILE__)}/test.#{$$}.rb"
  30496. res_name = "#{File.dirname(__FILE__)}/test.#{$$}.out"
  30497. File.open(test_name, "w+") do |file|
  30498. file.write(<<-CODE)
  30499. $:.unshift "../lib"
  30500. block = Proc.new do
  30501. #{code}
  30502. end
  30503. print block.call
  30504. CODE
  30505. end
  30506. system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
  30507. File.read(res_name)
  30508. ensure
  30509. File.delete(test_name) rescue nil
  30510. File.delete(res_name) rescue nil
  30511. end
  30512. end
  30513. class DirectoryCategory < ActionWebService::Struct
  30514. member :fullViewableName, :string
  30515. member :specialEncoding, :string
  30516. end
  30517. class ResultElement < ActionWebService::Struct
  30518. member :summary, :string
  30519. member :URL, :string
  30520. member :snippet, :string
  30521. member :title, :string
  30522. member :cachedSize, :string
  30523. member :relatedInformationPresent, :bool
  30524. member :hostName, :string
  30525. member :directoryCategory, DirectoryCategory
  30526. member :directoryTitle, :string
  30527. end
  30528. class GoogleSearchResult < ActionWebService::Struct
  30529. member :documentFiltering, :bool
  30530. member :searchComments, :string
  30531. member :estimatedTotalResultsCount, :int
  30532. member :estimateIsExact, :bool
  30533. member :resultElements, [ResultElement]
  30534. member :searchQuery, :string
  30535. member :startIndex, :int
  30536. member :endIndex, :int
  30537. member :searchTips, :string
  30538. member :directoryCategories, [DirectoryCategory]
  30539. member :searchTime, :float
  30540. end
  30541. class GoogleSearchAPI < ActionWebService::API::Base
  30542. inflect_names false
  30543. api_method :doGetCachedPage, :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
  30544. api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]
  30545. api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
  30546. {:key=>:string},
  30547. {:q=>:string},
  30548. {:start=>:int},
  30549. {:maxResults=>:int},
  30550. {:filter=>:bool},
  30551. {:restrict=>:string},
  30552. {:safeSearch=>:bool},
  30553. {:lr=>:string},
  30554. {:ie=>:string},
  30555. {:oe=>:string}
  30556. ]
  30557. end
  30558. class GoogleSearchController < ApplicationController
  30559. wsdl_service_name 'GoogleSearch'
  30560. def doGetCachedPage
  30561. "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
  30562. end
  30563. def doSpellingSuggestion
  30564. "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
  30565. end
  30566. def doGoogleSearch
  30567. resultElement = ResultElement.new
  30568. resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
  30569. resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
  30570. resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
  30571. "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
  30572. resultElement.title = "Teh Railz0r"
  30573. resultElement.cachedSize = "Almost no lines of code!"
  30574. resultElement.relatedInformationPresent = true
  30575. resultElement.hostName = "rubyonrails.com"
  30576. resultElement.directoryCategory = category("Web Development", "UTF-8")
  30577. result = GoogleSearchResult.new
  30578. result.documentFiltering = @params['filter']
  30579. result.searchComments = ""
  30580. result.estimatedTotalResultsCount = 322000
  30581. result.estimateIsExact = false
  30582. result.resultElements = [resultElement]
  30583. result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
  30584. result.startIndex = @params['start']
  30585. result.endIndex = @params['start'] + @params['maxResults']
  30586. result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
  30587. result.searchTime = 0.000001
  30588. # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
  30589. # the Ruby SOAP collapses them into one instance and uses references all over the
  30590. # place, confusing Mono.
  30591. #
  30592. # This has recently been fixed:
  30593. # http://bugzilla.ximian.com/show_bug.cgi?id=72265
  30594. result.directoryCategories = [
  30595. category("Web Development", "UTF-8"),
  30596. category("Programming", "US-ASCII"),
  30597. ]
  30598. result
  30599. end
  30600. private
  30601. def category(name, encoding)
  30602. cat = DirectoryCategory.new
  30603. cat.fullViewableName = name.dup
  30604. cat.specialEncoding = encoding.dup
  30605. cat
  30606. end
  30607. end
  30608. class DirectoryCategory < ActionWebService::Struct
  30609. member :fullViewableName, :string
  30610. member :specialEncoding, :string
  30611. end
  30612. class ResultElement < ActionWebService::Struct
  30613. member :summary, :string
  30614. member :URL, :string
  30615. member :snippet, :string
  30616. member :title, :string
  30617. member :cachedSize, :string
  30618. member :relatedInformationPresent, :bool
  30619. member :hostName, :string
  30620. member :directoryCategory, DirectoryCategory
  30621. member :directoryTitle, :string
  30622. end
  30623. class GoogleSearchResult < ActionWebService::Struct
  30624. member :documentFiltering, :bool
  30625. member :searchComments, :string
  30626. member :estimatedTotalResultsCount, :int
  30627. member :estimateIsExact, :bool
  30628. member :resultElements, [ResultElement]
  30629. member :searchQuery, :string
  30630. member :startIndex, :int
  30631. member :endIndex, :int
  30632. member :searchTips, :string
  30633. member :directoryCategories, [DirectoryCategory]
  30634. member :searchTime, :float
  30635. end
  30636. class GoogleSearchAPI < ActionWebService::API::Base
  30637. inflect_names false
  30638. api_method :doGetCachedPage, :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
  30639. api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]
  30640. api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
  30641. {:key=>:string},
  30642. {:q=>:string},
  30643. {:start=>:int},
  30644. {:maxResults=>:int},
  30645. {:filter=>:bool},
  30646. {:restrict=>:string},
  30647. {:safeSearch=>:bool},
  30648. {:lr=>:string},
  30649. {:ie=>:string},
  30650. {:oe=>:string}
  30651. ]
  30652. end
  30653. class GoogleSearchService < ActionWebService::Base
  30654. web_service_api GoogleSearchAPI
  30655. def doGetCachedPage(key, url)
  30656. "<html><body>i am a cached page</body></html>"
  30657. end
  30658. def doSpellingSuggestion(key, phrase)
  30659. "Did you mean 'teh'?"
  30660. end
  30661. def doGoogleSearch(key, q, start, maxResults, filter, restrict, safeSearch, lr, ie, oe)
  30662. resultElement = ResultElement.new
  30663. resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
  30664. resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
  30665. resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
  30666. "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
  30667. resultElement.title = "Teh Railz0r"
  30668. resultElement.cachedSize = "Almost no lines of code!"
  30669. resultElement.relatedInformationPresent = true
  30670. resultElement.hostName = "rubyonrails.com"
  30671. resultElement.directoryCategory = category("Web Development", "UTF-8")
  30672. result = GoogleSearchResult.new
  30673. result.documentFiltering = filter
  30674. result.searchComments = ""
  30675. result.estimatedTotalResultsCount = 322000
  30676. result.estimateIsExact = false
  30677. result.resultElements = [resultElement]
  30678. result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
  30679. result.startIndex = start
  30680. result.endIndex = start + maxResults
  30681. result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
  30682. result.searchTime = 0.000001
  30683. # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
  30684. # the Ruby SOAP collapses them into one instance and uses references all over the
  30685. # place, confusing Mono.
  30686. #
  30687. # This has recently been fixed:
  30688. # http://bugzilla.ximian.com/show_bug.cgi?id=72265
  30689. result.directoryCategories = [
  30690. category("Web Development", "UTF-8"),
  30691. category("Programming", "US-ASCII"),
  30692. ]
  30693. result
  30694. end
  30695. private
  30696. def category(name, encoding)
  30697. cat = DirectoryCategory.new
  30698. cat.fullViewableName = name.dup
  30699. cat.specialEncoding = encoding.dup
  30700. cat
  30701. end
  30702. end
  30703. require 'google_search_service'
  30704. class SearchController < ApplicationController
  30705. wsdl_service_name 'GoogleSearch'
  30706. web_service_dispatching_mode :delegated
  30707. web_service :beta3, GoogleSearchService.new
  30708. end
  30709. class DirectoryCategory < ActionWebService::Struct
  30710. member :fullViewableName, :string
  30711. member :specialEncoding, :string
  30712. end
  30713. class ResultElement < ActionWebService::Struct
  30714. member :summary, :string
  30715. member :URL, :string
  30716. member :snippet, :string
  30717. member :title, :string
  30718. member :cachedSize, :string
  30719. member :relatedInformationPresent, :bool
  30720. member :hostName, :string
  30721. member :directoryCategory, DirectoryCategory
  30722. member :directoryTitle, :string
  30723. end
  30724. class GoogleSearchResult < ActionWebService::Struct
  30725. member :documentFiltering, :bool
  30726. member :searchComments, :string
  30727. member :estimatedTotalResultsCount, :int
  30728. member :estimateIsExact, :bool
  30729. member :resultElements, [ResultElement]
  30730. member :searchQuery, :string
  30731. member :startIndex, :int
  30732. member :endIndex, :int
  30733. member :searchTips, :string
  30734. member :directoryCategories, [DirectoryCategory]
  30735. member :searchTime, :float
  30736. end
  30737. class GoogleSearchAPI < ActionWebService::API::Base
  30738. inflect_names false
  30739. api_method :doGetCachedPage, :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
  30740. api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]
  30741. api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
  30742. {:key=>:string},
  30743. {:q=>:string},
  30744. {:start=>:int},
  30745. {:maxResults=>:int},
  30746. {:filter=>:bool},
  30747. {:restrict=>:string},
  30748. {:safeSearch=>:bool},
  30749. {:lr=>:string},
  30750. {:ie=>:string},
  30751. {:oe=>:string}
  30752. ]
  30753. end
  30754. class SearchController < ApplicationController
  30755. web_service_api :google_search
  30756. wsdl_service_name 'GoogleSearch'
  30757. def doGetCachedPage
  30758. "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
  30759. end
  30760. def doSpellingSuggestion
  30761. "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
  30762. end
  30763. def doGoogleSearch
  30764. resultElement = ResultElement.new
  30765. resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
  30766. resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
  30767. resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
  30768. "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
  30769. resultElement.title = "Teh Railz0r"
  30770. resultElement.cachedSize = "Almost no lines of code!"
  30771. resultElement.relatedInformationPresent = true
  30772. resultElement.hostName = "rubyonrails.com"
  30773. resultElement.directoryCategory = category("Web Development", "UTF-8")
  30774. result = GoogleSearchResult.new
  30775. result.documentFiltering = @params['filter']
  30776. result.searchComments = ""
  30777. result.estimatedTotalResultsCount = 322000
  30778. result.estimateIsExact = false
  30779. result.resultElements = [resultElement]
  30780. result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
  30781. result.startIndex = @params['start']
  30782. result.endIndex = @params['start'] + @params['maxResults']
  30783. result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
  30784. result.searchTime = 0.000001
  30785. # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
  30786. # the Ruby SOAP collapses them into one instance and uses references all over the
  30787. # place, confusing Mono.
  30788. #
  30789. # This has recently been fixed:
  30790. # http://bugzilla.ximian.com/show_bug.cgi?id=72265
  30791. result.directoryCategories = [
  30792. category("Web Development", "UTF-8"),
  30793. category("Programming", "US-ASCII"),
  30794. ]
  30795. result
  30796. end
  30797. private
  30798. def category(name, encoding)
  30799. cat = DirectoryCategory.new
  30800. cat.fullViewableName = name.dup
  30801. cat.specialEncoding = encoding.dup
  30802. cat
  30803. end
  30804. end
  30805. #
  30806. # see the blogger API spec at http://www.blogger.com/developers/api/1_docs/
  30807. # note that the method signatures are subtly different to metaWeblog, they
  30808. # are not identical. take care to ensure you handle the different semantics
  30809. # properly if you want to support blogger API too, to get maximum compatibility.
  30810. #
  30811. module Blog
  30812. class Blog < ActionWebService::Struct
  30813. member :url, :string
  30814. member :blogid, :string
  30815. member :blogName, :string
  30816. end
  30817. class User < ActionWebService::Struct
  30818. member :nickname, :string
  30819. member :userid, :string
  30820. member :url, :string
  30821. member :email, :string
  30822. member :lastname, :string
  30823. member :firstname, :string
  30824. end
  30825. end
  30826. #
  30827. # blogger
  30828. #
  30829. class BloggerAPI < ActionWebService::API::Base
  30830. inflect_names false
  30831. api_method :newPost, :returns => [:string], :expects => [
  30832. {:appkey=>:string},
  30833. {:blogid=>:string},
  30834. {:username=>:string},
  30835. {:password=>:string},
  30836. {:content=>:string},
  30837. {:publish=>:bool}
  30838. ]
  30839. api_method :editPost, :returns => [:bool], :expects => [
  30840. {:appkey=>:string},
  30841. {:postid=>:string},
  30842. {:username=>:string},
  30843. {:password=>:string},
  30844. {:content=>:string},
  30845. {:publish=>:bool}
  30846. ]
  30847. api_method :getUsersBlogs, :returns => [[Blog::Blog]], :expects => [
  30848. {:appkey=>:string},
  30849. {:username=>:string},
  30850. {:password=>:string}
  30851. ]
  30852. api_method :getUserInfo, :returns => [Blog::User], :expects => [
  30853. {:appkey=>:string},
  30854. {:username=>:string},
  30855. {:password=>:string}
  30856. ]
  30857. end
  30858. require 'blogger_api'
  30859. class BloggerService < ActionWebService::Base
  30860. web_service_api BloggerAPI
  30861. def initialize
  30862. @postid = 0
  30863. end
  30864. def newPost(key, id, user, pw, content, publish)
  30865. $stderr.puts "id=#{id} user=#{user} pw=#{pw}, content=#{content.inspect} [#{publish}]"
  30866. (@postid += 1).to_s
  30867. end
  30868. def editPost(key, post_id, user, pw, content, publish)
  30869. $stderr.puts "id=#{post_id} user=#{user} pw=#{pw} content=#{content.inspect} [#{publish}]"
  30870. true
  30871. end
  30872. def getUsersBlogs(key, user, pw)
  30873. $stderr.puts "getting blogs for #{user}"
  30874. blog = Blog::Blog.new(
  30875. :url =>'http://blog',
  30876. :blogid => 'myblog',
  30877. :blogName => 'My Blog'
  30878. )
  30879. [blog]
  30880. end
  30881. def getUserInfo(key, user, pw)
  30882. $stderr.puts "getting user info for #{user}"
  30883. Blog::User.new(:nickname => 'user', :email => 'user@test.com')
  30884. end
  30885. end
  30886. #
  30887. # here lie structures, cousins of those on http://www.xmlrpc.com/metaWeblog
  30888. # but they don't necessarily the real world reflect
  30889. # so if you do, find that your client complains:
  30890. # please tell, of problems you suffered through
  30891. #
  30892. module Blog
  30893. class Post < ActionWebService::Struct
  30894. member :title, :string
  30895. member :link, :string
  30896. member :description, :string
  30897. member :author, :string
  30898. member :category, :string
  30899. member :comments, :string
  30900. member :guid, :string
  30901. member :pubDate, :string
  30902. end
  30903. class Category < ActionWebService::Struct
  30904. member :description, :string
  30905. member :htmlUrl, :string
  30906. member :rssUrl, :string
  30907. end
  30908. end
  30909. #
  30910. # metaWeblog
  30911. #
  30912. class MetaWeblogAPI < ActionWebService::API::Base
  30913. inflect_names false
  30914. api_method :newPost, :returns => [:string], :expects => [
  30915. {:blogid=>:string},
  30916. {:username=>:string},
  30917. {:password=>:string},
  30918. {:struct=>Blog::Post},
  30919. {:publish=>:bool}
  30920. ]
  30921. api_method :editPost, :returns => [:bool], :expects => [
  30922. {:postid=>:string},
  30923. {:username=>:string},
  30924. {:password=>:string},
  30925. {:struct=>Blog::Post},
  30926. {:publish=>:bool},
  30927. ]
  30928. api_method :getPost, :returns => [Blog::Post], :expects => [
  30929. {:postid=>:string},
  30930. {:username=>:string},
  30931. {:password=>:string},
  30932. ]
  30933. api_method :getCategories, :returns => [[Blog::Category]], :expects => [
  30934. {:blogid=>:string},
  30935. {:username=>:string},
  30936. {:password=>:string},
  30937. ]
  30938. api_method :getRecentPosts, :returns => [[Blog::Post]], :expects => [
  30939. {:blogid=>:string},
  30940. {:username=>:string},
  30941. {:password=>:string},
  30942. {:numberOfPosts=>:int},
  30943. ]
  30944. end
  30945. require 'meta_weblog_api'
  30946. class MetaWeblogService < ActionWebService::Base
  30947. web_service_api MetaWeblogAPI
  30948. def initialize
  30949. @postid = 0
  30950. end
  30951. def newPost(id, user, pw, struct, publish)
  30952. $stderr.puts "id=#{id} user=#{user} pw=#{pw}, struct=#{struct.inspect} [#{publish}]"
  30953. (@postid += 1).to_s
  30954. end
  30955. def editPost(post_id, user, pw, struct, publish)
  30956. $stderr.puts "id=#{post_id} user=#{user} pw=#{pw} struct=#{struct.inspect} [#{publish}]"
  30957. true
  30958. end
  30959. def getPost(post_id, user, pw)
  30960. $stderr.puts "get post #{post_id}"
  30961. Blog::Post.new(:title => 'hello world', :description => 'first post!')
  30962. end
  30963. def getCategories(id, user, pw)
  30964. $stderr.puts "categories for #{user}"
  30965. cat = Blog::Category.new(
  30966. :description => 'Tech',
  30967. :htmlUrl => 'http://blog/tech',
  30968. :rssUrl => 'http://blog/tech.rss')
  30969. [cat]
  30970. end
  30971. def getRecentPosts(id, user, pw, num)
  30972. $stderr.puts "recent #{num} posts for #{user} on blog #{id}"
  30973. post1 = Blog::Post.new(
  30974. :title => 'first post!',
  30975. :link => 'http://blog.xeraph.org/testOne.html',
  30976. :description => 'this is the first post'
  30977. )
  30978. post2 = Blog::Post.new(
  30979. :title => 'second post!',
  30980. :link => 'http://blog.xeraph.org/testTwo.html',
  30981. :description => 'this is the second post'
  30982. )
  30983. [post1, post2]
  30984. end
  30985. end
  30986. #
  30987. # example controller implementing both blogger and metaWeblog APIs
  30988. # in a way that should be compatible with clients supporting both/either.
  30989. #
  30990. # test by pointing your client at http://URL/xmlrpc/api
  30991. #
  30992. require 'meta_weblog_service'
  30993. require 'blogger_service'
  30994. class XmlrpcController < ApplicationController
  30995. web_service_dispatching_mode :layered
  30996. web_service :metaWeblog, MetaWeblogService.new
  30997. web_service :blogger, BloggerService.new
  30998. end
  30999. module ActionWebService # :nodoc:
  31000. module API # :nodoc:
  31001. # A web service API class specifies the methods that will be available for
  31002. # invocation for an API. It also contains metadata such as the method type
  31003. # signature hints.
  31004. #
  31005. # It is not intended to be instantiated.
  31006. #
  31007. # It is attached to web service implementation classes like
  31008. # ActionWebService::Base and ActionController::Base derivatives by using
  31009. # <tt>container.web_service_api</tt>, where <tt>container</tt> is an
  31010. # ActionController::Base or a ActionWebService::Base.
  31011. #
  31012. # See ActionWebService::Container::Direct::ClassMethods for an example
  31013. # of use.
  31014. class Base
  31015. # Action WebService API subclasses should be reloaded by the dispatcher in Rails
  31016. # when Dependencies.mechanism = :load.
  31017. include Reloadable::Subclasses
  31018. # Whether to transform the public API method names into camel-cased names
  31019. class_inheritable_option :inflect_names, true
  31020. # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
  31021. # The default is +false+; you should be aware of the security implications
  31022. # of allowing this, and ensure that you don't allow remote callers to
  31023. # easily overwrite data they should not have access to.
  31024. class_inheritable_option :allow_active_record_expects, false
  31025. # If present, the name of a method to call when the remote caller
  31026. # tried to call a nonexistent method. Semantically equivalent to
  31027. # +method_missing+.
  31028. class_inheritable_option :default_api_method
  31029. # Disallow instantiation
  31030. private_class_method :new, :allocate
  31031. class << self
  31032. include ActionWebService::SignatureTypes
  31033. # API methods have a +name+, which must be the Ruby method name to use when
  31034. # performing the invocation on the web service object.
  31035. #
  31036. # The signatures for the method input parameters and return value can
  31037. # by specified in +options+.
  31038. #
  31039. # A signature is an array of one or more parameter specifiers.
  31040. # A parameter specifier can be one of the following:
  31041. #
  31042. # * A symbol or string representing one of the Action Web Service base types.
  31043. # See ActionWebService::SignatureTypes for a canonical list of the base types.
  31044. # * The Class object of the parameter type
  31045. # * A single-element Array containing one of the two preceding items. This
  31046. # will cause Action Web Service to treat the parameter at that position
  31047. # as an array containing only values of the given type.
  31048. # * A Hash containing as key the name of the parameter, and as value
  31049. # one of the three preceding items
  31050. #
  31051. # If no method input parameter or method return value signatures are given,
  31052. # the method is assumed to take no parameters and/or return no values of
  31053. # interest, and any values that are received by the server will be
  31054. # discarded and ignored.
  31055. #
  31056. # Valid options:
  31057. # [<tt>:expects</tt>] Signature for the method input parameters
  31058. # [<tt>:returns</tt>] Signature for the method return value
  31059. # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
  31060. def api_method(name, options={})
  31061. unless options.is_a?(Hash)
  31062. raise(ActionWebServiceError, "Expected a Hash for options")
  31063. end
  31064. validate_options([:expects, :returns, :expects_and_returns], options.keys)
  31065. if options[:expects_and_returns]
  31066. expects = options[:expects_and_returns]
  31067. returns = options[:expects_and_returns]
  31068. else
  31069. expects = options[:expects]
  31070. returns = options[:returns]
  31071. end
  31072. expects = canonical_signature(expects)
  31073. returns = canonical_signature(returns)
  31074. if expects
  31075. expects.each do |type|
  31076. type = type.element_type if type.is_a?(ArrayType)
  31077. if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
  31078. raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
  31079. end
  31080. end
  31081. end
  31082. name = name.to_sym
  31083. public_name = public_api_method_name(name)
  31084. method = Method.new(name, public_name, expects, returns)
  31085. write_inheritable_hash("api_methods", name => method)
  31086. write_inheritable_hash("api_public_method_names", public_name => name)
  31087. end
  31088. # Whether the given method name is a service method on this API
  31089. def has_api_method?(name)
  31090. api_methods.has_key?(name)
  31091. end
  31092. # Whether the given public method name has a corresponding service method
  31093. # on this API
  31094. def has_public_api_method?(public_name)
  31095. api_public_method_names.has_key?(public_name)
  31096. end
  31097. # The corresponding public method name for the given service method name
  31098. def public_api_method_name(name)
  31099. if inflect_names
  31100. name.to_s.camelize
  31101. else
  31102. name.to_s
  31103. end
  31104. end
  31105. # The corresponding service method name for the given public method name
  31106. def api_method_name(public_name)
  31107. api_public_method_names[public_name]
  31108. end
  31109. # A Hash containing all service methods on this API, and their
  31110. # associated metadata.
  31111. def api_methods
  31112. read_inheritable_attribute("api_methods") || {}
  31113. end
  31114. # The Method instance for the given public API method name, if any
  31115. def public_api_method_instance(public_method_name)
  31116. api_method_instance(api_method_name(public_method_name))
  31117. end
  31118. # The Method instance for the given API method name, if any
  31119. def api_method_instance(method_name)
  31120. api_methods[method_name]
  31121. end
  31122. # The Method instance for the default API method, if any
  31123. def default_api_method_instance
  31124. return nil unless name = default_api_method
  31125. instance = read_inheritable_attribute("default_api_method_instance")
  31126. if instance && instance.name == name
  31127. return instance
  31128. end
  31129. instance = Method.new(name, public_api_method_name(name), nil, nil)
  31130. write_inheritable_attribute("default_api_method_instance", instance)
  31131. instance
  31132. end
  31133. private
  31134. def api_public_method_names
  31135. read_inheritable_attribute("api_public_method_names") || {}
  31136. end
  31137. def validate_options(valid_option_keys, supplied_option_keys)
  31138. unknown_option_keys = supplied_option_keys - valid_option_keys
  31139. unless unknown_option_keys.empty?
  31140. raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
  31141. end
  31142. end
  31143. end
  31144. end
  31145. # Represents an API method and its associated metadata, and provides functionality
  31146. # to assist in commonly performed API method tasks.
  31147. class Method
  31148. attr :name
  31149. attr :public_name
  31150. attr :expects
  31151. attr :returns
  31152. def initialize(name, public_name, expects, returns)
  31153. @name = name
  31154. @public_name = public_name
  31155. @expects = expects
  31156. @returns = returns
  31157. @caster = ActionWebService::Casting::BaseCaster.new(self)
  31158. end
  31159. # The list of parameter names for this method
  31160. def param_names
  31161. return [] unless @expects
  31162. @expects.map{ |type| type.name }
  31163. end
  31164. # Casts a set of Ruby values into the expected Ruby values
  31165. def cast_expects(params)
  31166. @caster.cast_expects(params)
  31167. end
  31168. # Cast a Ruby return value into the expected Ruby value
  31169. def cast_returns(return_value)
  31170. @caster.cast_returns(return_value)
  31171. end
  31172. # Returns the index of the first expected parameter
  31173. # with the given name
  31174. def expects_index_of(param_name)
  31175. return -1 if @expects.nil?
  31176. (0..(@expects.length-1)).each do |i|
  31177. return i if @expects[i].name.to_s == param_name.to_s
  31178. end
  31179. -1
  31180. end
  31181. # Returns a hash keyed by parameter name for the given
  31182. # parameter list
  31183. def expects_to_hash(params)
  31184. return {} if @expects.nil?
  31185. h = {}
  31186. @expects.zip(params){ |type, param| h[type.name] = param }
  31187. h
  31188. end
  31189. # Backwards compatibility with previous API
  31190. def [](sig_type)
  31191. case sig_type
  31192. when :expects
  31193. @expects.map{|x| compat_signature_entry(x)}
  31194. when :returns
  31195. @returns.map{|x| compat_signature_entry(x)}
  31196. end
  31197. end
  31198. # String representation of this method
  31199. def to_s
  31200. fqn = ""
  31201. fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
  31202. fqn << "#{@public_name}("
  31203. fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
  31204. fqn << ")"
  31205. fqn
  31206. end
  31207. private
  31208. def compat_signature_entry(entry)
  31209. if entry.array?
  31210. [compat_signature_entry(entry.element_type)]
  31211. else
  31212. if entry.spec.is_a?(Hash)
  31213. {entry.spec.keys.first => entry.type_class}
  31214. else
  31215. entry.type_class
  31216. end
  31217. end
  31218. end
  31219. end
  31220. end
  31221. end
  31222. module ActionWebService # :nodoc:
  31223. class ActionWebServiceError < StandardError # :nodoc:
  31224. end
  31225. # An Action Web Service object implements a specified API.
  31226. #
  31227. # Used by controllers operating in _Delegated_ dispatching mode.
  31228. #
  31229. # ==== Example
  31230. #
  31231. # class PersonService < ActionWebService::Base
  31232. # web_service_api PersonAPI
  31233. #
  31234. # def find_person(criteria)
  31235. # Person.find_all [...]
  31236. # end
  31237. #
  31238. # def delete_person(id)
  31239. # Person.find_by_id(id).destroy
  31240. # end
  31241. # end
  31242. #
  31243. # class PersonAPI < ActionWebService::API::Base
  31244. # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]]
  31245. # api_method :delete_person, :expects => [:int]
  31246. # end
  31247. #
  31248. # class SearchCriteria < ActionWebService::Struct
  31249. # member :firstname, :string
  31250. # member :lastname, :string
  31251. # member :email, :string
  31252. # end
  31253. class Base
  31254. # Action WebService subclasses should be reloaded by the dispatcher in Rails
  31255. # when Dependencies.mechanism = :load.
  31256. include Reloadable::Subclasses
  31257. # Whether to report exceptions back to the caller in the protocol's exception
  31258. # format
  31259. class_inheritable_option :web_service_exception_reporting, true
  31260. end
  31261. end
  31262. require 'time'
  31263. require 'date'
  31264. require 'xmlrpc/datetime'
  31265. module ActionWebService # :nodoc:
  31266. module Casting # :nodoc:
  31267. class CastingError < ActionWebServiceError # :nodoc:
  31268. end
  31269. # Performs casting of arbitrary values into the correct types for the signature
  31270. class BaseCaster # :nodoc:
  31271. def initialize(api_method)
  31272. @api_method = api_method
  31273. end
  31274. # Coerces the parameters in +params+ (an Enumerable) into the types
  31275. # this method expects
  31276. def cast_expects(params)
  31277. self.class.cast_expects(@api_method, params)
  31278. end
  31279. # Coerces the given +return_value+ into the type returned by this
  31280. # method
  31281. def cast_returns(return_value)
  31282. self.class.cast_returns(@api_method, return_value)
  31283. end
  31284. class << self
  31285. include ActionWebService::SignatureTypes
  31286. def cast_expects(api_method, params) # :nodoc:
  31287. return [] if api_method.expects.nil?
  31288. api_method.expects.zip(params).map{ |type, param| cast(param, type) }
  31289. end
  31290. def cast_returns(api_method, return_value) # :nodoc:
  31291. return nil if api_method.returns.nil?
  31292. cast(return_value, api_method.returns[0])
  31293. end
  31294. def cast(value, signature_type) # :nodoc:
  31295. return value if signature_type.nil? # signature.length != params.length
  31296. return nil if value.nil?
  31297. # XMLRPC protocol doesn't support nil values. It uses false instead.
  31298. # It should never happen for SOAP.
  31299. if signature_type.structured? && value.equal?(false)
  31300. return nil
  31301. end
  31302. unless signature_type.array? || signature_type.structured?
  31303. return value if canonical_type(value.class) == signature_type.type
  31304. end
  31305. if signature_type.array?
  31306. unless value.respond_to?(:entries) && !value.is_a?(String)
  31307. raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
  31308. end
  31309. value.entries.map do |entry|
  31310. cast(entry, signature_type.element_type)
  31311. end
  31312. elsif signature_type.structured?
  31313. cast_to_structured_type(value, signature_type)
  31314. elsif !signature_type.custom?
  31315. cast_base_type(value, signature_type)
  31316. end
  31317. end
  31318. def cast_base_type(value, signature_type) # :nodoc:
  31319. # This is a work-around for the fact that XML-RPC special-cases DateTime values into its own DateTime type
  31320. # in order to support iso8601 dates. This doesn't work too well for us, so we'll convert it into a Time,
  31321. # with the caveat that we won't be able to handle pre-1970 dates that are sent to us.
  31322. #
  31323. # See http://dev.rubyonrails.com/ticket/2516
  31324. value = value.to_time if value.is_a?(XMLRPC::DateTime)
  31325. case signature_type.type
  31326. when :int
  31327. Integer(value)
  31328. when :string
  31329. value.to_s
  31330. when :base64
  31331. if value.is_a?(ActionWebService::Base64)
  31332. value
  31333. else
  31334. ActionWebService::Base64.new(value.to_s)
  31335. end
  31336. when :bool
  31337. return false if value.nil?
  31338. return value if value == true || value == false
  31339. case value.to_s.downcase
  31340. when '1', 'true', 'y', 'yes'
  31341. true
  31342. when '0', 'false', 'n', 'no'
  31343. false
  31344. else
  31345. raise CastingError, "Don't know how to cast #{value.class} into Boolean"
  31346. end
  31347. when :float
  31348. Float(value)
  31349. when :time
  31350. value = "#{value['2']}/#{value['3']}/#{value['1']} #{value['4']}:#{value['5']}:#{value['6']}" if value.kind_of?(Hash)
  31351. Time.parse(value.to_s)
  31352. when :date
  31353. value = "#{value['2']}/#{value['3']}/#{value['1']}" if value.kind_of?(Hash)
  31354. Date.parse(value.to_s)
  31355. when :datetime
  31356. value = "#{value['2']}/#{value['3']}/#{value['1']} #{value['4']}:#{value['5']}:#{value['6']}" if value.kind_of?(Hash)
  31357. DateTime.parse(value.to_s)
  31358. end
  31359. end
  31360. def cast_to_structured_type(value, signature_type) # :nodoc:
  31361. obj = nil
  31362. obj = value if canonical_type(value.class) == canonical_type(signature_type.type)
  31363. obj ||= signature_type.type_class.new
  31364. if value.respond_to?(:each_pair)
  31365. klass = signature_type.type_class
  31366. value.each_pair do |name, val|
  31367. type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
  31368. val = cast(val, type) if type
  31369. # See http://dev.rubyonrails.com/ticket/3567
  31370. val = val.to_time if val.is_a?(XMLRPC::DateTime)
  31371. obj.__send__("#{name}=", val) if obj.respond_to?(name)
  31372. end
  31373. elsif value.respond_to?(:attributes)
  31374. signature_type.each_member do |name, type|
  31375. val = value.__send__(name)
  31376. obj.__send__("#{name}=", cast(val, type)) if obj.respond_to?(name)
  31377. end
  31378. else
  31379. raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
  31380. end
  31381. obj
  31382. end
  31383. end
  31384. end
  31385. end
  31386. end
  31387. module ActionWebService # :nodoc:
  31388. module Client # :nodoc:
  31389. class ClientError < StandardError # :nodoc:
  31390. end
  31391. class Base # :nodoc:
  31392. def initialize(api, endpoint_uri)
  31393. @api = api
  31394. @endpoint_uri = endpoint_uri
  31395. end
  31396. def method_missing(name, *args) # :nodoc:
  31397. call_name = method_name(name)
  31398. return super(name, *args) if call_name.nil?
  31399. self.perform_invocation(call_name, args)
  31400. end
  31401. private
  31402. def method_name(name)
  31403. if @api.has_api_method?(name.to_sym)
  31404. name.to_s
  31405. elsif @api.has_public_api_method?(name.to_s)
  31406. @api.api_method_name(name.to_s).to_s
  31407. end
  31408. end
  31409. end
  31410. end
  31411. end
  31412. require 'soap/rpc/driver'
  31413. require 'uri'
  31414. module ActionWebService # :nodoc:
  31415. module Client # :nodoc:
  31416. # Implements SOAP client support (using RPC encoding for the messages).
  31417. #
  31418. # ==== Example Usage
  31419. #
  31420. # class PersonAPI < ActionWebService::API::Base
  31421. # api_method :find_all, :returns => [[Person]]
  31422. # end
  31423. #
  31424. # soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
  31425. # persons = soap_client.find_all
  31426. #
  31427. class Soap < Base
  31428. # Creates a new web service client using the SOAP RPC protocol.
  31429. #
  31430. # +api+ must be an ActionWebService::API::Base derivative, and
  31431. # +endpoint_uri+ must point at the relevant URL to which protocol requests
  31432. # will be sent with HTTP POST.
  31433. #
  31434. # Valid options:
  31435. # [<tt>:namespace</tt>] If the remote server has used a custom namespace to
  31436. # declare its custom types, you can specify it here. This would
  31437. # be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute
  31438. # in .NET, for example.
  31439. # [<tt>:driver_options</tt>] If you want to supply any custom SOAP RPC driver
  31440. # options, you can provide them as a Hash here
  31441. #
  31442. # The <tt>:driver_options</tt> option can be used to configure the backend SOAP
  31443. # RPC driver. An example of configuring the SOAP backend to do
  31444. # client-certificate authenticated SSL connections to the server:
  31445. #
  31446. # opts = {}
  31447. # opts['protocol.http.ssl_config.verify_mode'] = 'OpenSSL::SSL::VERIFY_PEER'
  31448. # opts['protocol.http.ssl_config.client_cert'] = client_cert_file_path
  31449. # opts['protocol.http.ssl_config.client_key'] = client_key_file_path
  31450. # opts['protocol.http.ssl_config.ca_file'] = ca_cert_file_path
  31451. # client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts)
  31452. def initialize(api, endpoint_uri, options={})
  31453. super(api, endpoint_uri)
  31454. @namespace = options[:namespace] || 'urn:ActionWebService'
  31455. @driver_options = options[:driver_options] || {}
  31456. @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace
  31457. @soap_action_base = options[:soap_action_base]
  31458. @soap_action_base ||= URI.parse(endpoint_uri).path
  31459. @driver = create_soap_rpc_driver(api, endpoint_uri)
  31460. @driver_options.each do |name, value|
  31461. @driver.options[name.to_s] = value
  31462. end
  31463. end
  31464. protected
  31465. def perform_invocation(method_name, args)
  31466. method = @api.api_methods[method_name.to_sym]
  31467. args = method.cast_expects(args.dup) rescue args
  31468. return_value = @driver.send(method_name, *args)
  31469. method.cast_returns(return_value.dup) rescue return_value
  31470. end
  31471. def soap_action(method_name)
  31472. "#{@soap_action_base}/#{method_name}"
  31473. end
  31474. private
  31475. def create_soap_rpc_driver(api, endpoint_uri)
  31476. @protocol.register_api(api)
  31477. driver = SoapDriver.new(endpoint_uri, nil)
  31478. driver.mapping_registry = @protocol.marshaler.registry
  31479. api.api_methods.each do |name, method|
  31480. qname = XSD::QName.new(@namespace, method.public_name)
  31481. action = soap_action(method.public_name)
  31482. expects = method.expects
  31483. returns = method.returns
  31484. param_def = []
  31485. if expects
  31486. expects.each do |type|
  31487. type_binding = @protocol.marshaler.lookup_type(type)
  31488. if SOAP::Version >= "1.5.5"
  31489. param_def << ['in', type.name.to_s, [type_binding.type.type_class.to_s]]
  31490. else
  31491. param_def << ['in', type.name, type_binding.mapping]
  31492. end
  31493. end
  31494. end
  31495. if returns
  31496. type_binding = @protocol.marshaler.lookup_type(returns[0])
  31497. if SOAP::Version >= "1.5.5"
  31498. param_def << ['retval', 'return', [type_binding.type.type_class.to_s]]
  31499. else
  31500. param_def << ['retval', 'return', type_binding.mapping]
  31501. end
  31502. end
  31503. driver.add_method(qname, action, method.name.to_s, param_def)
  31504. end
  31505. driver
  31506. end
  31507. class SoapDriver < SOAP::RPC::Driver # :nodoc:
  31508. def add_method(qname, soapaction, name, param_def)
  31509. @proxy.add_rpc_method(qname, soapaction, name, param_def)
  31510. add_rpc_method_interface(name, param_def)
  31511. end
  31512. end
  31513. end
  31514. end
  31515. end
  31516. require 'uri'
  31517. require 'xmlrpc/client'
  31518. module ActionWebService # :nodoc:
  31519. module Client # :nodoc:
  31520. # Implements XML-RPC client support
  31521. #
  31522. # ==== Example Usage
  31523. #
  31524. # class BloggerAPI < ActionWebService::API::Base
  31525. # inflect_names false
  31526. # api_method :getRecentPosts, :returns => [[Blog::Post]]
  31527. # end
  31528. #
  31529. # blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
  31530. # posts = blog.getRecentPosts
  31531. class XmlRpc < Base
  31532. # Creates a new web service client using the XML-RPC protocol.
  31533. #
  31534. # +api+ must be an ActionWebService::API::Base derivative, and
  31535. # +endpoint_uri+ must point at the relevant URL to which protocol requests
  31536. # will be sent with HTTP POST.
  31537. #
  31538. # Valid options:
  31539. # [<tt>:handler_name</tt>] If the remote server defines its services inside special
  31540. # handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example),
  31541. # provide it here, or your method calls will fail
  31542. def initialize(api, endpoint_uri, options={})
  31543. @api = api
  31544. @handler_name = options[:handler_name]
  31545. @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
  31546. @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
  31547. end
  31548. protected
  31549. def perform_invocation(method_name, args)
  31550. method = @api.api_methods[method_name.to_sym]
  31551. if method.expects && method.expects.length != args.length
  31552. raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
  31553. end
  31554. args = method.cast_expects(args.dup) rescue args
  31555. if method.expects
  31556. method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
  31557. end
  31558. ok, return_value = @client.call2(public_name(method_name), *args)
  31559. return (method.cast_returns(return_value.dup) rescue return_value) if ok
  31560. raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
  31561. end
  31562. def public_name(method_name)
  31563. public_name = @api.public_api_method_name(method_name)
  31564. @handler_name ? "#{@handler_name}.#{public_name}" : public_name
  31565. end
  31566. end
  31567. end
  31568. end
  31569. require 'action_web_service/client/base'
  31570. require 'action_web_service/client/soap_client'
  31571. require 'action_web_service/client/xmlrpc_client'
  31572. module ActionWebService # :nodoc:
  31573. module Container # :nodoc:
  31574. module ActionController # :nodoc:
  31575. def self.append_features(base) # :nodoc:
  31576. class << base
  31577. include ClassMethods
  31578. alias_method :inherited_without_api, :inherited
  31579. alias_method :inherited, :inherited_with_api
  31580. alias_method :web_service_api_without_require, :web_service_api
  31581. alias_method :web_service_api, :web_service_api_with_require
  31582. end
  31583. end
  31584. module ClassMethods
  31585. # Creates a client for accessing remote web services, using the
  31586. # given +protocol+ to communicate with the +endpoint_uri+.
  31587. #
  31588. # ==== Example
  31589. #
  31590. # class MyController < ActionController::Base
  31591. # web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger'
  31592. # end
  31593. #
  31594. # In this example, a protected method named <tt>blogger</tt> will
  31595. # now exist on the controller, and calling it will return the
  31596. # XML-RPC client object for working with that remote service.
  31597. #
  31598. # +options+ is the set of protocol client specific options (see
  31599. # a protocol client class for details).
  31600. #
  31601. # If your API definition does not exist on the load path with the
  31602. # correct rules for it to be found using +name+, you can pass in
  31603. # the API definition class via +options+, using a key of <tt>:api</tt>
  31604. def web_client_api(name, protocol, endpoint_uri, options={})
  31605. unless method_defined?(name)
  31606. api_klass = options.delete(:api) || require_web_service_api(name)
  31607. class_eval do
  31608. define_method(name) do
  31609. create_web_service_client(api_klass, protocol, endpoint_uri, options)
  31610. end
  31611. protected name
  31612. end
  31613. end
  31614. end
  31615. def web_service_api_with_require(definition=nil) # :nodoc:
  31616. return web_service_api_without_require if definition.nil?
  31617. case definition
  31618. when String, Symbol
  31619. klass = require_web_service_api(definition)
  31620. else
  31621. klass = definition
  31622. end
  31623. web_service_api_without_require(klass)
  31624. end
  31625. def require_web_service_api(name) # :nodoc:
  31626. case name
  31627. when String, Symbol
  31628. file_name = name.to_s.underscore + "_api"
  31629. class_name = file_name.camelize
  31630. class_names = [class_name, class_name.sub(/Api$/, 'API')]
  31631. begin
  31632. require_dependency(file_name)
  31633. rescue LoadError => load_error
  31634. requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
  31635. msg = requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
  31636. raise LoadError.new(msg).copy_blame!(load_error)
  31637. end
  31638. klass = nil
  31639. class_names.each do |name|
  31640. klass = name.constantize rescue nil
  31641. break unless klass.nil?
  31642. end
  31643. unless klass
  31644. raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
  31645. end
  31646. klass
  31647. else
  31648. raise(ArgumentError, "expected String or Symbol argument")
  31649. end
  31650. end
  31651. private
  31652. def inherited_with_api(child)
  31653. inherited_without_api(child)
  31654. begin child.web_service_api(child.controller_path)
  31655. rescue MissingSourceFile => e
  31656. raise unless e.is_missing?("apis/#{child.controller_path}_api")
  31657. end
  31658. end
  31659. end
  31660. end
  31661. end
  31662. end
  31663. module ActionWebService # :nodoc:
  31664. module Container # :nodoc:
  31665. module Delegated # :nodoc:
  31666. class ContainerError < ActionWebServiceError # :nodoc:
  31667. end
  31668. def self.append_features(base) # :nodoc:
  31669. super
  31670. base.extend(ClassMethods)
  31671. base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
  31672. end
  31673. module ClassMethods
  31674. # Declares a web service that will provide access to the API of the given
  31675. # +object+. +object+ must be an ActionWebService::Base derivative.
  31676. #
  31677. # Web service object creation can either be _immediate_, where the object
  31678. # instance is given at class definition time, or _deferred_, where
  31679. # object instantiation is delayed until request time.
  31680. #
  31681. # ==== Immediate web service object example
  31682. #
  31683. # class ApiController < ApplicationController
  31684. # web_service_dispatching_mode :delegated
  31685. #
  31686. # web_service :person, PersonService.new
  31687. # end
  31688. #
  31689. # For deferred instantiation, a block should be given instead of an
  31690. # object instance. This block will be executed in controller instance
  31691. # context, so it can rely on controller instance variables being present.
  31692. #
  31693. # ==== Deferred web service object example
  31694. #
  31695. # class ApiController < ApplicationController
  31696. # web_service_dispatching_mode :delegated
  31697. #
  31698. # web_service(:person) { PersonService.new(request.env) }
  31699. # end
  31700. def web_service(name, object=nil, &block)
  31701. if (object && block_given?) || (object.nil? && block.nil?)
  31702. raise(ContainerError, "either service, or a block must be given")
  31703. end
  31704. name = name.to_sym
  31705. if block_given?
  31706. info = { name => { :block => block } }
  31707. else
  31708. info = { name => { :object => object } }
  31709. end
  31710. write_inheritable_hash("web_services", info)
  31711. call_web_service_definition_callbacks(self, name, info)
  31712. end
  31713. # Whether this service contains a service with the given +name+
  31714. def has_web_service?(name)
  31715. web_services.has_key?(name.to_sym)
  31716. end
  31717. def web_services # :nodoc:
  31718. read_inheritable_attribute("web_services") || {}
  31719. end
  31720. def add_web_service_definition_callback(&block) # :nodoc:
  31721. write_inheritable_array("web_service_definition_callbacks", [block])
  31722. end
  31723. private
  31724. def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
  31725. (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
  31726. block.call(container_class, web_service_name, service_info)
  31727. end
  31728. end
  31729. end
  31730. module InstanceMethods # :nodoc:
  31731. def web_service_object(web_service_name)
  31732. info = self.class.web_services[web_service_name.to_sym]
  31733. unless info
  31734. raise(ContainerError, "no such web service '#{web_service_name}'")
  31735. end
  31736. service = info[:block]
  31737. service ? self.instance_eval(&service) : info[:object]
  31738. end
  31739. end
  31740. end
  31741. end
  31742. end
  31743. module ActionWebService # :nodoc:
  31744. module Container # :nodoc:
  31745. module Direct # :nodoc:
  31746. class ContainerError < ActionWebServiceError # :nodoc:
  31747. end
  31748. def self.append_features(base) # :nodoc:
  31749. super
  31750. base.extend(ClassMethods)
  31751. end
  31752. module ClassMethods
  31753. # Attaches ActionWebService API +definition+ to the calling class.
  31754. #
  31755. # Action Controllers can have a default associated API, removing the need
  31756. # to call this method if you follow the Action Web Service naming conventions.
  31757. #
  31758. # A controller with a class name of GoogleSearchController will
  31759. # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
  31760. # API definition class to be named <tt>GoogleSearchAPI</tt> or
  31761. # <tt>GoogleSearchApi</tt>.
  31762. #
  31763. # ==== Service class example
  31764. #
  31765. # class MyService < ActionWebService::Base
  31766. # web_service_api MyAPI
  31767. # end
  31768. #
  31769. # class MyAPI < ActionWebService::API::Base
  31770. # ...
  31771. # end
  31772. #
  31773. # ==== Controller class example
  31774. #
  31775. # class MyController < ActionController::Base
  31776. # web_service_api MyAPI
  31777. # end
  31778. #
  31779. # class MyAPI < ActionWebService::API::Base
  31780. # ...
  31781. # end
  31782. def web_service_api(definition=nil)
  31783. if definition.nil?
  31784. read_inheritable_attribute("web_service_api")
  31785. else
  31786. if definition.is_a?(Symbol)
  31787. raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
  31788. end
  31789. unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
  31790. raise(ContainerError, "#{definition.to_s} is not a valid API definition")
  31791. end
  31792. write_inheritable_attribute("web_service_api", definition)
  31793. call_web_service_api_callbacks(self, definition)
  31794. end
  31795. end
  31796. def add_web_service_api_callback(&block) # :nodoc:
  31797. write_inheritable_array("web_service_api_callbacks", [block])
  31798. end
  31799. private
  31800. def call_web_service_api_callbacks(container_class, definition)
  31801. (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
  31802. block.call(container_class, definition)
  31803. end
  31804. end
  31805. end
  31806. end
  31807. end
  31808. end
  31809. require 'action_web_service/container/direct_container'
  31810. require 'action_web_service/container/delegated_container'
  31811. require 'action_web_service/container/action_controller_container'
  31812. require 'benchmark'
  31813. module ActionWebService # :nodoc:
  31814. module Dispatcher # :nodoc:
  31815. class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
  31816. end
  31817. def self.append_features(base) # :nodoc:
  31818. super
  31819. base.class_inheritable_option(:web_service_dispatching_mode, :direct)
  31820. base.class_inheritable_option(:web_service_exception_reporting, true)
  31821. base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
  31822. end
  31823. module InstanceMethods # :nodoc:
  31824. private
  31825. def invoke_web_service_request(protocol_request)
  31826. invocation = web_service_invocation(protocol_request)
  31827. if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
  31828. xmlrpc_multicall_invoke(invocation)
  31829. else
  31830. web_service_invoke(invocation)
  31831. end
  31832. end
  31833. def web_service_direct_invoke(invocation)
  31834. @method_params = invocation.method_ordered_params
  31835. arity = method(invocation.api_method.name).arity rescue 0
  31836. if arity < 0 || arity > 0
  31837. params = @method_params
  31838. else
  31839. params = []
  31840. end
  31841. web_service_filtered_invoke(invocation, params)
  31842. end
  31843. def web_service_delegated_invoke(invocation)
  31844. web_service_filtered_invoke(invocation, invocation.method_ordered_params)
  31845. end
  31846. def web_service_filtered_invoke(invocation, params)
  31847. cancellation_reason = nil
  31848. return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
  31849. cancellation_reason = x
  31850. end
  31851. if cancellation_reason
  31852. raise(DispatcherError, "request canceled: #{cancellation_reason}")
  31853. end
  31854. return_value
  31855. end
  31856. def web_service_invoke(invocation)
  31857. case web_service_dispatching_mode
  31858. when :direct
  31859. return_value = web_service_direct_invoke(invocation)
  31860. when :delegated, :layered
  31861. return_value = web_service_delegated_invoke(invocation)
  31862. end
  31863. web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
  31864. end
  31865. def xmlrpc_multicall_invoke(invocations)
  31866. responses = []
  31867. invocations.each do |invocation|
  31868. if invocation.is_a?(Hash)
  31869. responses << invocation
  31870. next
  31871. end
  31872. begin
  31873. case web_service_dispatching_mode
  31874. when :direct
  31875. return_value = web_service_direct_invoke(invocation)
  31876. when :delegated, :layered
  31877. return_value = web_service_delegated_invoke(invocation)
  31878. end
  31879. api_method = invocation.api_method
  31880. if invocation.api.has_api_method?(api_method.name)
  31881. return_value = api_method.cast_returns(return_value)
  31882. end
  31883. responses << [return_value]
  31884. rescue Exception => e
  31885. responses << { 'faultCode' => 3, 'faultString' => e.message }
  31886. end
  31887. end
  31888. invocation = invocations[0]
  31889. invocation.protocol.encode_response('system.multicall', responses, nil, invocation.protocol_options)
  31890. end
  31891. def web_service_invocation(request, level = 0)
  31892. public_method_name = request.method_name
  31893. invocation = Invocation.new
  31894. invocation.protocol = request.protocol
  31895. invocation.protocol_options = request.protocol_options
  31896. invocation.service_name = request.service_name
  31897. if web_service_dispatching_mode == :layered
  31898. case invocation.protocol
  31899. when Protocol::Soap::SoapProtocol
  31900. soap_action = request.protocol_options[:soap_action]
  31901. if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
  31902. invocation.service_name = $1
  31903. end
  31904. when Protocol::XmlRpc::XmlRpcProtocol
  31905. if request.method_name =~ /^([^\.]+)\.(.*)$/
  31906. public_method_name = $2
  31907. invocation.service_name = $1
  31908. end
  31909. end
  31910. end
  31911. if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
  31912. if public_method_name == 'multicall' && invocation.service_name == 'system'
  31913. if level > 0
  31914. raise(DispatcherError, "Recursive system.multicall invocations not allowed")
  31915. end
  31916. multicall = request.method_params.dup
  31917. unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
  31918. raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
  31919. end
  31920. multicall = multicall[0]
  31921. return multicall.map do |item|
  31922. raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
  31923. raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
  31924. method_name = item['methodName']
  31925. params = item.has_key?('params') ? item['params'] : []
  31926. multicall_request = request.dup
  31927. multicall_request.method_name = method_name
  31928. multicall_request.method_params = params
  31929. begin
  31930. web_service_invocation(multicall_request, level + 1)
  31931. rescue Exception => e
  31932. {'faultCode' => 4, 'faultMessage' => e.message}
  31933. end
  31934. end
  31935. end
  31936. end
  31937. case web_service_dispatching_mode
  31938. when :direct
  31939. invocation.api = self.class.web_service_api
  31940. invocation.service = self
  31941. when :delegated, :layered
  31942. invocation.service = web_service_object(invocation.service_name)
  31943. invocation.api = invocation.service.class.web_service_api
  31944. end
  31945. if invocation.api.nil?
  31946. raise(DispatcherError, "no API attached to #{invocation.service.class}")
  31947. end
  31948. invocation.protocol.register_api(invocation.api)
  31949. request.api = invocation.api
  31950. if invocation.api.has_public_api_method?(public_method_name)
  31951. invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
  31952. else
  31953. if invocation.api.default_api_method.nil?
  31954. raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
  31955. else
  31956. invocation.api_method = invocation.api.default_api_method_instance
  31957. end
  31958. end
  31959. if invocation.service.nil?
  31960. raise(DispatcherError, "no service available for service name #{invocation.service_name}")
  31961. end
  31962. unless invocation.service.respond_to?(invocation.api_method.name)
  31963. raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
  31964. end
  31965. request.api_method = invocation.api_method
  31966. begin
  31967. invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
  31968. rescue
  31969. logger.warn "Casting of method parameters failed" unless logger.nil?
  31970. invocation.method_ordered_params = request.method_params
  31971. end
  31972. request.method_params = invocation.method_ordered_params
  31973. invocation.method_named_params = {}
  31974. invocation.api_method.param_names.inject(0) do |m, n|
  31975. invocation.method_named_params[n] = invocation.method_ordered_params[m]
  31976. m + 1
  31977. end
  31978. invocation
  31979. end
  31980. def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
  31981. if api.has_api_method?(api_method.name)
  31982. return_type = api_method.returns ? api_method.returns[0] : nil
  31983. return_value = api_method.cast_returns(return_value)
  31984. else
  31985. return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
  31986. end
  31987. protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
  31988. end
  31989. class Invocation # :nodoc:
  31990. attr_accessor :protocol
  31991. attr_accessor :protocol_options
  31992. attr_accessor :service_name
  31993. attr_accessor :api
  31994. attr_accessor :api_method
  31995. attr_accessor :method_ordered_params
  31996. attr_accessor :method_named_params
  31997. attr_accessor :service
  31998. end
  31999. end
  32000. end
  32001. end
  32002. require 'benchmark'
  32003. require 'builder/xmlmarkup'
  32004. module ActionWebService # :nodoc:
  32005. module Dispatcher # :nodoc:
  32006. module ActionController # :nodoc:
  32007. def self.append_features(base) # :nodoc:
  32008. super
  32009. class << base
  32010. include ClassMethods
  32011. alias_method :inherited_without_action_controller, :inherited
  32012. alias_method :inherited, :inherited_with_action_controller
  32013. end
  32014. base.class_eval do
  32015. alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
  32016. end
  32017. base.add_web_service_api_callback do |klass, api|
  32018. if klass.web_service_dispatching_mode == :direct
  32019. klass.class_eval 'def api; dispatch_web_service_request; end'
  32020. end
  32021. end
  32022. base.add_web_service_definition_callback do |klass, name, info|
  32023. if klass.web_service_dispatching_mode == :delegated
  32024. klass.class_eval "def #{name}; dispatch_web_service_request; end"
  32025. elsif klass.web_service_dispatching_mode == :layered
  32026. klass.class_eval 'def api; dispatch_web_service_request; end'
  32027. end
  32028. end
  32029. base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
  32030. end
  32031. module ClassMethods # :nodoc:
  32032. def inherited_with_action_controller(child)
  32033. inherited_without_action_controller(child)
  32034. child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
  32035. end
  32036. end
  32037. module InstanceMethods # :nodoc:
  32038. private
  32039. def dispatch_web_service_request
  32040. exception = nil
  32041. begin
  32042. ws_request = discover_web_service_request(request)
  32043. rescue Exception => e
  32044. exception = e
  32045. end
  32046. if ws_request
  32047. ws_response = nil
  32048. exception = nil
  32049. bm = Benchmark.measure do
  32050. begin
  32051. ws_response = invoke_web_service_request(ws_request)
  32052. rescue Exception => e
  32053. exception = e
  32054. end
  32055. end
  32056. log_request(ws_request, request.raw_post)
  32057. if exception
  32058. log_error(exception) unless logger.nil?
  32059. send_web_service_error_response(ws_request, exception)
  32060. else
  32061. send_web_service_response(ws_response, bm.real)
  32062. end
  32063. else
  32064. exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
  32065. log_error(exception) unless logger.nil?
  32066. send_web_service_error_response(ws_request, exception)
  32067. end
  32068. rescue Exception => e
  32069. log_error(e) unless logger.nil?
  32070. send_web_service_error_response(ws_request, e)
  32071. end
  32072. def send_web_service_response(ws_response, elapsed=nil)
  32073. log_response(ws_response, elapsed)
  32074. options = { :type => ws_response.content_type, :disposition => 'inline' }
  32075. send_data(ws_response.body, options)
  32076. end
  32077. def send_web_service_error_response(ws_request, exception)
  32078. if ws_request
  32079. unless self.class.web_service_exception_reporting
  32080. exception = DispatcherError.new("Internal server error (exception raised)")
  32081. end
  32082. api_method = ws_request.api_method
  32083. public_method_name = api_method ? api_method.public_name : ws_request.method_name
  32084. return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
  32085. ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
  32086. send_web_service_response(ws_response)
  32087. else
  32088. if self.class.web_service_exception_reporting
  32089. message = exception.message
  32090. backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
  32091. else
  32092. message = "Exception raised"
  32093. backtrace = ""
  32094. end
  32095. render_text("Internal protocol error: #{message}#{backtrace}", "500 Internal Protocol Error")
  32096. end
  32097. end
  32098. def web_service_direct_invoke(invocation)
  32099. invocation.method_named_params.each do |name, value|
  32100. params[name] = value
  32101. end
  32102. params['action'] = invocation.api_method.name.to_s
  32103. if before_action == false
  32104. raise(DispatcherError, "Method filtered")
  32105. end
  32106. return_value = web_service_direct_invoke_without_controller(invocation)
  32107. after_action
  32108. return_value
  32109. end
  32110. def log_request(ws_request, body)
  32111. unless logger.nil?
  32112. name = ws_request.method_name
  32113. api_method = ws_request.api_method
  32114. params = ws_request.method_params
  32115. if api_method && api_method.expects
  32116. params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
  32117. else
  32118. params = params.map{ |param| param.inspect }
  32119. end
  32120. service = ws_request.service_name
  32121. logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
  32122. logger.debug(indent(body))
  32123. end
  32124. end
  32125. def log_response(ws_response, elapsed=nil)
  32126. unless logger.nil?
  32127. elapsed = (elapsed ? " (%f):" % elapsed : ":")
  32128. logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
  32129. logger.debug(indent(ws_response.body))
  32130. end
  32131. end
  32132. def indent(body)
  32133. body.split(/\n/).map{|x| " #{x}"}.join("\n")
  32134. end
  32135. end
  32136. module WsdlAction # :nodoc:
  32137. XsdNs = 'http://www.w3.org/2001/XMLSchema'
  32138. WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
  32139. SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
  32140. SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
  32141. SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
  32142. def wsdl
  32143. case request.method
  32144. when :get
  32145. begin
  32146. options = { :type => 'text/xml', :disposition => 'inline' }
  32147. send_data(to_wsdl, options)
  32148. rescue Exception => e
  32149. log_error(e) unless logger.nil?
  32150. end
  32151. when :post
  32152. render_text('POST not supported', '500 POST not supported')
  32153. end
  32154. end
  32155. private
  32156. def base_uri
  32157. host = request.env['HTTP_HOST'] || request.env['SERVER_NAME'] || 'localhost'
  32158. relative_url_root = request.relative_url_root
  32159. scheme = request.ssl? ? 'https' : 'http'
  32160. '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
  32161. end
  32162. def to_wsdl
  32163. xml = ''
  32164. dispatching_mode = web_service_dispatching_mode
  32165. global_service_name = wsdl_service_name
  32166. namespace = wsdl_namespace || 'urn:ActionWebService'
  32167. soap_action_base = "/#{controller_name}"
  32168. marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
  32169. apis = {}
  32170. case dispatching_mode
  32171. when :direct
  32172. api = self.class.web_service_api
  32173. web_service_name = controller_class_name.sub(/Controller$/, '').underscore
  32174. apis[web_service_name] = [api, register_api(api, marshaler)]
  32175. when :delegated, :layered
  32176. self.class.web_services.each do |web_service_name, info|
  32177. service = web_service_object(web_service_name)
  32178. api = service.class.web_service_api
  32179. apis[web_service_name] = [api, register_api(api, marshaler)]
  32180. end
  32181. end
  32182. custom_types = []
  32183. apis.values.each do |api, bindings|
  32184. bindings.each do |b|
  32185. custom_types << b unless custom_types.include?(b)
  32186. end
  32187. end
  32188. xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
  32189. xm.instruct!
  32190. xm.definitions('name' => wsdl_service_name,
  32191. 'targetNamespace' => namespace,
  32192. 'xmlns:typens' => namespace,
  32193. 'xmlns:xsd' => XsdNs,
  32194. 'xmlns:soap' => SoapNs,
  32195. 'xmlns:soapenc' => SoapEncodingNs,
  32196. 'xmlns:wsdl' => WsdlNs,
  32197. 'xmlns' => WsdlNs) do
  32198. # Generate XSD
  32199. if custom_types.size > 0
  32200. xm.types do
  32201. xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
  32202. custom_types.each do |binding|
  32203. case
  32204. when binding.type.array?
  32205. xm.xsd(:complexType, 'name' => binding.type_name) do
  32206. xm.xsd(:complexContent) do
  32207. xm.xsd(:restriction, 'base' => 'soapenc:Array') do
  32208. xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
  32209. 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
  32210. end
  32211. end
  32212. end
  32213. when binding.type.structured?
  32214. xm.xsd(:complexType, 'name' => binding.type_name) do
  32215. xm.xsd(:all) do
  32216. binding.type.each_member do |name, type|
  32217. b = marshaler.register_type(type)
  32218. xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
  32219. end
  32220. end
  32221. end
  32222. end
  32223. end
  32224. end
  32225. end
  32226. end
  32227. # APIs
  32228. apis.each do |api_name, values|
  32229. api = values[0]
  32230. api.api_methods.each do |name, method|
  32231. gen = lambda do |msg_name, direction|
  32232. xm.message('name' => message_name_for(api_name, msg_name)) do
  32233. sym = nil
  32234. if direction == :out
  32235. returns = method.returns
  32236. if returns
  32237. binding = marshaler.register_type(returns[0])
  32238. xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
  32239. end
  32240. else
  32241. expects = method.expects
  32242. expects.each do |type|
  32243. binding = marshaler.register_type(type)
  32244. xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
  32245. end if expects
  32246. end
  32247. end
  32248. end
  32249. public_name = method.public_name
  32250. gen.call(public_name, :in)
  32251. gen.call("#{public_name}Response", :out)
  32252. end
  32253. # Port
  32254. port_name = port_name_for(global_service_name, api_name)
  32255. xm.portType('name' => port_name) do
  32256. api.api_methods.each do |name, method|
  32257. xm.operation('name' => method.public_name) do
  32258. xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
  32259. xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
  32260. end
  32261. end
  32262. end
  32263. # Bind it
  32264. binding_name = binding_name_for(global_service_name, api_name)
  32265. xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
  32266. xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
  32267. api.api_methods.each do |name, method|
  32268. xm.operation('name' => method.public_name) do
  32269. case web_service_dispatching_mode
  32270. when :direct
  32271. soap_action = soap_action_base + "/api/" + method.public_name
  32272. when :delegated, :layered
  32273. soap_action = soap_action_base \
  32274. + "/" + api_name.to_s \
  32275. + "/" + method.public_name
  32276. end
  32277. xm.soap(:operation, 'soapAction' => soap_action)
  32278. xm.input do
  32279. xm.soap(:body,
  32280. 'use' => 'encoded',
  32281. 'namespace' => namespace,
  32282. 'encodingStyle' => SoapEncodingNs)
  32283. end
  32284. xm.output do
  32285. xm.soap(:body,
  32286. 'use' => 'encoded',
  32287. 'namespace' => namespace,
  32288. 'encodingStyle' => SoapEncodingNs)
  32289. end
  32290. end
  32291. end
  32292. end
  32293. end
  32294. # Define it
  32295. xm.service('name' => "#{global_service_name}Service") do
  32296. apis.each do |api_name, values|
  32297. port_name = port_name_for(global_service_name, api_name)
  32298. binding_name = binding_name_for(global_service_name, api_name)
  32299. case web_service_dispatching_mode
  32300. when :direct, :layered
  32301. binding_target = 'api'
  32302. when :delegated
  32303. binding_target = api_name.to_s
  32304. end
  32305. xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
  32306. xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
  32307. end
  32308. end
  32309. end
  32310. end
  32311. end
  32312. def port_name_for(global_service, service)
  32313. "#{global_service}#{service.to_s.camelize}Port"
  32314. end
  32315. def binding_name_for(global_service, service)
  32316. "#{global_service}#{service.to_s.camelize}Binding"
  32317. end
  32318. def message_name_for(api_name, message_name)
  32319. mode = web_service_dispatching_mode
  32320. if mode == :layered || mode == :delegated
  32321. api_name.to_s + '-' + message_name
  32322. else
  32323. message_name
  32324. end
  32325. end
  32326. def register_api(api, marshaler)
  32327. bindings = {}
  32328. traverse_custom_types(api, marshaler, bindings) do |binding|
  32329. bindings[binding] = nil unless bindings.has_key?(binding)
  32330. element_binding = binding.element_binding
  32331. bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
  32332. end
  32333. bindings.keys
  32334. end
  32335. def traverse_custom_types(api, marshaler, bindings, &block)
  32336. api.api_methods.each do |name, method|
  32337. expects, returns = method.expects, method.returns
  32338. expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
  32339. returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
  32340. end
  32341. end
  32342. def traverse_type(marshaler, type, bindings, &block)
  32343. binding = marshaler.register_type(type)
  32344. return if bindings.has_key?(binding)
  32345. bindings[binding] = nil
  32346. yield binding
  32347. if type.array?
  32348. yield marshaler.register_type(type.element_type)
  32349. type = type.element_type
  32350. end
  32351. type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
  32352. end
  32353. end
  32354. end
  32355. end
  32356. end
  32357. require 'action_web_service/dispatcher/abstract'
  32358. require 'action_web_service/dispatcher/action_controller_dispatcher'
  32359. module ActionWebService # :nodoc:
  32360. module Invocation # :nodoc:
  32361. class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
  32362. end
  32363. def self.append_features(base) # :nodoc:
  32364. super
  32365. base.extend(ClassMethods)
  32366. base.send(:include, ActionWebService::Invocation::InstanceMethods)
  32367. end
  32368. # Invocation interceptors provide a means to execute custom code before
  32369. # and after method invocations on ActionWebService::Base objects.
  32370. #
  32371. # When running in _Direct_ dispatching mode, ActionController filters
  32372. # should be used for this functionality instead.
  32373. #
  32374. # The semantics of invocation interceptors are the same as ActionController
  32375. # filters, and accept the same parameters and options.
  32376. #
  32377. # A _before_ interceptor can also cancel execution by returning +false+,
  32378. # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply
  32379. # a reason for canceling the request.
  32380. #
  32381. # === Example
  32382. #
  32383. # class CustomService < ActionWebService::Base
  32384. # before_invocation :intercept_add, :only => [:add]
  32385. #
  32386. # def add(a, b)
  32387. # a + b
  32388. # end
  32389. #
  32390. # private
  32391. # def intercept_add
  32392. # return [false, "permission denied"] # cancel it
  32393. # end
  32394. # end
  32395. #
  32396. # Options:
  32397. # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called
  32398. # [<tt>:only</tt>] A list of methods for which the interceptor WILL be called
  32399. module ClassMethods
  32400. # Appends the given +interceptors+ to be called
  32401. # _before_ method invocation.
  32402. def append_before_invocation(*interceptors, &block)
  32403. conditions = extract_conditions!(interceptors)
  32404. interceptors << block if block_given?
  32405. add_interception_conditions(interceptors, conditions)
  32406. append_interceptors_to_chain("before", interceptors)
  32407. end
  32408. # Prepends the given +interceptors+ to be called
  32409. # _before_ method invocation.
  32410. def prepend_before_invocation(*interceptors, &block)
  32411. conditions = extract_conditions!(interceptors)
  32412. interceptors << block if block_given?
  32413. add_interception_conditions(interceptors, conditions)
  32414. prepend_interceptors_to_chain("before", interceptors)
  32415. end
  32416. alias :before_invocation :append_before_invocation
  32417. # Appends the given +interceptors+ to be called
  32418. # _after_ method invocation.
  32419. def append_after_invocation(*interceptors, &block)
  32420. conditions = extract_conditions!(interceptors)
  32421. interceptors << block if block_given?
  32422. add_interception_conditions(interceptors, conditions)
  32423. append_interceptors_to_chain("after", interceptors)
  32424. end
  32425. # Prepends the given +interceptors+ to be called
  32426. # _after_ method invocation.
  32427. def prepend_after_invocation(*interceptors, &block)
  32428. conditions = extract_conditions!(interceptors)
  32429. interceptors << block if block_given?
  32430. add_interception_conditions(interceptors, conditions)
  32431. prepend_interceptors_to_chain("after", interceptors)
  32432. end
  32433. alias :after_invocation :append_after_invocation
  32434. def before_invocation_interceptors # :nodoc:
  32435. read_inheritable_attribute("before_invocation_interceptors")
  32436. end
  32437. def after_invocation_interceptors # :nodoc:
  32438. read_inheritable_attribute("after_invocation_interceptors")
  32439. end
  32440. def included_intercepted_methods # :nodoc:
  32441. read_inheritable_attribute("included_intercepted_methods") || {}
  32442. end
  32443. def excluded_intercepted_methods # :nodoc:
  32444. read_inheritable_attribute("excluded_intercepted_methods") || {}
  32445. end
  32446. private
  32447. def append_interceptors_to_chain(condition, interceptors)
  32448. write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
  32449. end
  32450. def prepend_interceptors_to_chain(condition, interceptors)
  32451. interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
  32452. write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
  32453. end
  32454. def extract_conditions!(interceptors)
  32455. return nil unless interceptors.last.is_a? Hash
  32456. interceptors.pop
  32457. end
  32458. def add_interception_conditions(interceptors, conditions)
  32459. return unless conditions
  32460. included, excluded = conditions[:only], conditions[:except]
  32461. write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
  32462. write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
  32463. end
  32464. def condition_hash(interceptors, *methods)
  32465. interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
  32466. end
  32467. end
  32468. module InstanceMethods # :nodoc:
  32469. def self.append_features(base)
  32470. super
  32471. base.class_eval do
  32472. alias_method :perform_invocation_without_interception, :perform_invocation
  32473. alias_method :perform_invocation, :perform_invocation_with_interception
  32474. end
  32475. end
  32476. def perform_invocation_with_interception(method_name, params, &block)
  32477. return if before_invocation(method_name, params, &block) == false
  32478. return_value = perform_invocation_without_interception(method_name, params)
  32479. after_invocation(method_name, params, return_value)
  32480. return_value
  32481. end
  32482. def perform_invocation(method_name, params)
  32483. send(method_name, *params)
  32484. end
  32485. def before_invocation(name, args, &block)
  32486. call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
  32487. end
  32488. def after_invocation(name, args, result)
  32489. call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
  32490. end
  32491. private
  32492. def call_interceptors(interceptors, interceptor_args, &block)
  32493. if interceptors and not interceptors.empty?
  32494. interceptors.each do |interceptor|
  32495. next if method_exempted?(interceptor, interceptor_args[0].to_s)
  32496. result = case
  32497. when interceptor.is_a?(Symbol)
  32498. self.send(interceptor, *interceptor_args)
  32499. when interceptor_block?(interceptor)
  32500. interceptor.call(self, *interceptor_args)
  32501. when interceptor_class?(interceptor)
  32502. interceptor.intercept(self, *interceptor_args)
  32503. else
  32504. raise(
  32505. InvocationError,
  32506. "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
  32507. )
  32508. end
  32509. reason = nil
  32510. if result.is_a?(Array)
  32511. reason = result[1] if result[1]
  32512. result = result[0]
  32513. end
  32514. if result == false
  32515. block.call(reason) if block && reason
  32516. return false
  32517. end
  32518. end
  32519. end
  32520. end
  32521. def interceptor_block?(interceptor)
  32522. interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
  32523. end
  32524. def interceptor_class?(interceptor)
  32525. interceptor.respond_to?("intercept")
  32526. end
  32527. def method_exempted?(interceptor, method_name)
  32528. case
  32529. when self.class.included_intercepted_methods[interceptor]
  32530. !self.class.included_intercepted_methods[interceptor].include?(method_name)
  32531. when self.class.excluded_intercepted_methods[interceptor]
  32532. self.class.excluded_intercepted_methods[interceptor].include?(method_name)
  32533. end
  32534. end
  32535. end
  32536. end
  32537. end
  32538. module ActionWebService # :nodoc:
  32539. module Protocol # :nodoc:
  32540. class ProtocolError < ActionWebServiceError # :nodoc:
  32541. end
  32542. class AbstractProtocol # :nodoc:
  32543. def setup(controller)
  32544. end
  32545. def decode_action_pack_request(action_pack_request)
  32546. end
  32547. def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
  32548. klass = options[:request_class] || SimpleActionPackRequest
  32549. request = klass.new
  32550. request.request_parameters['action'] = service_name.to_s
  32551. request.env['RAW_POST_DATA'] = raw_body
  32552. request.env['REQUEST_METHOD'] = 'POST'
  32553. request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
  32554. request
  32555. end
  32556. def decode_request(raw_request, service_name, protocol_options={})
  32557. end
  32558. def encode_request(method_name, params, param_types)
  32559. end
  32560. def decode_response(raw_response)
  32561. end
  32562. def encode_response(method_name, return_value, return_type, protocol_options={})
  32563. end
  32564. def protocol_client(api, protocol_name, endpoint_uri, options)
  32565. end
  32566. def register_api(api)
  32567. end
  32568. end
  32569. class Request # :nodoc:
  32570. attr :protocol
  32571. attr_accessor :method_name
  32572. attr_accessor :method_params
  32573. attr :service_name
  32574. attr_accessor :api
  32575. attr_accessor :api_method
  32576. attr :protocol_options
  32577. def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil, protocol_options=nil)
  32578. @protocol = protocol
  32579. @method_name = method_name
  32580. @method_params = method_params
  32581. @service_name = service_name
  32582. @api = api
  32583. @api_method = api_method
  32584. @protocol_options = protocol_options || {}
  32585. end
  32586. end
  32587. class Response # :nodoc:
  32588. attr :body
  32589. attr :content_type
  32590. attr :return_value
  32591. def initialize(body, content_type, return_value)
  32592. @body = body
  32593. @content_type = content_type
  32594. @return_value = return_value
  32595. end
  32596. end
  32597. class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc:
  32598. def initialize
  32599. @env = {}
  32600. @qparams = {}
  32601. @rparams = {}
  32602. @cookies = {}
  32603. reset_session
  32604. end
  32605. def query_parameters
  32606. @qparams
  32607. end
  32608. def request_parameters
  32609. @rparams
  32610. end
  32611. def env
  32612. @env
  32613. end
  32614. def host
  32615. ''
  32616. end
  32617. def cookies
  32618. @cookies
  32619. end
  32620. def session
  32621. @session
  32622. end
  32623. def reset_session
  32624. @session = {}
  32625. end
  32626. end
  32627. end
  32628. end
  32629. module ActionWebService # :nodoc:
  32630. module Protocol # :nodoc:
  32631. module Discovery # :nodoc:
  32632. def self.included(base)
  32633. base.extend(ClassMethods)
  32634. base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods)
  32635. end
  32636. module ClassMethods # :nodoc:
  32637. def register_protocol(klass)
  32638. write_inheritable_array("web_service_protocols", [klass])
  32639. end
  32640. end
  32641. module InstanceMethods # :nodoc:
  32642. private
  32643. def discover_web_service_request(action_pack_request)
  32644. (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
  32645. protocol = protocol.create(self)
  32646. request = protocol.decode_action_pack_request(action_pack_request)
  32647. return request unless request.nil?
  32648. end
  32649. nil
  32650. end
  32651. def create_web_service_client(api, protocol_name, endpoint_uri, options)
  32652. (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
  32653. protocol = protocol.create(self)
  32654. client = protocol.protocol_client(api, protocol_name, endpoint_uri, options)
  32655. return client unless client.nil?
  32656. end
  32657. nil
  32658. end
  32659. end
  32660. end
  32661. end
  32662. end
  32663. require 'soap/mapping'
  32664. module ActionWebService
  32665. module Protocol
  32666. module Soap
  32667. # Workaround for SOAP4R return values changing
  32668. class Registry < SOAP::Mapping::Registry
  32669. if SOAP::Version >= "1.5.4"
  32670. def find_mapped_soap_class(obj_class)
  32671. return @map.instance_eval { @obj2soap[obj_class][0] }
  32672. end
  32673. def find_mapped_obj_class(soap_class)
  32674. return @map.instance_eval { @soap2obj[soap_class][0] }
  32675. end
  32676. end
  32677. end
  32678. class SoapMarshaler
  32679. attr :namespace
  32680. attr :registry
  32681. def initialize(namespace=nil)
  32682. @namespace = namespace || 'urn:ActionWebService'
  32683. @registry = Registry.new
  32684. @type2binding = {}
  32685. register_static_factories
  32686. end
  32687. def soap_to_ruby(obj)
  32688. SOAP::Mapping.soap2obj(obj, @registry)
  32689. end
  32690. def ruby_to_soap(obj)
  32691. soap = SOAP::Mapping.obj2soap(obj, @registry)
  32692. soap.elename = XSD::QName.new if SOAP::Version >= "1.5.5" && soap.elename == XSD::QName::EMPTY
  32693. soap
  32694. end
  32695. def register_type(type)
  32696. return @type2binding[type] if @type2binding.has_key?(type)
  32697. if type.array?
  32698. array_mapping = @registry.find_mapped_soap_class(Array)
  32699. qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
  32700. element_type_binding = register_type(type.element_type)
  32701. @type2binding[type] = SoapBinding.new(self, qname, type, array_mapping, element_type_binding)
  32702. elsif (mapping = @registry.find_mapped_soap_class(type.type_class) rescue nil)
  32703. qname = mapping[2] ? mapping[2][:type] : nil
  32704. qname ||= soap_base_type_name(mapping[0])
  32705. @type2binding[type] = SoapBinding.new(self, qname, type, mapping)
  32706. else
  32707. qname = XSD::QName.new(@namespace, soap_type_name(type.type_class.name))
  32708. @registry.add(type.type_class,
  32709. SOAP::SOAPStruct,
  32710. typed_struct_factory(type.type_class),
  32711. { :type => qname })
  32712. mapping = @registry.find_mapped_soap_class(type.type_class)
  32713. @type2binding[type] = SoapBinding.new(self, qname, type, mapping)
  32714. end
  32715. if type.structured?
  32716. type.each_member do |m_name, m_type|
  32717. register_type(m_type)
  32718. end
  32719. end
  32720. @type2binding[type]
  32721. end
  32722. alias :lookup_type :register_type
  32723. def annotate_arrays(binding, value)
  32724. if value.nil?
  32725. return
  32726. elsif binding.type.array?
  32727. mark_typed_array(value, binding.element_binding.qname)
  32728. if binding.element_binding.type.custom?
  32729. value.each do |element|
  32730. annotate_arrays(binding.element_binding, element)
  32731. end
  32732. end
  32733. elsif binding.type.structured?
  32734. binding.type.each_member do |name, type|
  32735. member_binding = register_type(type)
  32736. member_value = value.respond_to?('[]') ? value[name] : value.send(name)
  32737. annotate_arrays(member_binding, member_value) if type.custom?
  32738. end
  32739. end
  32740. end
  32741. private
  32742. def typed_struct_factory(type_class)
  32743. if Object.const_defined?('ActiveRecord')
  32744. if type_class.ancestors.include?(ActiveRecord::Base)
  32745. qname = XSD::QName.new(@namespace, soap_type_name(type_class.name))
  32746. type_class.instance_variable_set('@qname', qname)
  32747. return SoapActiveRecordStructFactory.new
  32748. end
  32749. end
  32750. SOAP::Mapping::Registry::TypedStructFactory
  32751. end
  32752. def mark_typed_array(array, qname)
  32753. (class << array; self; end).class_eval do
  32754. define_method(:arytype) do
  32755. qname
  32756. end
  32757. end
  32758. end
  32759. def soap_base_type_name(type)
  32760. xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' }
  32761. xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
  32762. end
  32763. def soap_type_name(type_name)
  32764. type_name.gsub(/::/, '..')
  32765. end
  32766. def register_static_factories
  32767. @registry.add(ActionWebService::Base64,
  32768. SOAP::SOAPBase64,
  32769. SoapBase64Factory.new,
  32770. nil)
  32771. mapping = @registry.find_mapped_soap_class(ActionWebService::Base64)
  32772. @type2binding[ActionWebService::Base64] =
  32773. SoapBinding.new(self, SOAP::SOAPBase64::Type,
  32774. ActionWebService::Base64, mapping)
  32775. @registry.add(Array,
  32776. SOAP::SOAPArray,
  32777. SoapTypedArrayFactory.new,
  32778. nil)
  32779. end
  32780. end
  32781. class SoapBinding
  32782. attr :qname
  32783. attr :type
  32784. attr :mapping
  32785. attr :element_binding
  32786. def initialize(marshaler, qname, type, mapping, element_binding=nil)
  32787. @marshaler = marshaler
  32788. @qname = qname
  32789. @type = type
  32790. @mapping = mapping
  32791. @element_binding = element_binding
  32792. end
  32793. def type_name
  32794. @type.custom? ? @qname.name : nil
  32795. end
  32796. def qualified_type_name(ns=nil)
  32797. if @type.custom?
  32798. "#{ns ? ns : @qname.namespace}:#{@qname.name}"
  32799. else
  32800. ns = XSD::NS.new
  32801. ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
  32802. ns.assign(SOAP::EncodingNamespace, "soapenc")
  32803. xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
  32804. return ns.name(XSD::AnyTypeName) unless xsd_klass
  32805. ns.name(xsd_klass.const_get('Type'))
  32806. end
  32807. end
  32808. def eql?(other)
  32809. @qname == other.qname
  32810. end
  32811. alias :== :eql?
  32812. def hash
  32813. @qname.hash
  32814. end
  32815. end
  32816. class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
  32817. def obj2soap(soap_class, obj, info, map)
  32818. unless obj.is_a?(ActiveRecord::Base)
  32819. return nil
  32820. end
  32821. soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
  32822. obj.class.columns.each do |column|
  32823. key = column.name.to_s
  32824. value = obj.send(key)
  32825. soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
  32826. end
  32827. soap_obj
  32828. end
  32829. def soap2obj(obj_class, node, info, map)
  32830. unless node.type == obj_class.instance_variable_get('@qname')
  32831. return false
  32832. end
  32833. obj = obj_class.new
  32834. node.each do |key, value|
  32835. obj[key] = value.data
  32836. end
  32837. obj.instance_variable_set('@new_record', false)
  32838. return true, obj
  32839. end
  32840. end
  32841. class SoapTypedArrayFactory < SOAP::Mapping::Factory
  32842. def obj2soap(soap_class, obj, info, map)
  32843. unless obj.respond_to?(:arytype)
  32844. return nil
  32845. end
  32846. soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
  32847. mark_marshalled_obj(obj, soap_obj)
  32848. obj.each do |item|
  32849. child = SOAP::Mapping._obj2soap(item, map)
  32850. soap_obj.add(child)
  32851. end
  32852. soap_obj
  32853. end
  32854. def soap2obj(obj_class, node, info, map)
  32855. return false
  32856. end
  32857. end
  32858. class SoapBase64Factory < SOAP::Mapping::Factory
  32859. def obj2soap(soap_class, obj, info, map)
  32860. unless obj.is_a?(ActionWebService::Base64)
  32861. return nil
  32862. end
  32863. return soap_class.new(obj)
  32864. end
  32865. def soap2obj(obj_class, node, info, map)
  32866. unless node.type == SOAP::SOAPBase64::Type
  32867. return false
  32868. end
  32869. return true, obj_class.new(node.string)
  32870. end
  32871. end
  32872. end
  32873. end
  32874. end
  32875. require 'action_web_service/protocol/soap_protocol/marshaler'
  32876. require 'soap/streamHandler'
  32877. require 'action_web_service/client/soap_client'
  32878. module ActionWebService # :nodoc:
  32879. module API # :nodoc:
  32880. class Base # :nodoc:
  32881. def self.soap_client(endpoint_uri, options={})
  32882. ActionWebService::Client::Soap.new self, endpoint_uri, options
  32883. end
  32884. end
  32885. end
  32886. module Protocol # :nodoc:
  32887. module Soap # :nodoc:
  32888. def self.included(base)
  32889. base.register_protocol(SoapProtocol)
  32890. base.class_inheritable_option(:wsdl_service_name)
  32891. base.class_inheritable_option(:wsdl_namespace)
  32892. end
  32893. class SoapProtocol < AbstractProtocol # :nodoc:
  32894. AWSEncoding = 'UTF-8'
  32895. XSDEncoding = 'UTF8'
  32896. attr :marshaler
  32897. def initialize(namespace=nil)
  32898. namespace ||= 'urn:ActionWebService'
  32899. @marshaler = SoapMarshaler.new namespace
  32900. end
  32901. def self.create(controller)
  32902. SoapProtocol.new(controller.wsdl_namespace)
  32903. end
  32904. def decode_action_pack_request(action_pack_request)
  32905. return nil unless soap_action = has_valid_soap_action?(action_pack_request)
  32906. service_name = action_pack_request.parameters['action']
  32907. input_encoding = parse_charset(action_pack_request.env['HTTP_CONTENT_TYPE'])
  32908. protocol_options = {
  32909. :soap_action => soap_action,
  32910. :charset => input_encoding
  32911. }
  32912. decode_request(action_pack_request.raw_post, service_name, protocol_options)
  32913. end
  32914. def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
  32915. request = super
  32916. request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
  32917. request
  32918. end
  32919. def decode_request(raw_request, service_name, protocol_options={})
  32920. envelope = SOAP::Processor.unmarshal(raw_request, :charset => protocol_options[:charset])
  32921. unless envelope
  32922. raise ProtocolError, "Failed to parse SOAP request message"
  32923. end
  32924. request = envelope.body.request
  32925. method_name = request.elename.name
  32926. params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) }
  32927. Request.new(self, method_name, params, service_name, nil, nil, protocol_options)
  32928. end
  32929. def encode_request(method_name, params, param_types)
  32930. param_types.each{ |type| marshaler.register_type(type) } if param_types
  32931. qname = XSD::QName.new(marshaler.namespace, method_name)
  32932. param_def = []
  32933. if param_types
  32934. params = param_types.zip(params).map do |type, param|
  32935. param_def << ['in', type.name, marshaler.lookup_type(type).mapping]
  32936. [type.name, marshaler.ruby_to_soap(param)]
  32937. end
  32938. else
  32939. params = []
  32940. end
  32941. request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
  32942. request.set_param(params)
  32943. envelope = create_soap_envelope(request)
  32944. SOAP::Processor.marshal(envelope)
  32945. end
  32946. def decode_response(raw_response)
  32947. envelope = SOAP::Processor.unmarshal(raw_response)
  32948. unless envelope
  32949. raise ProtocolError, "Failed to parse SOAP request message"
  32950. end
  32951. method_name = envelope.body.request.elename.name
  32952. return_value = envelope.body.response
  32953. return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil?
  32954. [method_name, return_value]
  32955. end
  32956. def encode_response(method_name, return_value, return_type, protocol_options={})
  32957. if return_type
  32958. return_binding = marshaler.register_type(return_type)
  32959. marshaler.annotate_arrays(return_binding, return_value)
  32960. end
  32961. qname = XSD::QName.new(marshaler.namespace, method_name)
  32962. if return_value.nil?
  32963. response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
  32964. else
  32965. if return_value.is_a?(Exception)
  32966. detail = SOAP::Mapping::SOAPException.new(return_value)
  32967. response = SOAP::SOAPFault.new(
  32968. SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
  32969. SOAP::SOAPString.new(return_value.to_s),
  32970. SOAP::SOAPString.new(self.class.name),
  32971. marshaler.ruby_to_soap(detail))
  32972. else
  32973. if return_type
  32974. param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]]
  32975. response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
  32976. response.retval = marshaler.ruby_to_soap(return_value)
  32977. else
  32978. response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
  32979. end
  32980. end
  32981. end
  32982. envelope = create_soap_envelope(response)
  32983. # FIXME: This is not thread-safe, but StringFactory_ in SOAP4R only
  32984. # reads target encoding from the XSD::Charset.encoding variable.
  32985. # This is required to ensure $KCODE strings are converted
  32986. # correctly to UTF-8 for any values of $KCODE.
  32987. previous_encoding = XSD::Charset.encoding
  32988. XSD::Charset.encoding = XSDEncoding
  32989. response_body = SOAP::Processor.marshal(envelope, :charset => AWSEncoding)
  32990. XSD::Charset.encoding = previous_encoding
  32991. Response.new(response_body, "text/xml; charset=#{AWSEncoding}", return_value)
  32992. end
  32993. def protocol_client(api, protocol_name, endpoint_uri, options={})
  32994. return nil unless protocol_name == :soap
  32995. ActionWebService::Client::Soap.new(api, endpoint_uri, options)
  32996. end
  32997. def register_api(api)
  32998. api.api_methods.each do |name, method|
  32999. method.expects.each{ |type| marshaler.register_type(type) } if method.expects
  33000. method.returns.each{ |type| marshaler.register_type(type) } if method.returns
  33001. end
  33002. end
  33003. private
  33004. def has_valid_soap_action?(request)
  33005. return nil unless request.method == :post
  33006. soap_action = request.env['HTTP_SOAPACTION']
  33007. return nil unless soap_action
  33008. soap_action = soap_action.dup
  33009. soap_action.gsub!(/^"/, '')
  33010. soap_action.gsub!(/"$/, '')
  33011. soap_action.strip!
  33012. return nil if soap_action.empty?
  33013. soap_action
  33014. end
  33015. def create_soap_envelope(body)
  33016. header = SOAP::SOAPHeader.new
  33017. body = SOAP::SOAPBody.new(body)
  33018. SOAP::SOAPEnvelope.new(header, body)
  33019. end
  33020. def parse_charset(content_type)
  33021. return AWSEncoding if content_type.nil?
  33022. if /^text\/xml(?:\s*;\s*charset=([^"]+|"[^"]+"))$/i =~ content_type
  33023. $1
  33024. else
  33025. AWSEncoding
  33026. end
  33027. end
  33028. end
  33029. end
  33030. end
  33031. end
  33032. require 'xmlrpc/marshal'
  33033. require 'action_web_service/client/xmlrpc_client'
  33034. module XMLRPC # :nodoc:
  33035. class FaultException # :nodoc:
  33036. alias :message :faultString
  33037. end
  33038. end
  33039. module ActionWebService # :nodoc:
  33040. module API # :nodoc:
  33041. class Base # :nodoc:
  33042. def self.xmlrpc_client(endpoint_uri, options={})
  33043. ActionWebService::Client::XmlRpc.new self, endpoint_uri, options
  33044. end
  33045. end
  33046. end
  33047. module Protocol # :nodoc:
  33048. module XmlRpc # :nodoc:
  33049. def self.included(base)
  33050. base.register_protocol(XmlRpcProtocol)
  33051. end
  33052. class XmlRpcProtocol < AbstractProtocol # :nodoc:
  33053. def self.create(controller)
  33054. XmlRpcProtocol.new
  33055. end
  33056. def decode_action_pack_request(action_pack_request)
  33057. service_name = action_pack_request.parameters['action']
  33058. decode_request(action_pack_request.raw_post, service_name)
  33059. end
  33060. def decode_request(raw_request, service_name)
  33061. method_name, params = XMLRPC::Marshal.load_call(raw_request)
  33062. Request.new(self, method_name, params, service_name)
  33063. end
  33064. def encode_request(method_name, params, param_types)
  33065. if param_types
  33066. params = params.dup
  33067. param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
  33068. end
  33069. XMLRPC::Marshal.dump_call(method_name, *params)
  33070. end
  33071. def decode_response(raw_response)
  33072. [nil, XMLRPC::Marshal.load_response(raw_response)]
  33073. end
  33074. def encode_response(method_name, return_value, return_type, protocol_options={})
  33075. if return_value && return_type
  33076. return_value = value_to_xmlrpc_wire_format(return_value, return_type)
  33077. end
  33078. return_value = false if return_value.nil?
  33079. raw_response = XMLRPC::Marshal.dump_response(return_value)
  33080. Response.new(raw_response, 'text/xml', return_value)
  33081. end
  33082. def protocol_client(api, protocol_name, endpoint_uri, options={})
  33083. return nil unless protocol_name == :xmlrpc
  33084. ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
  33085. end
  33086. def value_to_xmlrpc_wire_format(value, value_type)
  33087. if value_type.array?
  33088. value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
  33089. else
  33090. if value.is_a?(ActionWebService::Struct)
  33091. struct = {}
  33092. value.class.members.each do |name, type|
  33093. member_value = value[name]
  33094. next if member_value.nil?
  33095. struct[name.to_s] = value_to_xmlrpc_wire_format(member_value, type)
  33096. end
  33097. struct
  33098. elsif value.is_a?(ActiveRecord::Base)
  33099. struct = {}
  33100. value.attributes.each do |key, member_value|
  33101. next if member_value.nil?
  33102. struct[key.to_s] = member_value
  33103. end
  33104. struct
  33105. elsif value.is_a?(ActionWebService::Base64)
  33106. XMLRPC::Base64.new(value)
  33107. elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
  33108. XMLRPC::FaultException.new(2, value.message)
  33109. else
  33110. value
  33111. end
  33112. end
  33113. end
  33114. end
  33115. end
  33116. end
  33117. end
  33118. require 'action_web_service/protocol/abstract'
  33119. require 'action_web_service/protocol/discovery'
  33120. require 'action_web_service/protocol/soap_protocol'
  33121. require 'action_web_service/protocol/xmlrpc_protocol'
  33122. require 'benchmark'
  33123. require 'pathname'
  33124. module ActionWebService
  33125. module Scaffolding # :nodoc:
  33126. class ScaffoldingError < ActionWebServiceError # :nodoc:
  33127. end
  33128. def self.append_features(base)
  33129. super
  33130. base.extend(ClassMethods)
  33131. end
  33132. # Web service invocation scaffolding provides a way to quickly invoke web service methods in a controller. The
  33133. # generated scaffold actions have default views to let you enter the method parameters and view the
  33134. # results.
  33135. #
  33136. # Example:
  33137. #
  33138. # class ApiController < ActionController
  33139. # web_service_scaffold :invoke
  33140. # end
  33141. #
  33142. # This example generates an +invoke+ action in the +ApiController+ that you can navigate to from
  33143. # your browser, select the API method, enter its parameters, and perform the invocation.
  33144. #
  33145. # If you want to customize the default views, create the following views in "app/views":
  33146. #
  33147. # * <tt>action_name/methods.rhtml</tt>
  33148. # * <tt>action_name/parameters.rhtml</tt>
  33149. # * <tt>action_name/result.rhtml</tt>
  33150. # * <tt>action_name/layout.rhtml</tt>
  33151. #
  33152. # Where <tt>action_name</tt> is the name of the action you gave to ClassMethods#web_service_scaffold.
  33153. #
  33154. # You can use the default views in <tt>RAILS_DIR/lib/action_web_service/templates/scaffolds</tt> as
  33155. # a guide.
  33156. module ClassMethods
  33157. # Generates web service invocation scaffolding for the current controller. The given action name
  33158. # can then be used as the entry point for invoking API methods from a web browser.
  33159. def web_service_scaffold(action_name)
  33160. add_template_helper(Helpers)
  33161. module_eval <<-"end_eval", __FILE__, __LINE__
  33162. def #{action_name}
  33163. if request.method == :get
  33164. setup_invocation_assigns
  33165. render_invocation_scaffold 'methods'
  33166. end
  33167. end
  33168. def #{action_name}_method_params
  33169. if request.method == :get
  33170. setup_invocation_assigns
  33171. render_invocation_scaffold 'parameters'
  33172. end
  33173. end
  33174. def #{action_name}_submit
  33175. if request.method == :post
  33176. setup_invocation_assigns
  33177. protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap
  33178. case protocol_name
  33179. when :soap
  33180. @protocol = Protocol::Soap::SoapProtocol.create(self)
  33181. when :xmlrpc
  33182. @protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self)
  33183. end
  33184. bm = Benchmark.measure do
  33185. @protocol.register_api(@scaffold_service.api)
  33186. post_params = params['method_params'] ? params['method_params'].dup : nil
  33187. params = []
  33188. @scaffold_method.expects.each_with_index do |spec, i|
  33189. params << post_params[i.to_s]
  33190. end if @scaffold_method.expects
  33191. params = @scaffold_method.cast_expects(params)
  33192. method_name = public_method_name(@scaffold_service.name, @scaffold_method.public_name)
  33193. @method_request_xml = @protocol.encode_request(method_name, params, @scaffold_method.expects)
  33194. new_request = @protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
  33195. prepare_request(new_request, @scaffold_service.name, @scaffold_method.public_name)
  33196. @request = new_request
  33197. if @scaffold_container.dispatching_mode != :direct
  33198. request.parameters['action'] = @scaffold_service.name
  33199. end
  33200. dispatch_web_service_request
  33201. @method_response_xml = @response.body
  33202. method_name, obj = @protocol.decode_response(@method_response_xml)
  33203. return if handle_invocation_exception(obj)
  33204. @method_return_value = @scaffold_method.cast_returns(obj)
  33205. end
  33206. @method_elapsed = bm.real
  33207. add_instance_variables_to_assigns
  33208. reset_invocation_response
  33209. render_invocation_scaffold 'result'
  33210. end
  33211. end
  33212. private
  33213. def setup_invocation_assigns
  33214. @scaffold_class = self.class
  33215. @scaffold_action_name = "#{action_name}"
  33216. @scaffold_container = WebServiceModel::Container.new(self)
  33217. if params['service'] && params['method']
  33218. @scaffold_service = @scaffold_container.services.find{ |x| x.name == params['service'] }
  33219. @scaffold_method = @scaffold_service.api_methods[params['method']]
  33220. end
  33221. add_instance_variables_to_assigns
  33222. end
  33223. def render_invocation_scaffold(action)
  33224. customized_template = "\#{self.class.controller_path}/#{action_name}/\#{action}"
  33225. default_template = scaffold_path(action)
  33226. if template_exists?(customized_template)
  33227. content = @template.render_file(customized_template)
  33228. else
  33229. content = @template.render_file(default_template, false)
  33230. end
  33231. @template.instance_variable_set("@content_for_layout", content)
  33232. if self.active_layout.nil?
  33233. render_file(scaffold_path("layout"))
  33234. else
  33235. render_file(self.active_layout, "200 OK", true)
  33236. end
  33237. end
  33238. def scaffold_path(template_name)
  33239. File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
  33240. end
  33241. def reset_invocation_response
  33242. erase_render_results
  33243. @response.headers = ::ActionController::AbstractResponse::DEFAULT_HEADERS.merge("cookie" => [])
  33244. end
  33245. def public_method_name(service_name, method_name)
  33246. if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
  33247. service_name + '.' + method_name
  33248. else
  33249. method_name
  33250. end
  33251. end
  33252. def prepare_request(new_request, service_name, method_name)
  33253. new_request.parameters.update(request.parameters)
  33254. request.env.each{ |k, v| new_request.env[k] = v unless new_request.env.has_key?(k) }
  33255. if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
  33256. new_request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}"
  33257. end
  33258. end
  33259. def handle_invocation_exception(obj)
  33260. exception = nil
  33261. if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception)
  33262. exception = obj.detail.cause
  33263. elsif obj.is_a?(XMLRPC::FaultException)
  33264. exception = obj
  33265. end
  33266. return unless exception
  33267. reset_invocation_response
  33268. rescue_action(exception)
  33269. true
  33270. end
  33271. end_eval
  33272. end
  33273. end
  33274. module Helpers # :nodoc:
  33275. def method_parameter_input_fields(method, type, field_name_base, idx, was_structured=false)
  33276. if type.array?
  33277. return content_tag('em', "Typed array input fields not supported yet (#{type.name})")
  33278. end
  33279. if type.structured?
  33280. return content_tag('em', "Nested structural types not supported yet (#{type.name})") if was_structured
  33281. parameters = ""
  33282. type.each_member do |member_name, member_type|
  33283. label = method_parameter_label(member_name, member_type)
  33284. nested_content = method_parameter_input_fields(
  33285. method,
  33286. member_type,
  33287. "#{field_name_base}[#{idx}][#{member_name}]",
  33288. idx,
  33289. true)
  33290. if member_type.custom?
  33291. parameters << content_tag('li', label)
  33292. parameters << content_tag('ul', nested_content)
  33293. else
  33294. parameters << content_tag('li', label + ' ' + nested_content)
  33295. end
  33296. end
  33297. content_tag('ul', parameters)
  33298. else
  33299. # If the data source was structured previously we already have the index set
  33300. field_name_base = "#{field_name_base}[#{idx}]" unless was_structured
  33301. case type.type
  33302. when :int
  33303. text_field_tag "#{field_name_base}"
  33304. when :string
  33305. text_field_tag "#{field_name_base}"
  33306. when :base64
  33307. text_area_tag "#{field_name_base}", nil, :size => "40x5"
  33308. when :bool
  33309. radio_button_tag("#{field_name_base}", "true") + " True" +
  33310. radio_button_tag("#{field_name_base}", "false") + "False"
  33311. when :float
  33312. text_field_tag "#{field_name_base}"
  33313. when :time, :datetime
  33314. time = Time.now
  33315. i = 0
  33316. %w|year month day hour minute second|.map do |name|
  33317. i += 1
  33318. send("select_#{name}", time, :prefix => "#{field_name_base}[#{i}]", :discard_type => true)
  33319. end.join
  33320. when :date
  33321. date = Date.today
  33322. i = 0
  33323. %w|year month day|.map do |name|
  33324. i += 1
  33325. send("select_#{name}", date, :prefix => "#{field_name_base}[#{i}]", :discard_type => true)
  33326. end.join
  33327. end
  33328. end
  33329. end
  33330. def method_parameter_label(name, type)
  33331. name.to_s.capitalize + ' (' + type.human_name(false) + ')'
  33332. end
  33333. def service_method_list(service)
  33334. action = @scaffold_action_name + '_method_params'
  33335. methods = service.api_methods_full.map do |desc, name|
  33336. content_tag("li", link_to(desc, :action => action, :service => service.name, :method => name))
  33337. end
  33338. content_tag("ul", methods.join("\n"))
  33339. end
  33340. end
  33341. module WebServiceModel # :nodoc:
  33342. class Container # :nodoc:
  33343. attr :services
  33344. attr :dispatching_mode
  33345. def initialize(real_container)
  33346. @real_container = real_container
  33347. @dispatching_mode = @real_container.class.web_service_dispatching_mode
  33348. @services = []
  33349. if @dispatching_mode == :direct
  33350. @services << Service.new(@real_container.controller_name, @real_container)
  33351. else
  33352. @real_container.class.web_services.each do |name, obj|
  33353. @services << Service.new(name, @real_container.instance_eval{ web_service_object(name) })
  33354. end
  33355. end
  33356. end
  33357. end
  33358. class Service # :nodoc:
  33359. attr :name
  33360. attr :object
  33361. attr :api
  33362. attr :api_methods
  33363. attr :api_methods_full
  33364. def initialize(name, real_service)
  33365. @name = name.to_s
  33366. @object = real_service
  33367. @api = @object.class.web_service_api
  33368. if @api.nil?
  33369. raise ScaffoldingError, "No web service API attached to #{object.class}"
  33370. end
  33371. @api_methods = {}
  33372. @api_methods_full = []
  33373. @api.api_methods.each do |name, method|
  33374. @api_methods[method.public_name.to_s] = method
  33375. @api_methods_full << [method.to_s, method.public_name.to_s]
  33376. end
  33377. end
  33378. def to_s
  33379. self.name.camelize
  33380. end
  33381. end
  33382. end
  33383. end
  33384. end
  33385. module ActionWebService
  33386. # To send structured types across the wire, derive from ActionWebService::Struct,
  33387. # and use +member+ to declare structure members.
  33388. #
  33389. # ActionWebService::Struct should be used in method signatures when you want to accept or return
  33390. # structured types that have no Active Record model class representations, or you don't
  33391. # want to expose your entire Active Record model to remote callers.
  33392. #
  33393. # === Example
  33394. #
  33395. # class Person < ActionWebService::Struct
  33396. # member :id, :int
  33397. # member :firstnames, [:string]
  33398. # member :lastname, :string
  33399. # member :email, :string
  33400. # end
  33401. # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe')
  33402. #
  33403. # Active Record model classes are already implicitly supported in method
  33404. # signatures.
  33405. class Struct
  33406. # Action WebService Struct subclasses should be reloaded by the dispatcher in Rails
  33407. # when Dependencies.mechanism = :load.
  33408. include Reloadable::Subclasses
  33409. # If a Hash is given as argument to an ActionWebService::Struct constructor,
  33410. # it can contain initial values for the structure member.
  33411. def initialize(values={})
  33412. if values.is_a?(Hash)
  33413. values.map{|k,v| __send__('%s=' % k.to_s, v)}
  33414. end
  33415. end
  33416. # The member with the given name
  33417. def [](name)
  33418. send(name.to_s)
  33419. end
  33420. # Iterates through each member
  33421. def each_pair(&block)
  33422. self.class.members.each do |name, type|
  33423. yield name, self.__send__(name)
  33424. end
  33425. end
  33426. class << self
  33427. # Creates a structure member with the specified +name+ and +type+. Generates
  33428. # accessor methods for reading and writing the member value.
  33429. def member(name, type)
  33430. name = name.to_sym
  33431. type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0)
  33432. write_inheritable_hash("struct_members", name => type)
  33433. class_eval <<-END
  33434. def #{name}; @#{name}; end
  33435. def #{name}=(value); @#{name} = value; end
  33436. END
  33437. end
  33438. def members # :nodoc:
  33439. read_inheritable_attribute("struct_members") || {}
  33440. end
  33441. def member_type(name) # :nodoc:
  33442. members[name.to_sym]
  33443. end
  33444. end
  33445. end
  33446. end
  33447. class Class # :nodoc:
  33448. def class_inheritable_option(sym, default_value=nil)
  33449. write_inheritable_attribute sym, default_value
  33450. class_eval <<-EOS
  33451. def self.#{sym}(value=nil)
  33452. if !value.nil?
  33453. write_inheritable_attribute(:#{sym}, value)
  33454. else
  33455. read_inheritable_attribute(:#{sym})
  33456. end
  33457. end
  33458. def self.#{sym}=(value)
  33459. write_inheritable_attribute(:#{sym}, value)
  33460. end
  33461. def #{sym}
  33462. self.class.#{sym}
  33463. end
  33464. def #{sym}=(value)
  33465. self.class.#{sym} = value
  33466. end
  33467. EOS
  33468. end
  33469. end
  33470. module ActionWebService # :nodoc:
  33471. # Action Web Service supports the following base types in a signature:
  33472. #
  33473. # [<tt>:int</tt>] Represents an integer value, will be cast to an integer using <tt>Integer(value)</tt>
  33474. # [<tt>:string</tt>] Represents a string value, will be cast to an string using the <tt>to_s</tt> method on an object
  33475. # [<tt>:base64</tt>] Represents a Base 64 value, will contain the binary bytes of a Base 64 value sent by the caller
  33476. # [<tt>:bool</tt>] Represents a boolean value, whatever is passed will be cast to boolean (<tt>true</tt>, '1', 'true', 'y', 'yes' are taken to represent true; <tt>false</tt>, '0', 'false', 'n', 'no' and <tt>nil</tt> represent false)
  33477. # [<tt>:float</tt>] Represents a floating point value, will be cast to a float using <tt>Float(value)</tt>
  33478. # [<tt>:time</tt>] Represents a timestamp, will be cast to a <tt>Time</tt> object
  33479. # [<tt>:datetime</tt>] Represents a timestamp, will be cast to a <tt>DateTime</tt> object
  33480. # [<tt>:date</tt>] Represents a date, will be cast to a <tt>Date</tt> object
  33481. #
  33482. # For structured types, you'll need to pass in the Class objects of
  33483. # ActionWebService::Struct and ActiveRecord::Base derivatives.
  33484. module SignatureTypes
  33485. def canonical_signature(signature) # :nodoc:
  33486. return nil if signature.nil?
  33487. unless signature.is_a?(Array)
  33488. raise(ActionWebServiceError, "Expected signature to be an Array")
  33489. end
  33490. i = -1
  33491. signature.map{ |spec| canonical_signature_entry(spec, i += 1) }
  33492. end
  33493. def canonical_signature_entry(spec, i) # :nodoc:
  33494. orig_spec = spec
  33495. name = "param#{i}"
  33496. if spec.is_a?(Hash)
  33497. name, spec = spec.keys.first, spec.values.first
  33498. end
  33499. type = spec
  33500. if spec.is_a?(Array)
  33501. ArrayType.new(orig_spec, canonical_signature_entry(spec[0], 0), name)
  33502. else
  33503. type = canonical_type(type)
  33504. if type.is_a?(Symbol)
  33505. BaseType.new(orig_spec, type, name)
  33506. else
  33507. StructuredType.new(orig_spec, type, name)
  33508. end
  33509. end
  33510. end
  33511. def canonical_type(type) # :nodoc:
  33512. type_name = symbol_name(type) || class_to_type_name(type)
  33513. type = type_name || type
  33514. return canonical_type_name(type) if type.is_a?(Symbol)
  33515. type
  33516. end
  33517. def canonical_type_name(name) # :nodoc:
  33518. name = name.to_sym
  33519. case name
  33520. when :int, :integer, :fixnum, :bignum
  33521. :int
  33522. when :string, :text
  33523. :string
  33524. when :base64, :binary
  33525. :base64
  33526. when :bool, :boolean
  33527. :bool
  33528. when :float, :double
  33529. :float
  33530. when :time, :timestamp
  33531. :time
  33532. when :datetime
  33533. :datetime
  33534. when :date
  33535. :date
  33536. else
  33537. raise(TypeError, "#{name} is not a valid base type")
  33538. end
  33539. end
  33540. def canonical_type_class(type) # :nodoc:
  33541. type = canonical_type(type)
  33542. type.is_a?(Symbol) ? type_name_to_class(type) : type
  33543. end
  33544. def symbol_name(name) # :nodoc:
  33545. return name.to_sym if name.is_a?(Symbol) || name.is_a?(String)
  33546. nil
  33547. end
  33548. def class_to_type_name(klass) # :nodoc:
  33549. klass = klass.class unless klass.is_a?(Class)
  33550. if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass)
  33551. :int
  33552. elsif klass == String
  33553. :string
  33554. elsif klass == Base64
  33555. :base64
  33556. elsif klass == TrueClass || klass == FalseClass
  33557. :bool
  33558. elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass)
  33559. :float
  33560. elsif klass == Time
  33561. :time
  33562. elsif klass == DateTime
  33563. :datetime
  33564. elsif klass == Date
  33565. :date
  33566. else
  33567. nil
  33568. end
  33569. end
  33570. def type_name_to_class(name) # :nodoc:
  33571. case canonical_type_name(name)
  33572. when :int
  33573. Integer
  33574. when :string
  33575. String
  33576. when :base64
  33577. Base64
  33578. when :bool
  33579. TrueClass
  33580. when :float
  33581. Float
  33582. when :time
  33583. Time
  33584. when :date
  33585. Date
  33586. when :datetime
  33587. DateTime
  33588. else
  33589. nil
  33590. end
  33591. end
  33592. def derived_from?(ancestor, child) # :nodoc:
  33593. child.ancestors.include?(ancestor)
  33594. end
  33595. module_function :type_name_to_class
  33596. module_function :class_to_type_name
  33597. module_function :symbol_name
  33598. module_function :canonical_type_class
  33599. module_function :canonical_type_name
  33600. module_function :canonical_type
  33601. module_function :canonical_signature_entry
  33602. module_function :canonical_signature
  33603. module_function :derived_from?
  33604. end
  33605. class BaseType # :nodoc:
  33606. include SignatureTypes
  33607. attr :spec
  33608. attr :type
  33609. attr :type_class
  33610. attr :name
  33611. def initialize(spec, type, name)
  33612. @spec = spec
  33613. @type = canonical_type(type)
  33614. @type_class = canonical_type_class(@type)
  33615. @name = name
  33616. end
  33617. def custom?
  33618. false
  33619. end
  33620. def array?
  33621. false
  33622. end
  33623. def structured?
  33624. false
  33625. end
  33626. def human_name(show_name=true)
  33627. type_type = array? ? element_type.type.to_s : self.type.to_s
  33628. str = array? ? (type_type + '[]') : type_type
  33629. show_name ? (str + " " + name.to_s) : str
  33630. end
  33631. end
  33632. class ArrayType < BaseType # :nodoc:
  33633. attr :element_type
  33634. def initialize(spec, element_type, name)
  33635. super(spec, Array, name)
  33636. @element_type = element_type
  33637. end
  33638. def custom?
  33639. true
  33640. end
  33641. def array?
  33642. true
  33643. end
  33644. end
  33645. class StructuredType < BaseType # :nodoc:
  33646. def each_member
  33647. if @type_class.respond_to?(:members)
  33648. @type_class.members.each do |name, type|
  33649. yield name, type
  33650. end
  33651. elsif @type_class.respond_to?(:columns)
  33652. i = -1
  33653. @type_class.columns.each do |column|
  33654. yield column.name, canonical_signature_entry(column.type, i += 1)
  33655. end
  33656. end
  33657. end
  33658. def custom?
  33659. true
  33660. end
  33661. def structured?
  33662. true
  33663. end
  33664. end
  33665. class Base64 < String # :nodoc:
  33666. end
  33667. end
  33668. require 'test/unit'
  33669. module Test # :nodoc:
  33670. module Unit # :nodoc:
  33671. class TestCase # :nodoc:
  33672. private
  33673. # invoke the specified API method
  33674. def invoke_direct(method_name, *args)
  33675. prepare_request('api', 'api', method_name, *args)
  33676. @controller.process(@request, @response)
  33677. decode_rpc_response
  33678. end
  33679. alias_method :invoke, :invoke_direct
  33680. # invoke the specified API method on the specified service
  33681. def invoke_delegated(service_name, method_name, *args)
  33682. prepare_request(service_name.to_s, service_name, method_name, *args)
  33683. @controller.process(@request, @response)
  33684. decode_rpc_response
  33685. end
  33686. # invoke the specified layered API method on the correct service
  33687. def invoke_layered(service_name, method_name, *args)
  33688. prepare_request('api', service_name, method_name, *args)
  33689. @controller.process(@request, @response)
  33690. decode_rpc_response
  33691. end
  33692. # ---------------------- internal ---------------------------
  33693. def prepare_request(action, service_name, api_method_name, *args)
  33694. @request.recycle!
  33695. @request.request_parameters['action'] = action
  33696. @request.env['REQUEST_METHOD'] = 'POST'
  33697. @request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
  33698. @request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args)
  33699. case protocol
  33700. when ActionWebService::Protocol::Soap::SoapProtocol
  33701. soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}"
  33702. @request.env['HTTP_SOAPACTION'] = soap_action
  33703. when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
  33704. @request.env.delete('HTTP_SOAPACTION')
  33705. end
  33706. end
  33707. def encode_rpc_call(service_name, api_method_name, *args)
  33708. case @controller.web_service_dispatching_mode
  33709. when :direct
  33710. api = @controller.class.web_service_api
  33711. when :delegated, :layered
  33712. api = @controller.web_service_object(service_name.to_sym).class.web_service_api
  33713. end
  33714. protocol.register_api(api)
  33715. method = api.api_methods[api_method_name.to_sym]
  33716. raise ArgumentError, "wrong number of arguments for rpc call (#{args.length} for #{method.expects.length})" unless args.length == method.expects.length
  33717. protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects)
  33718. end
  33719. def decode_rpc_response
  33720. public_method_name, return_value = protocol.decode_response(@response.body)
  33721. exception = is_exception?(return_value)
  33722. raise exception if exception
  33723. return_value
  33724. end
  33725. def public_method_name(service_name, api_method_name)
  33726. public_name = service_api(service_name).public_api_method_name(api_method_name)
  33727. if @controller.web_service_dispatching_mode == :layered && protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
  33728. '%s.%s' % [service_name.to_s, public_name]
  33729. else
  33730. public_name
  33731. end
  33732. end
  33733. def service_api(service_name)
  33734. case @controller.web_service_dispatching_mode
  33735. when :direct
  33736. @controller.class.web_service_api
  33737. when :delegated, :layered
  33738. @controller.web_service_object(service_name.to_sym).class.web_service_api
  33739. end
  33740. end
  33741. def protocol
  33742. if @protocol.nil?
  33743. @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
  33744. else
  33745. case @protocol
  33746. when :xmlrpc
  33747. @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@controller)
  33748. when :soap
  33749. @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
  33750. else
  33751. @protocol
  33752. end
  33753. end
  33754. end
  33755. def is_exception?(obj)
  33756. case protocol
  33757. when :soap, ActionWebService::Protocol::Soap::SoapProtocol
  33758. (obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
  33759. obj.detail.cause.is_a?(Exception)) ? obj.detail.cause : nil
  33760. when :xmlrpc, ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
  33761. obj.is_a?(XMLRPC::FaultException) ? obj : nil
  33762. end
  33763. end
  33764. end
  33765. end
  33766. end
  33767. module ActionWebService
  33768. module VERSION #:nodoc:
  33769. MAJOR = 1
  33770. MINOR = 1
  33771. TINY = 6
  33772. STRING = [MAJOR, MINOR, TINY].join('.')
  33773. end
  33774. end
  33775. #--
  33776. # Copyright (C) 2005 Leon Breedt
  33777. #
  33778. # Permission is hereby granted, free of charge, to any person obtaining
  33779. # a copy of this software and associated documentation files (the
  33780. # "Software"), to deal in the Software without restriction, including
  33781. # without limitation the rights to use, copy, modify, merge, publish,
  33782. # distribute, sublicense, and/or sell copies of the Software, and to
  33783. # permit persons to whom the Software is furnished to do so, subject to
  33784. # the following conditions:
  33785. #
  33786. # The above copyright notice and this permission notice shall be
  33787. # included in all copies or substantial portions of the Software.
  33788. #
  33789. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  33790. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  33791. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  33792. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  33793. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  33794. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  33795. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  33796. #++
  33797. begin
  33798. require 'active_support'
  33799. require 'action_controller'
  33800. require 'active_record'
  33801. rescue LoadError
  33802. require 'rubygems'
  33803. require_gem 'activesupport', '>= 1.0.2'
  33804. require_gem 'actionpack', '>= 1.6.0'
  33805. require_gem 'activerecord', '>= 1.9.0'
  33806. end
  33807. $:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")
  33808. require 'action_web_service/support/class_inheritable_options'
  33809. require 'action_web_service/support/signature_types'
  33810. require 'action_web_service/base'
  33811. require 'action_web_service/client'
  33812. require 'action_web_service/invocation'
  33813. require 'action_web_service/api'
  33814. require 'action_web_service/casting'
  33815. require 'action_web_service/struct'
  33816. require 'action_web_service/container'
  33817. require 'action_web_service/protocol'
  33818. require 'action_web_service/dispatcher'
  33819. require 'action_web_service/scaffolding'
  33820. ActionWebService::Base.class_eval do
  33821. include ActionWebService::Container::Direct
  33822. include ActionWebService::Invocation
  33823. end
  33824. ActionController::Base.class_eval do
  33825. include ActionWebService::Protocol::Discovery
  33826. include ActionWebService::Protocol::Soap
  33827. include ActionWebService::Protocol::XmlRpc
  33828. include ActionWebService::Container::Direct
  33829. include ActionWebService::Container::Delegated
  33830. include ActionWebService::Container::ActionController
  33831. include ActionWebService::Invocation
  33832. include ActionWebService::Dispatcher
  33833. include ActionWebService::Dispatcher::ActionController
  33834. include ActionWebService::Scaffolding
  33835. end
  33836. #
  33837. # setup.rb
  33838. #
  33839. # Copyright (c) 2000-2004 Minero Aoki
  33840. #
  33841. # Permission is hereby granted, free of charge, to any person obtaining
  33842. # a copy of this software and associated documentation files (the
  33843. # "Software"), to deal in the Software without restriction, including
  33844. # without limitation the rights to use, copy, modify, merge, publish,
  33845. # distribute, sublicense, and/or sell copies of the Software, and to
  33846. # permit persons to whom the Software is furnished to do so, subject to
  33847. # the following conditions:
  33848. #
  33849. # The above copyright notice and this permission notice shall be
  33850. # included in all copies or substantial portions of the Software.
  33851. #
  33852. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  33853. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  33854. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  33855. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  33856. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  33857. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  33858. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  33859. #
  33860. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  33861. # with permission of Minero Aoki.
  33862. #
  33863. unless Enumerable.method_defined?(:map) # Ruby 1.4.6
  33864. module Enumerable
  33865. alias map collect
  33866. end
  33867. end
  33868. unless File.respond_to?(:read) # Ruby 1.6
  33869. def File.read(fname)
  33870. open(fname) {|f|
  33871. return f.read
  33872. }
  33873. end
  33874. end
  33875. def File.binread(fname)
  33876. open(fname, 'rb') {|f|
  33877. return f.read
  33878. }
  33879. end
  33880. # for corrupted windows stat(2)
  33881. def File.dir?(path)
  33882. File.directory?((path[-1,1] == '/') ? path : path + '/')
  33883. end
  33884. class SetupError < StandardError; end
  33885. def setup_rb_error(msg)
  33886. raise SetupError, msg
  33887. end
  33888. #
  33889. # Config
  33890. #
  33891. if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
  33892. ARGV.delete(arg)
  33893. require arg.split(/=/, 2)[1]
  33894. $".push 'rbconfig.rb'
  33895. else
  33896. require 'rbconfig'
  33897. end
  33898. def multipackage_install?
  33899. FileTest.directory?(File.dirname($0) + '/packages')
  33900. end
  33901. class ConfigItem
  33902. def initialize(name, template, default, desc)
  33903. @name = name.freeze
  33904. @template = template
  33905. @value = default
  33906. @default = default.dup.freeze
  33907. @description = desc
  33908. end
  33909. attr_reader :name
  33910. attr_reader :description
  33911. attr_accessor :default
  33912. alias help_default default
  33913. def help_opt
  33914. "--#{@name}=#{@template}"
  33915. end
  33916. def value
  33917. @value
  33918. end
  33919. def eval(table)
  33920. @value.gsub(%r<\$([^/]+)>) { table[$1] }
  33921. end
  33922. def set(val)
  33923. @value = check(val)
  33924. end
  33925. private
  33926. def check(val)
  33927. setup_rb_error "config: --#{name} requires argument" unless val
  33928. val
  33929. end
  33930. end
  33931. class BoolItem < ConfigItem
  33932. def config_type
  33933. 'bool'
  33934. end
  33935. def help_opt
  33936. "--#{@name}"
  33937. end
  33938. private
  33939. def check(val)
  33940. return 'yes' unless val
  33941. unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
  33942. setup_rb_error "config: --#{@name} accepts only yes/no for argument"
  33943. end
  33944. (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
  33945. end
  33946. end
  33947. class PathItem < ConfigItem
  33948. def config_type
  33949. 'path'
  33950. end
  33951. private
  33952. def check(path)
  33953. setup_rb_error "config: --#{@name} requires argument" unless path
  33954. path[0,1] == '$' ? path : File.expand_path(path)
  33955. end
  33956. end
  33957. class ProgramItem < ConfigItem
  33958. def config_type
  33959. 'program'
  33960. end
  33961. end
  33962. class SelectItem < ConfigItem
  33963. def initialize(name, template, default, desc)
  33964. super
  33965. @ok = template.split('/')
  33966. end
  33967. def config_type
  33968. 'select'
  33969. end
  33970. private
  33971. def check(val)
  33972. unless @ok.include?(val.strip)
  33973. setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
  33974. end
  33975. val.strip
  33976. end
  33977. end
  33978. class PackageSelectionItem < ConfigItem
  33979. def initialize(name, template, default, help_default, desc)
  33980. super name, template, default, desc
  33981. @help_default = help_default
  33982. end
  33983. attr_reader :help_default
  33984. def config_type
  33985. 'package'
  33986. end
  33987. private
  33988. def check(val)
  33989. unless File.dir?("packages/#{val}")
  33990. setup_rb_error "config: no such package: #{val}"
  33991. end
  33992. val
  33993. end
  33994. end
  33995. class ConfigTable_class
  33996. def initialize(items)
  33997. @items = items
  33998. @table = {}
  33999. items.each do |i|
  34000. @table[i.name] = i
  34001. end
  34002. ALIASES.each do |ali, name|
  34003. @table[ali] = @table[name]
  34004. end
  34005. end
  34006. include Enumerable
  34007. def each(&block)
  34008. @items.each(&block)
  34009. end
  34010. def key?(name)
  34011. @table.key?(name)
  34012. end
  34013. def lookup(name)
  34014. @table[name] or raise ArgumentError, "no such config item: #{name}"
  34015. end
  34016. def add(item)
  34017. @items.push item
  34018. @table[item.name] = item
  34019. end
  34020. def remove(name)
  34021. item = lookup(name)
  34022. @items.delete_if {|i| i.name == name }
  34023. @table.delete_if {|name, i| i.name == name }
  34024. item
  34025. end
  34026. def new
  34027. dup()
  34028. end
  34029. def savefile
  34030. '.config'
  34031. end
  34032. def load
  34033. begin
  34034. t = dup()
  34035. File.foreach(savefile()) do |line|
  34036. k, v = *line.split(/=/, 2)
  34037. t[k] = v.strip
  34038. end
  34039. t
  34040. rescue Errno::ENOENT
  34041. setup_rb_error $!.message + "#{File.basename($0)} config first"
  34042. end
  34043. end
  34044. def save
  34045. @items.each {|i| i.value }
  34046. File.open(savefile(), 'w') {|f|
  34047. @items.each do |i|
  34048. f.printf "%s=%s\n", i.name, i.value if i.value
  34049. end
  34050. }
  34051. end
  34052. def [](key)
  34053. lookup(key).eval(self)
  34054. end
  34055. def []=(key, val)
  34056. lookup(key).set val
  34057. end
  34058. end
  34059. c = ::Config::CONFIG
  34060. rubypath = c['bindir'] + '/' + c['ruby_install_name']
  34061. major = c['MAJOR'].to_i
  34062. minor = c['MINOR'].to_i
  34063. teeny = c['TEENY'].to_i
  34064. version = "#{major}.#{minor}"
  34065. # ruby ver. >= 1.4.4?
  34066. newpath_p = ((major >= 2) or
  34067. ((major == 1) and
  34068. ((minor >= 5) or
  34069. ((minor == 4) and (teeny >= 4)))))
  34070. if c['rubylibdir']
  34071. # V < 1.6.3
  34072. _stdruby = c['rubylibdir']
  34073. _siteruby = c['sitedir']
  34074. _siterubyver = c['sitelibdir']
  34075. _siterubyverarch = c['sitearchdir']
  34076. elsif newpath_p
  34077. # 1.4.4 <= V <= 1.6.3
  34078. _stdruby = "$prefix/lib/ruby/#{version}"
  34079. _siteruby = c['sitedir']
  34080. _siterubyver = "$siteruby/#{version}"
  34081. _siterubyverarch = "$siterubyver/#{c['arch']}"
  34082. else
  34083. # V < 1.4.4
  34084. _stdruby = "$prefix/lib/ruby/#{version}"
  34085. _siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
  34086. _siterubyver = _siteruby
  34087. _siterubyverarch = "$siterubyver/#{c['arch']}"
  34088. end
  34089. libdir = '-* dummy libdir *-'
  34090. stdruby = '-* dummy rubylibdir *-'
  34091. siteruby = '-* dummy site_ruby *-'
  34092. siterubyver = '-* dummy site_ruby version *-'
  34093. parameterize = lambda {|path|
  34094. path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
  34095. .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\
  34096. .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\
  34097. .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\
  34098. .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
  34099. }
  34100. libdir = parameterize.call(c['libdir'])
  34101. stdruby = parameterize.call(_stdruby)
  34102. siteruby = parameterize.call(_siteruby)
  34103. siterubyver = parameterize.call(_siterubyver)
  34104. siterubyverarch = parameterize.call(_siterubyverarch)
  34105. if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
  34106. makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
  34107. else
  34108. makeprog = 'make'
  34109. end
  34110. common_conf = [
  34111. PathItem.new('prefix', 'path', c['prefix'],
  34112. 'path prefix of target environment'),
  34113. PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
  34114. 'the directory for commands'),
  34115. PathItem.new('libdir', 'path', libdir,
  34116. 'the directory for libraries'),
  34117. PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
  34118. 'the directory for shared data'),
  34119. PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
  34120. 'the directory for man pages'),
  34121. PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
  34122. 'the directory for man pages'),
  34123. PathItem.new('stdruby', 'path', stdruby,
  34124. 'the directory for standard ruby libraries'),
  34125. PathItem.new('siteruby', 'path', siteruby,
  34126. 'the directory for version-independent aux ruby libraries'),
  34127. PathItem.new('siterubyver', 'path', siterubyver,
  34128. 'the directory for aux ruby libraries'),
  34129. PathItem.new('siterubyverarch', 'path', siterubyverarch,
  34130. 'the directory for aux ruby binaries'),
  34131. PathItem.new('rbdir', 'path', '$siterubyver',
  34132. 'the directory for ruby scripts'),
  34133. PathItem.new('sodir', 'path', '$siterubyverarch',
  34134. 'the directory for ruby extentions'),
  34135. PathItem.new('rubypath', 'path', rubypath,
  34136. 'the path to set to #! line'),
  34137. ProgramItem.new('rubyprog', 'name', rubypath,
  34138. 'the ruby program using for installation'),
  34139. ProgramItem.new('makeprog', 'name', makeprog,
  34140. 'the make program to compile ruby extentions'),
  34141. SelectItem.new('shebang', 'all/ruby/never', 'ruby',
  34142. 'shebang line (#!) editing mode'),
  34143. BoolItem.new('without-ext', 'yes/no', 'no',
  34144. 'does not compile/install ruby extentions')
  34145. ]
  34146. class ConfigTable_class # open again
  34147. ALIASES = {
  34148. 'std-ruby' => 'stdruby',
  34149. 'site-ruby-common' => 'siteruby', # For backward compatibility
  34150. 'site-ruby' => 'siterubyver', # For backward compatibility
  34151. 'bin-dir' => 'bindir',
  34152. 'bin-dir' => 'bindir',
  34153. 'rb-dir' => 'rbdir',
  34154. 'so-dir' => 'sodir',
  34155. 'data-dir' => 'datadir',
  34156. 'ruby-path' => 'rubypath',
  34157. 'ruby-prog' => 'rubyprog',
  34158. 'ruby' => 'rubyprog',
  34159. 'make-prog' => 'makeprog',
  34160. 'make' => 'makeprog'
  34161. }
  34162. end
  34163. multipackage_conf = [
  34164. PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
  34165. 'package names that you want to install'),
  34166. PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
  34167. 'package names that you do not want to install')
  34168. ]
  34169. if multipackage_install?
  34170. ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
  34171. else
  34172. ConfigTable = ConfigTable_class.new(common_conf)
  34173. end
  34174. module MetaConfigAPI
  34175. def eval_file_ifexist(fname)
  34176. instance_eval File.read(fname), fname, 1 if File.file?(fname)
  34177. end
  34178. def config_names
  34179. ConfigTable.map {|i| i.name }
  34180. end
  34181. def config?(name)
  34182. ConfigTable.key?(name)
  34183. end
  34184. def bool_config?(name)
  34185. ConfigTable.lookup(name).config_type == 'bool'
  34186. end
  34187. def path_config?(name)
  34188. ConfigTable.lookup(name).config_type == 'path'
  34189. end
  34190. def value_config?(name)
  34191. case ConfigTable.lookup(name).config_type
  34192. when 'bool', 'path'
  34193. true
  34194. else
  34195. false
  34196. end
  34197. end
  34198. def add_config(item)
  34199. ConfigTable.add item
  34200. end
  34201. def add_bool_config(name, default, desc)
  34202. ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
  34203. end
  34204. def add_path_config(name, default, desc)
  34205. ConfigTable.add PathItem.new(name, 'path', default, desc)
  34206. end
  34207. def set_config_default(name, default)
  34208. ConfigTable.lookup(name).default = default
  34209. end
  34210. def remove_config(name)
  34211. ConfigTable.remove(name)
  34212. end
  34213. end
  34214. #
  34215. # File Operations
  34216. #
  34217. module FileOperations
  34218. def mkdir_p(dirname, prefix = nil)
  34219. dirname = prefix + File.expand_path(dirname) if prefix
  34220. $stderr.puts "mkdir -p #{dirname}" if verbose?
  34221. return if no_harm?
  34222. # does not check '/'... it's too abnormal case
  34223. dirs = File.expand_path(dirname).split(%r<(?=/)>)
  34224. if /\A[a-z]:\z/i =~ dirs[0]
  34225. disk = dirs.shift
  34226. dirs[0] = disk + dirs[0]
  34227. end
  34228. dirs.each_index do |idx|
  34229. path = dirs[0..idx].join('')
  34230. Dir.mkdir path unless File.dir?(path)
  34231. end
  34232. end
  34233. def rm_f(fname)
  34234. $stderr.puts "rm -f #{fname}" if verbose?
  34235. return if no_harm?
  34236. if File.exist?(fname) or File.symlink?(fname)
  34237. File.chmod 0777, fname
  34238. File.unlink fname
  34239. end
  34240. end
  34241. def rm_rf(dn)
  34242. $stderr.puts "rm -rf #{dn}" if verbose?
  34243. return if no_harm?
  34244. Dir.chdir dn
  34245. Dir.foreach('.') do |fn|
  34246. next if fn == '.'
  34247. next if fn == '..'
  34248. if File.dir?(fn)
  34249. verbose_off {
  34250. rm_rf fn
  34251. }
  34252. else
  34253. verbose_off {
  34254. rm_f fn
  34255. }
  34256. end
  34257. end
  34258. Dir.chdir '..'
  34259. Dir.rmdir dn
  34260. end
  34261. def move_file(src, dest)
  34262. File.unlink dest if File.exist?(dest)
  34263. begin
  34264. File.rename src, dest
  34265. rescue
  34266. File.open(dest, 'wb') {|f| f.write File.binread(src) }
  34267. File.chmod File.stat(src).mode, dest
  34268. File.unlink src
  34269. end
  34270. end
  34271. def install(from, dest, mode, prefix = nil)
  34272. $stderr.puts "install #{from} #{dest}" if verbose?
  34273. return if no_harm?
  34274. realdest = prefix ? prefix + File.expand_path(dest) : dest
  34275. realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
  34276. str = File.binread(from)
  34277. if diff?(str, realdest)
  34278. verbose_off {
  34279. rm_f realdest if File.exist?(realdest)
  34280. }
  34281. File.open(realdest, 'wb') {|f|
  34282. f.write str
  34283. }
  34284. File.chmod mode, realdest
  34285. File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
  34286. if prefix
  34287. f.puts realdest.sub(prefix, '')
  34288. else
  34289. f.puts realdest
  34290. end
  34291. }
  34292. end
  34293. end
  34294. def diff?(new_content, path)
  34295. return true unless File.exist?(path)
  34296. new_content != File.binread(path)
  34297. end
  34298. def command(str)
  34299. $stderr.puts str if verbose?
  34300. system str or raise RuntimeError, "'system #{str}' failed"
  34301. end
  34302. def ruby(str)
  34303. command config('rubyprog') + ' ' + str
  34304. end
  34305. def make(task = '')
  34306. command config('makeprog') + ' ' + task
  34307. end
  34308. def extdir?(dir)
  34309. File.exist?(dir + '/MANIFEST')
  34310. end
  34311. def all_files_in(dirname)
  34312. Dir.open(dirname) {|d|
  34313. return d.select {|ent| File.file?("#{dirname}/#{ent}") }
  34314. }
  34315. end
  34316. REJECT_DIRS = %w(
  34317. CVS SCCS RCS CVS.adm .svn
  34318. )
  34319. def all_dirs_in(dirname)
  34320. Dir.open(dirname) {|d|
  34321. return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
  34322. }
  34323. end
  34324. end
  34325. #
  34326. # Main Installer
  34327. #
  34328. module HookUtils
  34329. def run_hook(name)
  34330. try_run_hook "#{curr_srcdir()}/#{name}" or
  34331. try_run_hook "#{curr_srcdir()}/#{name}.rb"
  34332. end
  34333. def try_run_hook(fname)
  34334. return false unless File.file?(fname)
  34335. begin
  34336. instance_eval File.read(fname), fname, 1
  34337. rescue
  34338. setup_rb_error "hook #{fname} failed:\n" + $!.message
  34339. end
  34340. true
  34341. end
  34342. end
  34343. module HookScriptAPI
  34344. def get_config(key)
  34345. @config[key]
  34346. end
  34347. alias config get_config
  34348. def set_config(key, val)
  34349. @config[key] = val
  34350. end
  34351. #
  34352. # srcdir/objdir (works only in the package directory)
  34353. #
  34354. #abstract srcdir_root
  34355. #abstract objdir_root
  34356. #abstract relpath
  34357. def curr_srcdir
  34358. "#{srcdir_root()}/#{relpath()}"
  34359. end
  34360. def curr_objdir
  34361. "#{objdir_root()}/#{relpath()}"
  34362. end
  34363. def srcfile(path)
  34364. "#{curr_srcdir()}/#{path}"
  34365. end
  34366. def srcexist?(path)
  34367. File.exist?(srcfile(path))
  34368. end
  34369. def srcdirectory?(path)
  34370. File.dir?(srcfile(path))
  34371. end
  34372. def srcfile?(path)
  34373. File.file? srcfile(path)
  34374. end
  34375. def srcentries(path = '.')
  34376. Dir.open("#{curr_srcdir()}/#{path}") {|d|
  34377. return d.to_a - %w(. ..)
  34378. }
  34379. end
  34380. def srcfiles(path = '.')
  34381. srcentries(path).select {|fname|
  34382. File.file?(File.join(curr_srcdir(), path, fname))
  34383. }
  34384. end
  34385. def srcdirectories(path = '.')
  34386. srcentries(path).select {|fname|
  34387. File.dir?(File.join(curr_srcdir(), path, fname))
  34388. }
  34389. end
  34390. end
  34391. class ToplevelInstaller
  34392. Version = '3.3.1'
  34393. Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
  34394. TASKS = [
  34395. [ 'all', 'do config, setup, then install' ],
  34396. [ 'config', 'saves your configurations' ],
  34397. [ 'show', 'shows current configuration' ],
  34398. [ 'setup', 'compiles ruby extentions and others' ],
  34399. [ 'install', 'installs files' ],
  34400. [ 'clean', "does `make clean' for each extention" ],
  34401. [ 'distclean',"does `make distclean' for each extention" ]
  34402. ]
  34403. def ToplevelInstaller.invoke
  34404. instance().invoke
  34405. end
  34406. @singleton = nil
  34407. def ToplevelInstaller.instance
  34408. @singleton ||= new(File.dirname($0))
  34409. @singleton
  34410. end
  34411. include MetaConfigAPI
  34412. def initialize(ardir_root)
  34413. @config = nil
  34414. @options = { 'verbose' => true }
  34415. @ardir = File.expand_path(ardir_root)
  34416. end
  34417. def inspect
  34418. "#<#{self.class} #{__id__()}>"
  34419. end
  34420. def invoke
  34421. run_metaconfigs
  34422. case task = parsearg_global()
  34423. when nil, 'all'
  34424. @config = load_config('config')
  34425. parsearg_config
  34426. init_installers
  34427. exec_config
  34428. exec_setup
  34429. exec_install
  34430. else
  34431. @config = load_config(task)
  34432. __send__ "parsearg_#{task}"
  34433. init_installers
  34434. __send__ "exec_#{task}"
  34435. end
  34436. end
  34437. def run_metaconfigs
  34438. eval_file_ifexist "#{@ardir}/metaconfig"
  34439. end
  34440. def load_config(task)
  34441. case task
  34442. when 'config'
  34443. ConfigTable.new
  34444. when 'clean', 'distclean'
  34445. if File.exist?(ConfigTable.savefile)
  34446. then ConfigTable.load
  34447. else ConfigTable.new
  34448. end
  34449. else
  34450. ConfigTable.load
  34451. end
  34452. end
  34453. def init_installers
  34454. @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
  34455. end
  34456. #
  34457. # Hook Script API bases
  34458. #
  34459. def srcdir_root
  34460. @ardir
  34461. end
  34462. def objdir_root
  34463. '.'
  34464. end
  34465. def relpath
  34466. '.'
  34467. end
  34468. #
  34469. # Option Parsing
  34470. #
  34471. def parsearg_global
  34472. valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
  34473. while arg = ARGV.shift
  34474. case arg
  34475. when /\A\w+\z/
  34476. setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
  34477. return arg
  34478. when '-q', '--quiet'
  34479. @options['verbose'] = false
  34480. when '--verbose'
  34481. @options['verbose'] = true
  34482. when '-h', '--help'
  34483. print_usage $stdout
  34484. exit 0
  34485. when '-v', '--version'
  34486. puts "#{File.basename($0)} version #{Version}"
  34487. exit 0
  34488. when '--copyright'
  34489. puts Copyright
  34490. exit 0
  34491. else
  34492. setup_rb_error "unknown global option '#{arg}'"
  34493. end
  34494. end
  34495. nil
  34496. end
  34497. def parsearg_no_options
  34498. unless ARGV.empty?
  34499. setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}"
  34500. end
  34501. end
  34502. alias parsearg_show parsearg_no_options
  34503. alias parsearg_setup parsearg_no_options
  34504. alias parsearg_clean parsearg_no_options
  34505. alias parsearg_distclean parsearg_no_options
  34506. def parsearg_config
  34507. re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
  34508. @options['config-opt'] = []
  34509. while i = ARGV.shift
  34510. if /\A--?\z/ =~ i
  34511. @options['config-opt'] = ARGV.dup
  34512. break
  34513. end
  34514. m = re.match(i) or setup_rb_error "config: unknown option #{i}"
  34515. name, value = *m.to_a[1,2]
  34516. @config[name] = value
  34517. end
  34518. end
  34519. def parsearg_install
  34520. @options['no-harm'] = false
  34521. @options['install-prefix'] = ''
  34522. while a = ARGV.shift
  34523. case a
  34524. when /\A--no-harm\z/
  34525. @options['no-harm'] = true
  34526. when /\A--prefix=(.*)\z/
  34527. path = $1
  34528. path = File.expand_path(path) unless path[0,1] == '/'
  34529. @options['install-prefix'] = path
  34530. else
  34531. setup_rb_error "install: unknown option #{a}"
  34532. end
  34533. end
  34534. end
  34535. def print_usage(out)
  34536. out.puts 'Typical Installation Procedure:'
  34537. out.puts " $ ruby #{File.basename $0} config"
  34538. out.puts " $ ruby #{File.basename $0} setup"
  34539. out.puts " # ruby #{File.basename $0} install (may require root privilege)"
  34540. out.puts
  34541. out.puts 'Detailed Usage:'
  34542. out.puts " ruby #{File.basename $0} <global option>"
  34543. out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
  34544. fmt = " %-24s %s\n"
  34545. out.puts
  34546. out.puts 'Global options:'
  34547. out.printf fmt, '-q,--quiet', 'suppress message outputs'
  34548. out.printf fmt, ' --verbose', 'output messages verbosely'
  34549. out.printf fmt, '-h,--help', 'print this message'
  34550. out.printf fmt, '-v,--version', 'print version and quit'
  34551. out.printf fmt, ' --copyright', 'print copyright and quit'
  34552. out.puts
  34553. out.puts 'Tasks:'
  34554. TASKS.each do |name, desc|
  34555. out.printf fmt, name, desc
  34556. end
  34557. fmt = " %-24s %s [%s]\n"
  34558. out.puts
  34559. out.puts 'Options for CONFIG or ALL:'
  34560. ConfigTable.each do |item|
  34561. out.printf fmt, item.help_opt, item.description, item.help_default
  34562. end
  34563. out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
  34564. out.puts
  34565. out.puts 'Options for INSTALL:'
  34566. out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
  34567. out.printf fmt, '--prefix=path', 'install path prefix', '$prefix'
  34568. out.puts
  34569. end
  34570. #
  34571. # Task Handlers
  34572. #
  34573. def exec_config
  34574. @installer.exec_config
  34575. @config.save # must be final
  34576. end
  34577. def exec_setup
  34578. @installer.exec_setup
  34579. end
  34580. def exec_install
  34581. @installer.exec_install
  34582. end
  34583. def exec_show
  34584. ConfigTable.each do |i|
  34585. printf "%-20s %s\n", i.name, i.value
  34586. end
  34587. end
  34588. def exec_clean
  34589. @installer.exec_clean
  34590. end
  34591. def exec_distclean
  34592. @installer.exec_distclean
  34593. end
  34594. end
  34595. class ToplevelInstallerMulti < ToplevelInstaller
  34596. include HookUtils
  34597. include HookScriptAPI
  34598. include FileOperations
  34599. def initialize(ardir)
  34600. super
  34601. @packages = all_dirs_in("#{@ardir}/packages")
  34602. raise 'no package exists' if @packages.empty?
  34603. end
  34604. def run_metaconfigs
  34605. eval_file_ifexist "#{@ardir}/metaconfig"
  34606. @packages.each do |name|
  34607. eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
  34608. end
  34609. end
  34610. def init_installers
  34611. @installers = {}
  34612. @packages.each do |pack|
  34613. @installers[pack] = Installer.new(@config, @options,
  34614. "#{@ardir}/packages/#{pack}",
  34615. "packages/#{pack}")
  34616. end
  34617. with = extract_selection(config('with'))
  34618. without = extract_selection(config('without'))
  34619. @selected = @installers.keys.select {|name|
  34620. (with.empty? or with.include?(name)) \
  34621. and not without.include?(name)
  34622. }
  34623. end
  34624. def extract_selection(list)
  34625. a = list.split(/,/)
  34626. a.each do |name|
  34627. setup_rb_error "no such package: #{name}" unless @installers.key?(name)
  34628. end
  34629. a
  34630. end
  34631. def print_usage(f)
  34632. super
  34633. f.puts 'Inluded packages:'
  34634. f.puts ' ' + @packages.sort.join(' ')
  34635. f.puts
  34636. end
  34637. #
  34638. # multi-package metaconfig API
  34639. #
  34640. attr_reader :packages
  34641. def declare_packages(list)
  34642. raise 'package list is empty' if list.empty?
  34643. list.each do |name|
  34644. raise "directory packages/#{name} does not exist"\
  34645. unless File.dir?("#{@ardir}/packages/#{name}")
  34646. end
  34647. @packages = list
  34648. end
  34649. #
  34650. # Task Handlers
  34651. #
  34652. def exec_config
  34653. run_hook 'pre-config'
  34654. each_selected_installers {|inst| inst.exec_config }
  34655. run_hook 'post-config'
  34656. @config.save # must be final
  34657. end
  34658. def exec_setup
  34659. run_hook 'pre-setup'
  34660. each_selected_installers {|inst| inst.exec_setup }
  34661. run_hook 'post-setup'
  34662. end
  34663. def exec_install
  34664. run_hook 'pre-install'
  34665. each_selected_installers {|inst| inst.exec_install }
  34666. run_hook 'post-install'
  34667. end
  34668. def exec_clean
  34669. rm_f ConfigTable.savefile
  34670. run_hook 'pre-clean'
  34671. each_selected_installers {|inst| inst.exec_clean }
  34672. run_hook 'post-clean'
  34673. end
  34674. def exec_distclean
  34675. rm_f ConfigTable.savefile
  34676. run_hook 'pre-distclean'
  34677. each_selected_installers {|inst| inst.exec_distclean }
  34678. run_hook 'post-distclean'
  34679. end
  34680. #
  34681. # lib
  34682. #
  34683. def each_selected_installers
  34684. Dir.mkdir 'packages' unless File.dir?('packages')
  34685. @selected.each do |pack|
  34686. $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
  34687. Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
  34688. Dir.chdir "packages/#{pack}"
  34689. yield @installers[pack]
  34690. Dir.chdir '../..'
  34691. end
  34692. end
  34693. def verbose?
  34694. @options['verbose']
  34695. end
  34696. def no_harm?
  34697. @options['no-harm']
  34698. end
  34699. end
  34700. class Installer
  34701. FILETYPES = %w( bin lib ext data )
  34702. include HookScriptAPI
  34703. include HookUtils
  34704. include FileOperations
  34705. def initialize(config, opt, srcroot, objroot)
  34706. @config = config
  34707. @options = opt
  34708. @srcdir = File.expand_path(srcroot)
  34709. @objdir = File.expand_path(objroot)
  34710. @currdir = '.'
  34711. end
  34712. def inspect
  34713. "#<#{self.class} #{File.basename(@srcdir)}>"
  34714. end
  34715. #
  34716. # Hook Script API base methods
  34717. #
  34718. def srcdir_root
  34719. @srcdir
  34720. end
  34721. def objdir_root
  34722. @objdir
  34723. end
  34724. def relpath
  34725. @currdir
  34726. end
  34727. #
  34728. # configs/options
  34729. #
  34730. def no_harm?
  34731. @options['no-harm']
  34732. end
  34733. def verbose?
  34734. @options['verbose']
  34735. end
  34736. def verbose_off
  34737. begin
  34738. save, @options['verbose'] = @options['verbose'], false
  34739. yield
  34740. ensure
  34741. @options['verbose'] = save
  34742. end
  34743. end
  34744. #
  34745. # TASK config
  34746. #
  34747. def exec_config
  34748. exec_task_traverse 'config'
  34749. end
  34750. def config_dir_bin(rel)
  34751. end
  34752. def config_dir_lib(rel)
  34753. end
  34754. def config_dir_ext(rel)
  34755. extconf if extdir?(curr_srcdir())
  34756. end
  34757. def extconf
  34758. opt = @options['config-opt'].join(' ')
  34759. command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}"
  34760. end
  34761. def config_dir_data(rel)
  34762. end
  34763. #
  34764. # TASK setup
  34765. #
  34766. def exec_setup
  34767. exec_task_traverse 'setup'
  34768. end
  34769. def setup_dir_bin(rel)
  34770. all_files_in(curr_srcdir()).each do |fname|
  34771. adjust_shebang "#{curr_srcdir()}/#{fname}"
  34772. end
  34773. end
  34774. def adjust_shebang(path)
  34775. return if no_harm?
  34776. tmpfile = File.basename(path) + '.tmp'
  34777. begin
  34778. File.open(path, 'rb') {|r|
  34779. first = r.gets
  34780. return unless File.basename(config('rubypath')) == 'ruby'
  34781. return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby'
  34782. $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
  34783. File.open(tmpfile, 'wb') {|w|
  34784. w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
  34785. w.write r.read
  34786. }
  34787. move_file tmpfile, File.basename(path)
  34788. }
  34789. ensure
  34790. File.unlink tmpfile if File.exist?(tmpfile)
  34791. end
  34792. end
  34793. def setup_dir_lib(rel)
  34794. end
  34795. def setup_dir_ext(rel)
  34796. make if extdir?(curr_srcdir())
  34797. end
  34798. def setup_dir_data(rel)
  34799. end
  34800. #
  34801. # TASK install
  34802. #
  34803. def exec_install
  34804. rm_f 'InstalledFiles'
  34805. exec_task_traverse 'install'
  34806. end
  34807. def install_dir_bin(rel)
  34808. install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755
  34809. end
  34810. def install_dir_lib(rel)
  34811. install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644
  34812. end
  34813. def install_dir_ext(rel)
  34814. return unless extdir?(curr_srcdir())
  34815. install_files ruby_extentions('.'),
  34816. "#{config('sodir')}/#{File.dirname(rel)}",
  34817. 0555
  34818. end
  34819. def install_dir_data(rel)
  34820. install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644
  34821. end
  34822. def install_files(list, dest, mode)
  34823. mkdir_p dest, @options['install-prefix']
  34824. list.each do |fname|
  34825. install fname, dest, mode, @options['install-prefix']
  34826. end
  34827. end
  34828. def ruby_scripts
  34829. collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
  34830. end
  34831. # picked up many entries from cvs-1.11.1/src/ignore.c
  34832. reject_patterns = %w(
  34833. core RCSLOG tags TAGS .make.state
  34834. .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
  34835. *~ *.old *.bak *.BAK *.orig *.rej _$* *$
  34836. *.org *.in .*
  34837. )
  34838. mapping = {
  34839. '.' => '\.',
  34840. '$' => '\$',
  34841. '#' => '\#',
  34842. '*' => '.*'
  34843. }
  34844. REJECT_PATTERNS = Regexp.new('\A(?:' +
  34845. reject_patterns.map {|pat|
  34846. pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
  34847. }.join('|') +
  34848. ')\z')
  34849. def collect_filenames_auto
  34850. mapdir((existfiles() - hookfiles()).reject {|fname|
  34851. REJECT_PATTERNS =~ fname
  34852. })
  34853. end
  34854. def existfiles
  34855. all_files_in(curr_srcdir()) | all_files_in('.')
  34856. end
  34857. def hookfiles
  34858. %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
  34859. %w( config setup install clean ).map {|t| sprintf(fmt, t) }
  34860. }.flatten
  34861. end
  34862. def mapdir(filelist)
  34863. filelist.map {|fname|
  34864. if File.exist?(fname) # objdir
  34865. fname
  34866. else # srcdir
  34867. File.join(curr_srcdir(), fname)
  34868. end
  34869. }
  34870. end
  34871. def ruby_extentions(dir)
  34872. Dir.open(dir) {|d|
  34873. ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname }
  34874. if ents.empty?
  34875. setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
  34876. end
  34877. return ents
  34878. }
  34879. end
  34880. #
  34881. # TASK clean
  34882. #
  34883. def exec_clean
  34884. exec_task_traverse 'clean'
  34885. rm_f ConfigTable.savefile
  34886. rm_f 'InstalledFiles'
  34887. end
  34888. def clean_dir_bin(rel)
  34889. end
  34890. def clean_dir_lib(rel)
  34891. end
  34892. def clean_dir_ext(rel)
  34893. return unless extdir?(curr_srcdir())
  34894. make 'clean' if File.file?('Makefile')
  34895. end
  34896. def clean_dir_data(rel)
  34897. end
  34898. #
  34899. # TASK distclean
  34900. #
  34901. def exec_distclean
  34902. exec_task_traverse 'distclean'
  34903. rm_f ConfigTable.savefile
  34904. rm_f 'InstalledFiles'
  34905. end
  34906. def distclean_dir_bin(rel)
  34907. end
  34908. def distclean_dir_lib(rel)
  34909. end
  34910. def distclean_dir_ext(rel)
  34911. return unless extdir?(curr_srcdir())
  34912. make 'distclean' if File.file?('Makefile')
  34913. end
  34914. #
  34915. # lib
  34916. #
  34917. def exec_task_traverse(task)
  34918. run_hook "pre-#{task}"
  34919. FILETYPES.each do |type|
  34920. if config('without-ext') == 'yes' and type == 'ext'
  34921. $stderr.puts 'skipping ext/* by user option' if verbose?
  34922. next
  34923. end
  34924. traverse task, type, "#{task}_dir_#{type}"
  34925. end
  34926. run_hook "post-#{task}"
  34927. end
  34928. def traverse(task, rel, mid)
  34929. dive_into(rel) {
  34930. run_hook "pre-#{task}"
  34931. __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
  34932. all_dirs_in(curr_srcdir()).each do |d|
  34933. traverse task, "#{rel}/#{d}", mid
  34934. end
  34935. run_hook "post-#{task}"
  34936. }
  34937. end
  34938. def dive_into(rel)
  34939. return unless File.dir?("#{@srcdir}/#{rel}")
  34940. dir = File.basename(rel)
  34941. Dir.mkdir dir unless File.dir?(dir)
  34942. prevdir = Dir.pwd
  34943. Dir.chdir dir
  34944. $stderr.puts '---> ' + rel if verbose?
  34945. @currdir = rel
  34946. yield
  34947. Dir.chdir prevdir
  34948. $stderr.puts '<--- ' + rel if verbose?
  34949. @currdir = File.dirname(rel)
  34950. end
  34951. end
  34952. if $0 == __FILE__
  34953. begin
  34954. if multipackage_install?
  34955. ToplevelInstallerMulti.invoke
  34956. else
  34957. ToplevelInstaller.invoke
  34958. end
  34959. rescue SetupError
  34960. raise if $DEBUG
  34961. $stderr.puts $!.message
  34962. $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
  34963. exit 1
  34964. end
  34965. end
  34966. require File.dirname(__FILE__) + '/abstract_unit'
  34967. require 'webrick'
  34968. require 'webrick/log'
  34969. require 'singleton'
  34970. module ClientTest
  34971. class Person < ActionWebService::Struct
  34972. member :firstnames, [:string]
  34973. member :lastname, :string
  34974. def ==(other)
  34975. firstnames == other.firstnames && lastname == other.lastname
  34976. end
  34977. end
  34978. class Inner < ActionWebService::Struct
  34979. member :name, :string
  34980. end
  34981. class Outer < ActionWebService::Struct
  34982. member :name, :string
  34983. member :inner, Inner
  34984. end
  34985. class User < ActiveRecord::Base
  34986. end
  34987. module Accounting
  34988. class User < ActiveRecord::Base
  34989. end
  34990. end
  34991. class WithModel < ActionWebService::Struct
  34992. member :user, User
  34993. member :users, [User]
  34994. end
  34995. class WithMultiDimArray < ActionWebService::Struct
  34996. member :pref, [[:string]]
  34997. end
  34998. class API < ActionWebService::API::Base
  34999. api_method :void
  35000. api_method :normal, :expects => [:int, :int], :returns => [:int]
  35001. api_method :array_return, :returns => [[Person]]
  35002. api_method :struct_pass, :expects => [[Person]], :returns => [:bool]
  35003. api_method :nil_struct_return, :returns => [Person]
  35004. api_method :inner_nil, :returns => [Outer]
  35005. api_method :client_container, :returns => [:int]
  35006. api_method :named_parameters, :expects => [{:key=>:string}, {:id=>:int}]
  35007. api_method :thrower
  35008. api_method :user_return, :returns => [User]
  35009. api_method :with_model_return, :returns => [WithModel]
  35010. api_method :scoped_model_return, :returns => [Accounting::User]
  35011. api_method :multi_dim_return, :returns => [WithMultiDimArray]
  35012. end
  35013. class NullLogOut
  35014. def <<(*args); end
  35015. end
  35016. class Container < ActionController::Base
  35017. web_service_api API
  35018. attr_accessor :value_void
  35019. attr_accessor :value_normal
  35020. attr_accessor :value_array_return
  35021. attr_accessor :value_struct_pass
  35022. attr_accessor :value_named_parameters
  35023. def initialize
  35024. @session = @assigns = {}
  35025. @value_void = nil
  35026. @value_normal = nil
  35027. @value_array_return = nil
  35028. @value_struct_pass = nil
  35029. @value_named_parameters = nil
  35030. end
  35031. def void
  35032. @value_void = @method_params
  35033. end
  35034. def normal
  35035. @value_normal = @method_params
  35036. 5
  35037. end
  35038. def array_return
  35039. person = Person.new
  35040. person.firstnames = ["one", "two"]
  35041. person.lastname = "last"
  35042. @value_array_return = [person]
  35043. end
  35044. def struct_pass
  35045. @value_struct_pass = @method_params
  35046. true
  35047. end
  35048. def nil_struct_return
  35049. nil
  35050. end
  35051. def inner_nil
  35052. Outer.new :name => 'outer', :inner => nil
  35053. end
  35054. def client_container
  35055. 50
  35056. end
  35057. def named_parameters
  35058. @value_named_parameters = @method_params
  35059. end
  35060. def thrower
  35061. raise "Hi"
  35062. end
  35063. def user_return
  35064. User.find(1)
  35065. end
  35066. def with_model_return
  35067. WithModel.new :user => User.find(1), :users => User.find(:all)
  35068. end
  35069. def scoped_model_return
  35070. Accounting::User.find(1)
  35071. end
  35072. def multi_dim_return
  35073. WithMultiDimArray.new :pref => [%w{pref1 value1}, %w{pref2 value2}]
  35074. end
  35075. end
  35076. class AbstractClientLet < WEBrick::HTTPServlet::AbstractServlet
  35077. def initialize(controller)
  35078. @controller = controller
  35079. end
  35080. def get_instance(*args)
  35081. self
  35082. end
  35083. def require_path_info?
  35084. false
  35085. end
  35086. def do_GET(req, res)
  35087. raise WEBrick::HTTPStatus::MethodNotAllowed, "GET request not allowed."
  35088. end
  35089. def do_POST(req, res)
  35090. raise NotImplementedError
  35091. end
  35092. end
  35093. class AbstractServer
  35094. include ClientTest
  35095. include Singleton
  35096. attr :container
  35097. def initialize
  35098. @container = Container.new
  35099. @clientlet = create_clientlet(@container)
  35100. log = WEBrick::BasicLog.new(NullLogOut.new)
  35101. @server = WEBrick::HTTPServer.new(:Port => server_port, :Logger => log, :AccessLog => log)
  35102. @server.mount('/', @clientlet)
  35103. @thr = Thread.new { @server.start }
  35104. until @server.status == :Running; end
  35105. at_exit { @server.stop; @thr.join }
  35106. end
  35107. protected
  35108. def create_clientlet
  35109. raise NotImplementedError
  35110. end
  35111. def server_port
  35112. raise NotImplementedError
  35113. end
  35114. end
  35115. end
  35116. require File.dirname(__FILE__) + '/abstract_unit'
  35117. require 'stringio'
  35118. class ActionController::Base; def rescue_action(e) raise e end; end
  35119. module DispatcherTest
  35120. Utf8String = "One World Caf\303\251"
  35121. WsdlNamespace = 'http://rubyonrails.com/some/namespace'
  35122. class Node < ActiveRecord::Base
  35123. def initialize(*args)
  35124. super(*args)
  35125. @new_record = false
  35126. end
  35127. class << self
  35128. def name
  35129. "DispatcherTest::Node"
  35130. end
  35131. def columns(*args)
  35132. [
  35133. ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'),
  35134. ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'),
  35135. ActiveRecord::ConnectionAdapters::Column.new('description', nil, 'string'),
  35136. ]
  35137. end
  35138. def connection
  35139. self
  35140. end
  35141. end
  35142. end
  35143. class Person < ActionWebService::Struct
  35144. member :id, :int
  35145. member :name, :string
  35146. def ==(other)
  35147. self.id == other.id && self.name == other.name
  35148. end
  35149. end
  35150. class API < ActionWebService::API::Base
  35151. api_method :add, :expects => [:int, :int], :returns => [:int]
  35152. api_method :interceptee
  35153. api_method :struct_return, :returns => [[Node]]
  35154. api_method :void
  35155. end
  35156. class DirectAPI < ActionWebService::API::Base
  35157. api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
  35158. api_method :add2, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
  35159. api_method :before_filtered
  35160. api_method :after_filtered, :returns => [[:int]]
  35161. api_method :struct_return, :returns => [[Node]]
  35162. api_method :struct_pass, :expects => [{:person => Person}]
  35163. api_method :base_struct_return, :returns => [[Person]]
  35164. api_method :hash_struct_return, :returns => [[Person]]
  35165. api_method :thrower
  35166. api_method :void
  35167. api_method :test_utf8, :returns => [:string]
  35168. api_method :hex, :expects => [:base64], :returns => [:string]
  35169. api_method :unhex, :expects => [:string], :returns => [:base64]
  35170. api_method :time, :expects => [:time], :returns => [:time]
  35171. end
  35172. class VirtualAPI < ActionWebService::API::Base
  35173. default_api_method :fallback
  35174. end
  35175. class Service < ActionWebService::Base
  35176. web_service_api API
  35177. before_invocation :do_intercept, :only => [:interceptee]
  35178. attr :added
  35179. attr :intercepted
  35180. attr :void_called
  35181. def initialize
  35182. @void_called = false
  35183. end
  35184. def add(a, b)
  35185. @added = a + b
  35186. end
  35187. def interceptee
  35188. @intercepted = false
  35189. end
  35190. def struct_return
  35191. n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
  35192. n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
  35193. [n1, n2]
  35194. end
  35195. def void(*args)
  35196. @void_called = args
  35197. end
  35198. def do_intercept(name, args)
  35199. [false, "permission denied"]
  35200. end
  35201. end
  35202. class MTAPI < ActionWebService::API::Base
  35203. inflect_names false
  35204. api_method :getCategories, :returns => [[:string]]
  35205. api_method :bool, :returns => [:bool]
  35206. api_method :alwaysFail
  35207. end
  35208. class BloggerAPI < ActionWebService::API::Base
  35209. inflect_names false
  35210. api_method :getCategories, :returns => [[:string]]
  35211. api_method :str, :expects => [:int], :returns => [:string]
  35212. api_method :alwaysFail
  35213. end
  35214. class MTService < ActionWebService::Base
  35215. web_service_api MTAPI
  35216. def getCategories
  35217. ["mtCat1", "mtCat2"]
  35218. end
  35219. def bool
  35220. 'y'
  35221. end
  35222. def alwaysFail
  35223. raise "MT AlwaysFail"
  35224. end
  35225. end
  35226. class BloggerService < ActionWebService::Base
  35227. web_service_api BloggerAPI
  35228. def getCategories
  35229. ["bloggerCat1", "bloggerCat2"]
  35230. end
  35231. def str(int)
  35232. unless int.is_a?(Integer)
  35233. raise "Not an integer!"
  35234. end
  35235. 500 + int
  35236. end
  35237. def alwaysFail
  35238. raise "Blogger AlwaysFail"
  35239. end
  35240. end
  35241. class AbstractController < ActionController::Base
  35242. def generate_wsdl
  35243. @request ||= ::ActionController::TestRequest.new
  35244. to_wsdl
  35245. end
  35246. end
  35247. class DelegatedController < AbstractController
  35248. web_service_dispatching_mode :delegated
  35249. wsdl_namespace WsdlNamespace
  35250. web_service(:test_service) { @service ||= Service.new; @service }
  35251. end
  35252. class LayeredController < AbstractController
  35253. web_service_dispatching_mode :layered
  35254. wsdl_namespace WsdlNamespace
  35255. web_service(:mt) { @mt_service ||= MTService.new; @mt_service }
  35256. web_service(:blogger) { @blogger_service ||= BloggerService.new; @blogger_service }
  35257. end
  35258. class DirectController < AbstractController
  35259. web_service_api DirectAPI
  35260. web_service_dispatching_mode :direct
  35261. wsdl_namespace WsdlNamespace
  35262. before_invocation :alwaysfail, :only => [:before_filtered]
  35263. after_invocation :alwaysok, :only => [:after_filtered]
  35264. attr :added
  35265. attr :added2
  35266. attr :before_filter_called
  35267. attr :before_filter_target_called
  35268. attr :after_filter_called
  35269. attr :after_filter_target_called
  35270. attr :void_called
  35271. attr :struct_pass_value
  35272. def initialize
  35273. @before_filter_called = false
  35274. @before_filter_target_called = false
  35275. @after_filter_called = false
  35276. @after_filter_target_called = false
  35277. @void_called = false
  35278. @struct_pass_value = false
  35279. end
  35280. def add
  35281. @added = @params['a'] + @params['b']
  35282. end
  35283. def add2(a, b)
  35284. @added2 = a + b
  35285. end
  35286. def before_filtered
  35287. @before_filter_target_called = true
  35288. end
  35289. def after_filtered
  35290. @after_filter_target_called = true
  35291. [5, 6, 7]
  35292. end
  35293. def thrower
  35294. raise "Hi, I'm an exception"
  35295. end
  35296. def struct_return
  35297. n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
  35298. n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
  35299. [n1, n2]
  35300. end
  35301. def struct_pass(person)
  35302. @struct_pass_value = person
  35303. end
  35304. def base_struct_return
  35305. p1 = Person.new('id' => 1, 'name' => 'person1')
  35306. p2 = Person.new('id' => 2, 'name' => 'person2')
  35307. [p1, p2]
  35308. end
  35309. def hash_struct_return
  35310. p1 = { :id => '1', 'name' => 'test' }
  35311. p2 = { 'id' => '2', :name => 'person2' }
  35312. [p1, p2]
  35313. end
  35314. def void
  35315. @void_called = @method_params
  35316. end
  35317. def test_utf8
  35318. Utf8String
  35319. end
  35320. def hex(s)
  35321. return s.unpack("H*")[0]
  35322. end
  35323. def unhex(s)
  35324. return [s].pack("H*")
  35325. end
  35326. def time(t)
  35327. t
  35328. end
  35329. protected
  35330. def alwaysfail(method_name, params)
  35331. @before_filter_called = true
  35332. false
  35333. end
  35334. def alwaysok(method_name, params, return_value)
  35335. @after_filter_called = true
  35336. end
  35337. end
  35338. class VirtualController < AbstractController
  35339. web_service_api VirtualAPI
  35340. wsdl_namespace WsdlNamespace
  35341. def fallback
  35342. "fallback!"
  35343. end
  35344. end
  35345. end
  35346. module DispatcherCommonTests
  35347. def test_direct_dispatching
  35348. assert_equal(70, do_method_call(@direct_controller, 'Add', 20, 50))
  35349. assert_equal(70, @direct_controller.added)
  35350. assert_equal(50, do_method_call(@direct_controller, 'Add2', 25, 25))
  35351. assert_equal(50, @direct_controller.added2)
  35352. assert(@direct_controller.void_called == false)
  35353. assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?)
  35354. assert(@direct_controller.void_called == [])
  35355. result = do_method_call(@direct_controller, 'BaseStructReturn')
  35356. assert(result[0].is_a?(DispatcherTest::Person))
  35357. assert(result[1].is_a?(DispatcherTest::Person))
  35358. assert_equal("cafe", do_method_call(@direct_controller, 'Hex', "\xca\xfe"))
  35359. assert_equal("\xca\xfe", do_method_call(@direct_controller, 'Unhex', "cafe"))
  35360. time = Time.gm(1998, "Feb", 02, 15, 12, 01)
  35361. assert_equal(time, do_method_call(@direct_controller, 'Time', time))
  35362. end
  35363. def test_direct_entrypoint
  35364. assert(@direct_controller.respond_to?(:api))
  35365. end
  35366. def test_virtual_dispatching
  35367. assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualOne'))
  35368. assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualTwo'))
  35369. end
  35370. def test_direct_filtering
  35371. assert_equal(false, @direct_controller.before_filter_called)
  35372. assert_equal(false, @direct_controller.before_filter_target_called)
  35373. do_method_call(@direct_controller, 'BeforeFiltered')
  35374. assert_equal(true, @direct_controller.before_filter_called)
  35375. assert_equal(false, @direct_controller.before_filter_target_called)
  35376. assert_equal(false, @direct_controller.after_filter_called)
  35377. assert_equal(false, @direct_controller.after_filter_target_called)
  35378. assert_equal([5, 6, 7], do_method_call(@direct_controller, 'AfterFiltered'))
  35379. assert_equal(true, @direct_controller.after_filter_called)
  35380. assert_equal(true, @direct_controller.after_filter_target_called)
  35381. end
  35382. def test_delegated_dispatching
  35383. assert_equal(130, do_method_call(@delegated_controller, 'Add', 50, 80))
  35384. service = @delegated_controller.web_service_object(:test_service)
  35385. assert_equal(130, service.added)
  35386. @delegated_controller.web_service_exception_reporting = true
  35387. assert(service.intercepted.nil?)
  35388. result = do_method_call(@delegated_controller, 'Interceptee')
  35389. assert(service.intercepted.nil?)
  35390. assert(is_exception?(result))
  35391. assert_match(/permission denied/, exception_message(result))
  35392. result = do_method_call(@delegated_controller, 'NonExistentMethod')
  35393. assert(is_exception?(result))
  35394. assert_match(/NonExistentMethod/, exception_message(result))
  35395. assert(service.void_called == false)
  35396. assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?)
  35397. assert(service.void_called == [])
  35398. end
  35399. def test_garbage_request
  35400. [@direct_controller, @delegated_controller].each do |controller|
  35401. controller.class.web_service_exception_reporting = true
  35402. send_garbage_request = lambda do
  35403. service_name = service_name(controller)
  35404. request = protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest)
  35405. response = ActionController::TestResponse.new
  35406. controller.process(request, response)
  35407. # puts response.body
  35408. assert(response.headers['Status'] =~ /^500/)
  35409. end
  35410. send_garbage_request.call
  35411. controller.class.web_service_exception_reporting = false
  35412. send_garbage_request.call
  35413. end
  35414. end
  35415. def test_exception_marshaling
  35416. @direct_controller.web_service_exception_reporting = true
  35417. result = do_method_call(@direct_controller, 'Thrower')
  35418. assert(is_exception?(result))
  35419. assert_equal("Hi, I'm an exception", exception_message(result))
  35420. @direct_controller.web_service_exception_reporting = false
  35421. result = do_method_call(@direct_controller, 'Thrower')
  35422. assert(exception_message(result) != "Hi, I'm an exception")
  35423. end
  35424. def test_ar_struct_return
  35425. [@direct_controller, @delegated_controller].each do |controller|
  35426. result = do_method_call(controller, 'StructReturn')
  35427. assert(result[0].is_a?(DispatcherTest::Node))
  35428. assert(result[1].is_a?(DispatcherTest::Node))
  35429. assert_equal('node1', result[0].name)
  35430. assert_equal('node2', result[1].name)
  35431. end
  35432. end
  35433. def test_casting
  35434. assert_equal 70, do_method_call(@direct_controller, 'Add', "50", "20")
  35435. assert_equal false, @direct_controller.struct_pass_value
  35436. person = DispatcherTest::Person.new(:id => 1, :name => 'test')
  35437. result = do_method_call(@direct_controller, 'StructPass', person)
  35438. assert(nil == result || true == result)
  35439. assert_equal person, @direct_controller.struct_pass_value
  35440. assert !person.equal?(@direct_controller.struct_pass_value)
  35441. result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test'})
  35442. case
  35443. when soap?
  35444. assert_equal(person, @direct_controller.struct_pass_value)
  35445. assert !person.equal?(@direct_controller.struct_pass_value)
  35446. when xmlrpc?
  35447. assert_equal(person, @direct_controller.struct_pass_value)
  35448. assert !person.equal?(@direct_controller.struct_pass_value)
  35449. end
  35450. assert_equal person, do_method_call(@direct_controller, 'HashStructReturn')[0]
  35451. result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test', 'nonexistent_attribute' => 'value'})
  35452. case
  35453. when soap?
  35454. assert_equal(person, @direct_controller.struct_pass_value)
  35455. assert !person.equal?(@direct_controller.struct_pass_value)
  35456. when xmlrpc?
  35457. assert_equal(person, @direct_controller.struct_pass_value)
  35458. assert !person.equal?(@direct_controller.struct_pass_value)
  35459. end
  35460. end
  35461. def test_logging
  35462. buf = ""
  35463. ActionController::Base.logger = Logger.new(StringIO.new(buf))
  35464. test_casting
  35465. test_garbage_request
  35466. test_exception_marshaling
  35467. ActionController::Base.logger = nil
  35468. assert_match /Web Service Response/, buf
  35469. assert_match /Web Service Request/, buf
  35470. end
  35471. protected
  35472. def service_name(container)
  35473. raise NotImplementedError
  35474. end
  35475. def exception_message(obj)
  35476. raise NotImplementedError
  35477. end
  35478. def is_exception?(obj)
  35479. raise NotImplementedError
  35480. end
  35481. def protocol
  35482. @protocol
  35483. end
  35484. def soap?
  35485. protocol.is_a? ActionWebService::Protocol::Soap::SoapProtocol
  35486. end
  35487. def xmlrpc?
  35488. protocol.is_a? ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
  35489. end
  35490. def do_method_call(container, public_method_name, *params)
  35491. request_env = {}
  35492. mode = container.web_service_dispatching_mode
  35493. case mode
  35494. when :direct
  35495. service_name = service_name(container)
  35496. api = container.class.web_service_api
  35497. method = api.public_api_method_instance(public_method_name)
  35498. when :delegated
  35499. service_name = service_name(container)
  35500. api = container.web_service_object(service_name).class.web_service_api
  35501. method = api.public_api_method_instance(public_method_name)
  35502. when :layered
  35503. service_name = nil
  35504. real_method_name = nil
  35505. if public_method_name =~ /^([^\.]+)\.(.*)$/
  35506. service_name = $1
  35507. real_method_name = $2
  35508. end
  35509. if soap?
  35510. public_method_name = real_method_name
  35511. request_env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{real_method_name}"
  35512. end
  35513. api = container.web_service_object(service_name.to_sym).class.web_service_api rescue nil
  35514. method = api.public_api_method_instance(real_method_name) rescue nil
  35515. service_name = self.service_name(container)
  35516. end
  35517. protocol.register_api(api)
  35518. virtual = false
  35519. unless method
  35520. virtual = true
  35521. method ||= ActionWebService::API::Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
  35522. end
  35523. body = protocol.encode_request(public_method_name, params.dup, method.expects)
  35524. # puts body
  35525. ap_request = protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest)
  35526. ap_request.env.update(request_env)
  35527. ap_response = ActionController::TestResponse.new
  35528. container.process(ap_request, ap_response)
  35529. # puts ap_response.body
  35530. @response_body = ap_response.body
  35531. public_method_name, return_value = protocol.decode_response(ap_response.body)
  35532. unless is_exception?(return_value) || virtual
  35533. return_value = method.cast_returns(return_value)
  35534. end
  35535. if soap?
  35536. # http://dev.rubyonrails.com/changeset/920
  35537. assert_match(/Response$/, public_method_name) unless public_method_name == "fault"
  35538. end
  35539. return_value
  35540. end
  35541. end
  35542. ENV["RAILS_ENV"] = "test"
  35543. $:.unshift(File.dirname(__FILE__) + '/../lib')
  35544. $:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
  35545. $:.unshift(File.dirname(__FILE__) + '/../../actionpack/lib')
  35546. $:.unshift(File.dirname(__FILE__) + '/../../activerecord/lib')
  35547. require 'test/unit'
  35548. require 'action_web_service'
  35549. require 'action_controller'
  35550. require 'action_controller/test_process'
  35551. ActionController::Base.logger = nil
  35552. ActionController::Base.ignore_missing_templates = true
  35553. begin
  35554. PATH_TO_AR = File.dirname(__FILE__) + '/../../activerecord'
  35555. require "#{PATH_TO_AR}/lib/active_record" unless Object.const_defined?(:ActiveRecord)
  35556. require "#{PATH_TO_AR}/lib/active_record/fixtures" unless Object.const_defined?(:Fixtures)
  35557. rescue Object => e
  35558. fail "\nFailed to load activerecord: #{e}"
  35559. end
  35560. ActiveRecord::Base.establish_connection(
  35561. :adapter => "mysql",
  35562. :username => "rails",
  35563. :encoding => "utf8",
  35564. :database => "activewebservice_unittest"
  35565. )
  35566. ActiveRecord::Base.connection
  35567. Test::Unit::TestCase.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
  35568. # restore default raw_post functionality
  35569. class ActionController::TestRequest
  35570. def raw_post
  35571. super
  35572. end
  35573. endrequire File.dirname(__FILE__) + '/abstract_unit'
  35574. module APITest
  35575. class API < ActionWebService::API::Base
  35576. api_method :void
  35577. api_method :expects_and_returns, :expects_and_returns => [:string]
  35578. api_method :expects, :expects => [:int, :bool]
  35579. api_method :returns, :returns => [:int, [:string]]
  35580. api_method :named_signature, :expects => [{:appkey=>:int}, {:publish=>:bool}]
  35581. api_method :string_types, :expects => ['int', 'string', 'bool', 'base64']
  35582. api_method :class_types, :expects => [TrueClass, Bignum, String]
  35583. end
  35584. end
  35585. class TC_API < Test::Unit::TestCase
  35586. API = APITest::API
  35587. def test_api_method_declaration
  35588. %w(
  35589. void
  35590. expects_and_returns
  35591. expects
  35592. returns
  35593. named_signature
  35594. string_types
  35595. class_types
  35596. ).each do |name|
  35597. name = name.to_sym
  35598. public_name = API.public_api_method_name(name)
  35599. assert(API.has_api_method?(name))
  35600. assert(API.has_public_api_method?(public_name))
  35601. assert(API.api_method_name(public_name) == name)
  35602. assert(API.api_methods.has_key?(name))
  35603. end
  35604. end
  35605. def test_signature_canonicalization
  35606. assert_equal(nil, API.api_methods[:void].expects)
  35607. assert_equal(nil, API.api_methods[:void].returns)
  35608. assert_equal([String], API.api_methods[:expects_and_returns].expects.map{|x| x.type_class})
  35609. assert_equal([String], API.api_methods[:expects_and_returns].returns.map{|x| x.type_class})
  35610. assert_equal([Integer, TrueClass], API.api_methods[:expects].expects.map{|x| x.type_class})
  35611. assert_equal(nil, API.api_methods[:expects].returns)
  35612. assert_equal(nil, API.api_methods[:returns].expects)
  35613. assert_equal([Integer, [String]], API.api_methods[:returns].returns.map{|x| x.array?? [x.element_type.type_class] : x.type_class})
  35614. assert_equal([[:appkey, Integer], [:publish, TrueClass]], API.api_methods[:named_signature].expects.map{|x| [x.name, x.type_class]})
  35615. assert_equal(nil, API.api_methods[:named_signature].returns)
  35616. assert_equal([Integer, String, TrueClass, ActionWebService::Base64], API.api_methods[:string_types].expects.map{|x| x.type_class})
  35617. assert_equal(nil, API.api_methods[:string_types].returns)
  35618. assert_equal([TrueClass, Integer, String], API.api_methods[:class_types].expects.map{|x| x.type_class})
  35619. assert_equal(nil, API.api_methods[:class_types].returns)
  35620. end
  35621. def test_not_instantiable
  35622. assert_raises(NoMethodError) do
  35623. API.new
  35624. end
  35625. end
  35626. def test_api_errors
  35627. assert_raises(ActionWebService::ActionWebServiceError) do
  35628. klass = Class.new(ActionWebService::API::Base) do
  35629. api_method :test, :expects => [ActiveRecord::Base]
  35630. end
  35631. end
  35632. klass = Class.new(ActionWebService::API::Base) do
  35633. allow_active_record_expects true
  35634. api_method :test2, :expects => [ActiveRecord::Base]
  35635. end
  35636. assert_raises(ActionWebService::ActionWebServiceError) do
  35637. klass = Class.new(ActionWebService::API::Base) do
  35638. api_method :test, :invalid => [:int]
  35639. end
  35640. end
  35641. end
  35642. def test_parameter_names
  35643. method = API.api_methods[:named_signature]
  35644. assert_equal 0, method.expects_index_of(:appkey)
  35645. assert_equal 1, method.expects_index_of(:publish)
  35646. assert_equal 1, method.expects_index_of('publish')
  35647. assert_equal 0, method.expects_index_of('appkey')
  35648. assert_equal -1, method.expects_index_of('blah')
  35649. assert_equal -1, method.expects_index_of(:missing)
  35650. assert_equal -1, API.api_methods[:void].expects_index_of('test')
  35651. end
  35652. def test_parameter_hash
  35653. method = API.api_methods[:named_signature]
  35654. hash = method.expects_to_hash([5, false])
  35655. assert_equal({:appkey => 5, :publish => false}, hash)
  35656. end
  35657. def test_api_methods_compat
  35658. sig = API.api_methods[:named_signature][:expects]
  35659. assert_equal [{:appkey=>Integer}, {:publish=>TrueClass}], sig
  35660. end
  35661. def test_to_s
  35662. assert_equal 'void Expects(int param0, bool param1)', APITest::API.api_methods[:expects].to_s
  35663. end
  35664. end
  35665. class AutoLoadAPI < ActionWebService::API::Base
  35666. api_method :void
  35667. end
  35668. require File.dirname(__FILE__) + '/abstract_unit'
  35669. module BaseTest
  35670. class API < ActionWebService::API::Base
  35671. api_method :add, :expects => [:int, :int], :returns => [:int]
  35672. api_method :void
  35673. end
  35674. class PristineAPI < ActionWebService::API::Base
  35675. inflect_names false
  35676. api_method :add
  35677. api_method :under_score
  35678. end
  35679. class Service < ActionWebService::Base
  35680. web_service_api API
  35681. def add(a, b)
  35682. end
  35683. def void
  35684. end
  35685. end
  35686. class PristineService < ActionWebService::Base
  35687. web_service_api PristineAPI
  35688. def add
  35689. end
  35690. def under_score
  35691. end
  35692. end
  35693. end
  35694. class TC_Base < Test::Unit::TestCase
  35695. def test_options
  35696. assert(BaseTest::PristineService.web_service_api.inflect_names == false)
  35697. assert(BaseTest::Service.web_service_api.inflect_names == true)
  35698. end
  35699. end
  35700. require File.dirname(__FILE__) + '/abstract_unit'
  35701. module CastingTest
  35702. class API < ActionWebService::API::Base
  35703. api_method :int, :expects => [:int]
  35704. api_method :str, :expects => [:string]
  35705. api_method :base64, :expects => [:base64]
  35706. api_method :bool, :expects => [:bool]
  35707. api_method :float, :expects => [:float]
  35708. api_method :time, :expects => [:time]
  35709. api_method :datetime, :expects => [:datetime]
  35710. api_method :date, :expects => [:date]
  35711. api_method :int_array, :expects => [[:int]]
  35712. api_method :str_array, :expects => [[:string]]
  35713. api_method :bool_array, :expects => [[:bool]]
  35714. end
  35715. end
  35716. class TC_Casting < Test::Unit::TestCase
  35717. include CastingTest
  35718. def test_base_type_casting_valid
  35719. assert_equal 10000, cast_expects(:int, '10000')[0]
  35720. assert_equal '10000', cast_expects(:str, 10000)[0]
  35721. base64 = cast_expects(:base64, 10000)[0]
  35722. assert_equal '10000', base64
  35723. assert_instance_of ActionWebService::Base64, base64
  35724. [1, '1', 'true', 'y', 'yes'].each do |val|
  35725. assert_equal true, cast_expects(:bool, val)[0]
  35726. end
  35727. [0, '0', 'false', 'n', 'no'].each do |val|
  35728. assert_equal false, cast_expects(:bool, val)[0]
  35729. end
  35730. assert_equal 3.14159, cast_expects(:float, '3.14159')[0]
  35731. now = Time.at(Time.now.tv_sec)
  35732. casted = cast_expects(:time, now.to_s)[0]
  35733. assert_equal now, casted
  35734. now = DateTime.now
  35735. assert_equal now.to_s, cast_expects(:datetime, now.to_s)[0].to_s
  35736. today = Date.today
  35737. assert_equal today, cast_expects(:date, today.to_s)[0]
  35738. end
  35739. def test_base_type_casting_invalid
  35740. assert_raises ArgumentError do
  35741. cast_expects(:int, 'this is not a number')
  35742. end
  35743. assert_raises ActionWebService::Casting::CastingError do
  35744. # neither true or false ;)
  35745. cast_expects(:bool, 'i always lie')
  35746. end
  35747. assert_raises ArgumentError do
  35748. cast_expects(:float, 'not a float')
  35749. end
  35750. assert_raises ArgumentError do
  35751. cast_expects(:time, '111111111111111111111111111111111')
  35752. end
  35753. assert_raises ArgumentError do
  35754. cast_expects(:datetime, '-1')
  35755. end
  35756. assert_raises ArgumentError do
  35757. cast_expects(:date, '')
  35758. end
  35759. end
  35760. def test_array_type_casting
  35761. assert_equal [1, 2, 3213992, 4], cast_expects(:int_array, ['1', '2', '3213992', '4'])[0]
  35762. assert_equal ['one', 'two', '5.0', '200', nil, 'true'], cast_expects(:str_array, [:one, 'two', 5.0, 200, nil, true])[0]
  35763. assert_equal [true, nil, true, true, false], cast_expects(:bool_array, ['1', nil, 'y', true, 'false'])[0]
  35764. end
  35765. def test_array_type_casting_failure
  35766. assert_raises ActionWebService::Casting::CastingError do
  35767. cast_expects(:bool_array, ['false', 'blahblah'])
  35768. end
  35769. assert_raises ArgumentError do
  35770. cast_expects(:int_array, ['1', '2.021', '4'])
  35771. end
  35772. end
  35773. private
  35774. def cast_expects(method_name, *args)
  35775. API.api_method_instance(method_name.to_sym).cast_expects([*args])
  35776. end
  35777. end
  35778. require File.dirname(__FILE__) + '/abstract_client'
  35779. module ClientSoapTest
  35780. PORT = 8998
  35781. class SoapClientLet < ClientTest::AbstractClientLet
  35782. def do_POST(req, res)
  35783. test_request = ActionController::TestRequest.new
  35784. test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
  35785. test_request.env['REQUEST_METHOD'] = "POST"
  35786. test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
  35787. test_request.env['HTTP_SOAPACTION'] = req.header['soapaction'][0]
  35788. test_request.env['RAW_POST_DATA'] = req.body
  35789. response = ActionController::TestResponse.new
  35790. @controller.process(test_request, response)
  35791. res.header['content-type'] = 'text/xml'
  35792. res.body = response.body
  35793. rescue Exception => e
  35794. $stderr.puts e.message
  35795. $stderr.puts e.backtrace.join("\n")
  35796. end
  35797. end
  35798. class ClientContainer < ActionController::Base
  35799. web_client_api :client, :soap, "http://localhost:#{PORT}/client/api", :api => ClientTest::API
  35800. web_client_api :invalid, :null, "", :api => true
  35801. def get_client
  35802. client
  35803. end
  35804. def get_invalid
  35805. invalid
  35806. end
  35807. end
  35808. class SoapServer < ClientTest::AbstractServer
  35809. def create_clientlet(controller)
  35810. SoapClientLet.new(controller)
  35811. end
  35812. def server_port
  35813. PORT
  35814. end
  35815. end
  35816. end
  35817. class TC_ClientSoap < Test::Unit::TestCase
  35818. include ClientTest
  35819. include ClientSoapTest
  35820. fixtures :users
  35821. def setup
  35822. @server = SoapServer.instance
  35823. @container = @server.container
  35824. @client = ActionWebService::Client::Soap.new(API, "http://localhost:#{@server.server_port}/client/api")
  35825. end
  35826. def test_void
  35827. assert(@container.value_void.nil?)
  35828. @client.void
  35829. assert(!@container.value_void.nil?)
  35830. end
  35831. def test_normal
  35832. assert(@container.value_normal.nil?)
  35833. assert_equal(5, @client.normal(5, 6))
  35834. assert_equal([5, 6], @container.value_normal)
  35835. assert_equal(5, @client.normal("7", "8"))
  35836. assert_equal([7, 8], @container.value_normal)
  35837. assert_equal(5, @client.normal(true, false))
  35838. end
  35839. def test_array_return
  35840. assert(@container.value_array_return.nil?)
  35841. new_person = Person.new
  35842. new_person.firstnames = ["one", "two"]
  35843. new_person.lastname = "last"
  35844. assert_equal([new_person], @client.array_return)
  35845. assert_equal([new_person], @container.value_array_return)
  35846. end
  35847. def test_struct_pass
  35848. assert(@container.value_struct_pass.nil?)
  35849. new_person = Person.new
  35850. new_person.firstnames = ["one", "two"]
  35851. new_person.lastname = "last"
  35852. assert_equal(true, @client.struct_pass([new_person]))
  35853. assert_equal([[new_person]], @container.value_struct_pass)
  35854. end
  35855. def test_nil_struct_return
  35856. assert_nil @client.nil_struct_return
  35857. end
  35858. def test_inner_nil
  35859. outer = @client.inner_nil
  35860. assert_equal 'outer', outer.name
  35861. assert_nil outer.inner
  35862. end
  35863. def test_client_container
  35864. assert_equal(50, ClientContainer.new.get_client.client_container)
  35865. assert(ClientContainer.new.get_invalid.nil?)
  35866. end
  35867. def test_named_parameters
  35868. assert(@container.value_named_parameters.nil?)
  35869. assert(@client.named_parameters("key", 5).nil?)
  35870. assert_equal(["key", 5], @container.value_named_parameters)
  35871. end
  35872. def test_capitalized_method_name
  35873. @container.value_normal = nil
  35874. assert_equal(5, @client.Normal(5, 6))
  35875. assert_equal([5, 6], @container.value_normal)
  35876. @container.value_normal = nil
  35877. end
  35878. def test_model_return
  35879. user = @client.user_return
  35880. assert_equal 1, user.id
  35881. assert_equal 'Kent', user.name
  35882. assert user.active?
  35883. assert_kind_of Date, user.created_on
  35884. assert_equal Date.today, user.created_on
  35885. end
  35886. def test_with_model
  35887. with_model = @client.with_model_return
  35888. assert_equal 'Kent', with_model.user.name
  35889. assert_equal 2, with_model.users.size
  35890. with_model.users.each do |user|
  35891. assert_kind_of User, user
  35892. end
  35893. end
  35894. def test_scoped_model_return
  35895. scoped_model = @client.scoped_model_return
  35896. assert_kind_of Accounting::User, scoped_model
  35897. assert_equal 'Kent', scoped_model.name
  35898. end
  35899. def test_multi_dim_return
  35900. md_struct = @client.multi_dim_return
  35901. assert_kind_of Array, md_struct.pref
  35902. assert_equal 2, md_struct.pref.size
  35903. assert_kind_of Array, md_struct.pref[0]
  35904. end
  35905. end
  35906. require File.dirname(__FILE__) + '/abstract_client'
  35907. module ClientXmlRpcTest
  35908. PORT = 8999
  35909. class XmlRpcClientLet < ClientTest::AbstractClientLet
  35910. def do_POST(req, res)
  35911. test_request = ActionController::TestRequest.new
  35912. test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
  35913. test_request.env['REQUEST_METHOD'] = "POST"
  35914. test_request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
  35915. test_request.env['RAW_POST_DATA'] = req.body
  35916. response = ActionController::TestResponse.new
  35917. @controller.process(test_request, response)
  35918. res.header['content-type'] = 'text/xml'
  35919. res.body = response.body
  35920. # puts res.body
  35921. rescue Exception => e
  35922. $stderr.puts e.message
  35923. $stderr.puts e.backtrace.join("\n")
  35924. end
  35925. end
  35926. class ClientContainer < ActionController::Base
  35927. web_client_api :client, :xmlrpc, "http://localhost:#{PORT}/client/api", :api => ClientTest::API
  35928. def get_client
  35929. client
  35930. end
  35931. end
  35932. class XmlRpcServer < ClientTest::AbstractServer
  35933. def create_clientlet(controller)
  35934. XmlRpcClientLet.new(controller)
  35935. end
  35936. def server_port
  35937. PORT
  35938. end
  35939. end
  35940. end
  35941. class TC_ClientXmlRpc < Test::Unit::TestCase
  35942. include ClientTest
  35943. include ClientXmlRpcTest
  35944. fixtures :users
  35945. def setup
  35946. @server = XmlRpcServer.instance
  35947. @container = @server.container
  35948. @client = ActionWebService::Client::XmlRpc.new(API, "http://localhost:#{@server.server_port}/client/api")
  35949. end
  35950. def test_void
  35951. assert(@container.value_void.nil?)
  35952. @client.void
  35953. assert(!@container.value_void.nil?)
  35954. end
  35955. def test_normal
  35956. assert(@container.value_normal.nil?)
  35957. assert_equal(5, @client.normal(5, 6))
  35958. assert_equal([5, 6], @container.value_normal)
  35959. assert_equal(5, @client.normal("7", "8"))
  35960. assert_equal([7, 8], @container.value_normal)
  35961. assert_equal(5, @client.normal(true, false))
  35962. end
  35963. def test_array_return
  35964. assert(@container.value_array_return.nil?)
  35965. new_person = Person.new
  35966. new_person.firstnames = ["one", "two"]
  35967. new_person.lastname = "last"
  35968. assert_equal([new_person], @client.array_return)
  35969. assert_equal([new_person], @container.value_array_return)
  35970. end
  35971. def test_struct_pass
  35972. assert(@container.value_struct_pass.nil?)
  35973. new_person = Person.new
  35974. new_person.firstnames = ["one", "two"]
  35975. new_person.lastname = "last"
  35976. assert_equal(true, @client.struct_pass([new_person]))
  35977. assert_equal([[new_person]], @container.value_struct_pass)
  35978. end
  35979. def test_nil_struct_return
  35980. assert_equal false, @client.nil_struct_return
  35981. end
  35982. def test_inner_nil
  35983. outer = @client.inner_nil
  35984. assert_equal 'outer', outer.name
  35985. assert_nil outer.inner
  35986. end
  35987. def test_client_container
  35988. assert_equal(50, ClientContainer.new.get_client.client_container)
  35989. end
  35990. def test_named_parameters
  35991. assert(@container.value_named_parameters.nil?)
  35992. assert_equal(false, @client.named_parameters("xxx", 7))
  35993. assert_equal(["xxx", 7], @container.value_named_parameters)
  35994. end
  35995. def test_exception
  35996. assert_raises(ActionWebService::Client::ClientError) do
  35997. assert(@client.thrower)
  35998. end
  35999. end
  36000. def test_invalid_signature
  36001. assert_raises(ArgumentError) do
  36002. @client.normal
  36003. end
  36004. end
  36005. def test_model_return
  36006. user = @client.user_return
  36007. assert_equal 1, user.id
  36008. assert_equal 'Kent', user.name
  36009. assert user.active?
  36010. assert_kind_of Time, user.created_on
  36011. assert_equal Time.utc(Time.now.year, Time.now.month, Time.now.day), user.created_on
  36012. end
  36013. def test_with_model
  36014. with_model = @client.with_model_return
  36015. assert_equal 'Kent', with_model.user.name
  36016. assert_equal 2, with_model.users.size
  36017. with_model.users.each do |user|
  36018. assert_kind_of User, user
  36019. end
  36020. end
  36021. def test_scoped_model_return
  36022. scoped_model = @client.scoped_model_return
  36023. assert_kind_of Accounting::User, scoped_model
  36024. assert_equal 'Kent', scoped_model.name
  36025. end
  36026. def test_multi_dim_return
  36027. md_struct = @client.multi_dim_return
  36028. assert_kind_of Array, md_struct.pref
  36029. assert_equal 2, md_struct.pref.size
  36030. assert_kind_of Array, md_struct.pref[0]
  36031. end
  36032. end
  36033. require File.dirname(__FILE__) + '/abstract_unit'
  36034. module ContainerTest
  36035. $immediate_service = Object.new
  36036. $deferred_service = Object.new
  36037. class DelegateContainer < ActionController::Base
  36038. web_service_dispatching_mode :delegated
  36039. attr :flag
  36040. attr :previous_flag
  36041. def initialize
  36042. @previous_flag = nil
  36043. @flag = true
  36044. end
  36045. web_service :immediate_service, $immediate_service
  36046. web_service(:deferred_service) { @previous_flag = @flag; @flag = false; $deferred_service }
  36047. end
  36048. class DirectContainer < ActionController::Base
  36049. web_service_dispatching_mode :direct
  36050. end
  36051. class InvalidContainer
  36052. include ActionWebService::Container::Direct
  36053. end
  36054. end
  36055. class TC_Container < Test::Unit::TestCase
  36056. include ContainerTest
  36057. def setup
  36058. @delegate_container = DelegateContainer.new
  36059. @direct_container = DirectContainer.new
  36060. end
  36061. def test_registration
  36062. assert(DelegateContainer.has_web_service?(:immediate_service))
  36063. assert(DelegateContainer.has_web_service?(:deferred_service))
  36064. assert(!DelegateContainer.has_web_service?(:fake_service))
  36065. assert_raises(ActionWebService::Container::Delegated::ContainerError) do
  36066. DelegateContainer.web_service('invalid')
  36067. end
  36068. end
  36069. def test_service_object
  36070. assert_raises(ActionWebService::Container::Delegated::ContainerError) do
  36071. @delegate_container.web_service_object(:nonexistent)
  36072. end
  36073. assert(@delegate_container.flag == true)
  36074. assert(@delegate_container.web_service_object(:immediate_service) == $immediate_service)
  36075. assert(@delegate_container.previous_flag.nil?)
  36076. assert(@delegate_container.flag == true)
  36077. assert(@delegate_container.web_service_object(:deferred_service) == $deferred_service)
  36078. assert(@delegate_container.previous_flag == true)
  36079. assert(@delegate_container.flag == false)
  36080. end
  36081. def test_direct_container
  36082. assert(DirectContainer.web_service_dispatching_mode == :direct)
  36083. end
  36084. def test_validity
  36085. assert_raises(ActionWebService::Container::Direct::ContainerError) do
  36086. InvalidContainer.web_service_api :test
  36087. end
  36088. assert_raises(ActionWebService::Container::Direct::ContainerError) do
  36089. InvalidContainer.web_service_api 50.0
  36090. end
  36091. end
  36092. end
  36093. $:.unshift(File.dirname(__FILE__) + '/apis')
  36094. require File.dirname(__FILE__) + '/abstract_dispatcher'
  36095. require 'wsdl/parser'
  36096. class ActionController::Base
  36097. class << self
  36098. alias :inherited_without_name_error :inherited
  36099. def inherited(child)
  36100. begin
  36101. inherited_without_name_error(child)
  36102. rescue NameError => e
  36103. end
  36104. end
  36105. end
  36106. end
  36107. class AutoLoadController < ActionController::Base; end
  36108. class FailingAutoLoadController < ActionController::Base; end
  36109. class BrokenAutoLoadController < ActionController::Base; end
  36110. class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
  36111. include DispatcherTest
  36112. include DispatcherCommonTests
  36113. def setup
  36114. @direct_controller = DirectController.new
  36115. @delegated_controller = DelegatedController.new
  36116. @virtual_controller = VirtualController.new
  36117. @layered_controller = LayeredController.new
  36118. @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@direct_controller)
  36119. end
  36120. def test_wsdl_generation
  36121. ensure_valid_wsdl_generation DelegatedController.new, DispatcherTest::WsdlNamespace
  36122. ensure_valid_wsdl_generation DirectController.new, DispatcherTest::WsdlNamespace
  36123. end
  36124. def test_wsdl_action
  36125. delegated_types = ensure_valid_wsdl_action DelegatedController.new
  36126. delegated_names = delegated_types.map{|x| x.name.name}
  36127. assert(delegated_names.include?('DispatcherTest..NodeArray'))
  36128. assert(delegated_names.include?('DispatcherTest..Node'))
  36129. direct_types = ensure_valid_wsdl_action DirectController.new
  36130. direct_names = direct_types.map{|x| x.name.name}
  36131. assert(direct_names.include?('DispatcherTest..NodeArray'))
  36132. assert(direct_names.include?('DispatcherTest..Node'))
  36133. assert(direct_names.include?('IntegerArray'))
  36134. end
  36135. def test_autoloading
  36136. assert(!AutoLoadController.web_service_api.nil?)
  36137. assert(AutoLoadController.web_service_api.has_public_api_method?('Void'))
  36138. assert(FailingAutoLoadController.web_service_api.nil?)
  36139. assert_raises(MissingSourceFile) do
  36140. FailingAutoLoadController.require_web_service_api :blah
  36141. end
  36142. assert_raises(ArgumentError) do
  36143. FailingAutoLoadController.require_web_service_api 50.0
  36144. end
  36145. assert(BrokenAutoLoadController.web_service_api.nil?)
  36146. end
  36147. def test_layered_dispatching
  36148. mt_cats = do_method_call(@layered_controller, 'mt.getCategories')
  36149. assert_equal(["mtCat1", "mtCat2"], mt_cats)
  36150. blogger_cats = do_method_call(@layered_controller, 'blogger.getCategories')
  36151. assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats)
  36152. end
  36153. def test_utf8
  36154. @direct_controller.web_service_exception_reporting = true
  36155. $KCODE = 'u'
  36156. assert_equal(Utf8String, do_method_call(@direct_controller, 'TestUtf8'))
  36157. retval = SOAP::Processor.unmarshal(@response_body).body.response
  36158. assert retval.is_a?(SOAP::SOAPString)
  36159. # If $KCODE is not set to UTF-8, any strings with non-ASCII UTF-8 data
  36160. # will be sent back as base64 by SOAP4R. By the time we get it here though,
  36161. # it will be decoded back into a string. So lets read the base64 value
  36162. # from the message body directly.
  36163. $KCODE = 'NONE'
  36164. do_method_call(@direct_controller, 'TestUtf8')
  36165. retval = SOAP::Processor.unmarshal(@response_body).body.response
  36166. assert retval.is_a?(SOAP::SOAPBase64)
  36167. assert_equal "T25lIFdvcmxkIENhZsOp", retval.data.to_s
  36168. end
  36169. protected
  36170. def exception_message(soap_fault_exception)
  36171. soap_fault_exception.detail.cause.message
  36172. end
  36173. def is_exception?(obj)
  36174. obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
  36175. obj.detail.cause.is_a?(Exception)
  36176. end
  36177. def service_name(container)
  36178. container.is_a?(DelegatedController) ? 'test_service' : 'api'
  36179. end
  36180. def ensure_valid_wsdl_generation(controller, expected_namespace)
  36181. wsdl = controller.generate_wsdl
  36182. ensure_valid_wsdl(controller, wsdl, expected_namespace)
  36183. end
  36184. def ensure_valid_wsdl(controller, wsdl, expected_namespace)
  36185. definitions = WSDL::Parser.new.parse(wsdl)
  36186. assert(definitions.is_a?(WSDL::Definitions))
  36187. definitions.bindings.each do |binding|
  36188. assert(binding.name.name.index(':').nil?)
  36189. end
  36190. definitions.services.each do |service|
  36191. service.ports.each do |port|
  36192. assert(port.name.name.index(':').nil?)
  36193. end
  36194. end
  36195. types = definitions.collect_complextypes.map{|x| x.name}
  36196. types.each do |type|
  36197. assert(type.namespace == expected_namespace)
  36198. end
  36199. location = definitions.services[0].ports[0].soap_address.location
  36200. if controller.is_a?(DelegatedController)
  36201. assert_match %r{http://localhost/dispatcher_test/delegated/test_service$}, location
  36202. elsif controller.is_a?(DirectController)
  36203. assert_match %r{http://localhost/dispatcher_test/direct/api$}, location
  36204. end
  36205. definitions.collect_complextypes
  36206. end
  36207. def ensure_valid_wsdl_action(controller)
  36208. test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' })
  36209. test_request.env['REQUEST_METHOD'] = 'GET'
  36210. test_request.env['HTTP_HOST'] = 'localhost'
  36211. test_response = ActionController::TestResponse.new
  36212. wsdl = controller.process(test_request, test_response).body
  36213. ensure_valid_wsdl(controller, wsdl, DispatcherTest::WsdlNamespace)
  36214. end
  36215. end
  36216. require File.dirname(__FILE__) + '/abstract_dispatcher'
  36217. class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase
  36218. include DispatcherTest
  36219. include DispatcherCommonTests
  36220. def setup
  36221. @direct_controller = DirectController.new
  36222. @delegated_controller = DelegatedController.new
  36223. @layered_controller = LayeredController.new
  36224. @virtual_controller = VirtualController.new
  36225. @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@direct_controller)
  36226. end
  36227. def test_layered_dispatching
  36228. mt_cats = do_method_call(@layered_controller, 'mt.getCategories')
  36229. assert_equal(["mtCat1", "mtCat2"], mt_cats)
  36230. blogger_cats = do_method_call(@layered_controller, 'blogger.getCategories')
  36231. assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats)
  36232. end
  36233. def test_multicall
  36234. response = do_method_call(@layered_controller, 'system.multicall', [
  36235. {'methodName' => 'mt.getCategories'},
  36236. {'methodName' => 'blogger.getCategories'},
  36237. {'methodName' => 'mt.bool'},
  36238. {'methodName' => 'blogger.str', 'params' => ['2000']},
  36239. {'methodName' => 'mt.alwaysFail'},
  36240. {'methodName' => 'blogger.alwaysFail'},
  36241. {'methodName' => 'mt.blah'},
  36242. {'methodName' => 'blah.blah'}
  36243. ])
  36244. assert_equal [
  36245. [["mtCat1", "mtCat2"]],
  36246. [["bloggerCat1", "bloggerCat2"]],
  36247. [true],
  36248. ["2500"],
  36249. {"faultCode" => 3, "faultString" => "MT AlwaysFail"},
  36250. {"faultCode" => 3, "faultString" => "Blogger AlwaysFail"},
  36251. {"faultCode" => 4, "faultMessage" => "no such method 'blah' on API DispatcherTest::MTAPI"},
  36252. {"faultCode" => 4, "faultMessage" => "no such web service 'blah'"}
  36253. ], response
  36254. end
  36255. protected
  36256. def exception_message(xmlrpc_fault_exception)
  36257. xmlrpc_fault_exception.faultString
  36258. end
  36259. def is_exception?(obj)
  36260. obj.is_a?(XMLRPC::FaultException)
  36261. end
  36262. def service_name(container)
  36263. container.is_a?(DelegatedController) ? 'test_service' : 'api'
  36264. end
  36265. end
  36266. require File.dirname(__FILE__) + '/abstract_unit'
  36267. module InvocationTest
  36268. class API < ActionWebService::API::Base
  36269. api_method :add, :expects => [:int, :int], :returns => [:int]
  36270. api_method :transmogrify, :expects_and_returns => [:string]
  36271. api_method :fail_with_reason
  36272. api_method :fail_generic
  36273. api_method :no_before
  36274. api_method :no_after
  36275. api_method :only_one
  36276. api_method :only_two
  36277. end
  36278. class Interceptor
  36279. attr :args
  36280. def initialize
  36281. @args = nil
  36282. end
  36283. def intercept(*args)
  36284. @args = args
  36285. end
  36286. end
  36287. InterceptorClass = Interceptor.new
  36288. class Service < ActionController::Base
  36289. web_service_api API
  36290. before_invocation :intercept_before, :except => [:no_before]
  36291. after_invocation :intercept_after, :except => [:no_after]
  36292. prepend_after_invocation :intercept_after_first, :except => [:no_after]
  36293. prepend_before_invocation :intercept_only, :only => [:only_one, :only_two]
  36294. after_invocation(:only => [:only_one]) do |*args|
  36295. args[0].instance_variable_set('@block_invoked', args[1])
  36296. end
  36297. after_invocation InterceptorClass, :only => [:only_one]
  36298. attr_accessor :before_invoked
  36299. attr_accessor :after_invoked
  36300. attr_accessor :after_first_invoked
  36301. attr_accessor :only_invoked
  36302. attr_accessor :block_invoked
  36303. attr_accessor :invocation_result
  36304. def initialize
  36305. @before_invoked = nil
  36306. @after_invoked = nil
  36307. @after_first_invoked = nil
  36308. @only_invoked = nil
  36309. @invocation_result = nil
  36310. @block_invoked = nil
  36311. end
  36312. def add(a, b)
  36313. a + b
  36314. end
  36315. def transmogrify(str)
  36316. str.upcase
  36317. end
  36318. def fail_with_reason
  36319. end
  36320. def fail_generic
  36321. end
  36322. def no_before
  36323. 5
  36324. end
  36325. def no_after
  36326. end
  36327. def only_one
  36328. end
  36329. def only_two
  36330. end
  36331. protected
  36332. def intercept_before(name, args)
  36333. @before_invoked = name
  36334. return [false, "permission denied"] if name == :fail_with_reason
  36335. return false if name == :fail_generic
  36336. end
  36337. def intercept_after(name, args, result)
  36338. @after_invoked = name
  36339. @invocation_result = result
  36340. end
  36341. def intercept_after_first(name, args, result)
  36342. @after_first_invoked = name
  36343. end
  36344. def intercept_only(name, args)
  36345. raise "Interception error" unless name == :only_one || name == :only_two
  36346. @only_invoked = name
  36347. end
  36348. end
  36349. end
  36350. class TC_Invocation < Test::Unit::TestCase
  36351. include ActionWebService::Invocation
  36352. def setup
  36353. @service = InvocationTest::Service.new
  36354. end
  36355. def test_invocation
  36356. assert(perform_invocation(:add, 5, 10) == 15)
  36357. assert(perform_invocation(:transmogrify, "hello") == "HELLO")
  36358. assert_raises(NoMethodError) do
  36359. perform_invocation(:nonexistent_method_xyzzy)
  36360. end
  36361. end
  36362. def test_interceptor_registration
  36363. assert(InvocationTest::Service.before_invocation_interceptors.length == 2)
  36364. assert(InvocationTest::Service.after_invocation_interceptors.length == 4)
  36365. assert_equal(:intercept_only, InvocationTest::Service.before_invocation_interceptors[0])
  36366. assert_equal(:intercept_after_first, InvocationTest::Service.after_invocation_interceptors[0])
  36367. end
  36368. def test_interception
  36369. assert(@service.before_invoked.nil?)
  36370. assert(@service.after_invoked.nil?)
  36371. assert(@service.only_invoked.nil?)
  36372. assert(@service.block_invoked.nil?)
  36373. assert(@service.invocation_result.nil?)
  36374. perform_invocation(:add, 20, 50)
  36375. assert(@service.before_invoked == :add)
  36376. assert(@service.after_invoked == :add)
  36377. assert(@service.invocation_result == 70)
  36378. end
  36379. def test_interception_canceling
  36380. reason = nil
  36381. perform_invocation(:fail_with_reason){|r| reason = r}
  36382. assert(@service.before_invoked == :fail_with_reason)
  36383. assert(@service.after_invoked.nil?)
  36384. assert(@service.invocation_result.nil?)
  36385. assert(reason == "permission denied")
  36386. reason = true
  36387. @service.before_invoked = @service.after_invoked = @service.invocation_result = nil
  36388. perform_invocation(:fail_generic){|r| reason = r}
  36389. assert(@service.before_invoked == :fail_generic)
  36390. assert(@service.after_invoked.nil?)
  36391. assert(@service.invocation_result.nil?)
  36392. assert(reason == true)
  36393. end
  36394. def test_interception_except_conditions
  36395. perform_invocation(:no_before)
  36396. assert(@service.before_invoked.nil?)
  36397. assert(@service.after_first_invoked == :no_before)
  36398. assert(@service.after_invoked == :no_before)
  36399. assert(@service.invocation_result == 5)
  36400. @service.before_invoked = @service.after_invoked = @service.invocation_result = nil
  36401. perform_invocation(:no_after)
  36402. assert(@service.before_invoked == :no_after)
  36403. assert(@service.after_invoked.nil?)
  36404. assert(@service.invocation_result.nil?)
  36405. end
  36406. def test_interception_only_conditions
  36407. assert(@service.only_invoked.nil?)
  36408. perform_invocation(:only_one)
  36409. assert(@service.only_invoked == :only_one)
  36410. assert(@service.block_invoked == :only_one)
  36411. assert(InvocationTest::InterceptorClass.args[1] == :only_one)
  36412. @service.only_invoked = nil
  36413. perform_invocation(:only_two)
  36414. assert(@service.only_invoked == :only_two)
  36415. end
  36416. private
  36417. def perform_invocation(method_name, *args, &block)
  36418. @service.perform_invocation(method_name, args, &block)
  36419. end
  36420. end
  36421. require File.dirname(__FILE__) + '/abstract_unit'
  36422. ActionController::Routing::Routes.draw do |map|
  36423. map.connect '', :controller => 'scaffolded'
  36424. map.connect ':controller/:action/:id'
  36425. end
  36426. ActionController::Base.template_root = '.'
  36427. class ScaffoldPerson < ActionWebService::Struct
  36428. member :id, :int
  36429. member :name, :string
  36430. member :birth, :date
  36431. def ==(other)
  36432. self.id == other.id && self.name == other.name
  36433. end
  36434. end
  36435. class ScaffoldedControllerTestAPI < ActionWebService::API::Base
  36436. api_method :hello, :expects => [{:integer=>:int}, :string], :returns => [:bool]
  36437. api_method :hello_struct_param, :expects => [{:person => ScaffoldPerson}], :returns => [:bool]
  36438. api_method :date_of_birth, :expects => [ScaffoldPerson], :returns => [:string]
  36439. api_method :bye, :returns => [[ScaffoldPerson]]
  36440. api_method :date_diff, :expects => [{:start_date => :date}, {:end_date => :date}], :returns => [:int]
  36441. api_method :time_diff, :expects => [{:start_time => :time}, {:end_time => :time}], :returns => [:int]
  36442. api_method :base64_upcase, :expects => [:base64], :returns => [:base64]
  36443. end
  36444. class ScaffoldedController < ActionController::Base
  36445. web_service_api ScaffoldedControllerTestAPI
  36446. web_service_scaffold :scaffold_invoke
  36447. def hello(int, string)
  36448. 0
  36449. end
  36450. def hello_struct_param(person)
  36451. 0
  36452. end
  36453. def date_of_birth(person)
  36454. person.birth.to_s
  36455. end
  36456. def bye
  36457. [ScaffoldPerson.new(:id => 1, :name => "leon"), ScaffoldPerson.new(:id => 2, :name => "paul")]
  36458. end
  36459. def rescue_action(e)
  36460. raise e
  36461. end
  36462. def date_diff(start_date, end_date)
  36463. end_date - start_date
  36464. end
  36465. def time_diff(start_time, end_time)
  36466. end_time - start_time
  36467. end
  36468. def base64_upcase(data)
  36469. data.upcase
  36470. end
  36471. end
  36472. class ScaffoldedControllerTest < Test::Unit::TestCase
  36473. def setup
  36474. @controller = ScaffoldedController.new
  36475. @request = ActionController::TestRequest.new
  36476. @response = ActionController::TestResponse.new
  36477. end
  36478. def test_scaffold_invoke
  36479. get :scaffold_invoke
  36480. assert_rendered_file 'methods.rhtml'
  36481. end
  36482. def test_scaffold_invoke_method_params
  36483. get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'Hello'
  36484. assert_rendered_file 'parameters.rhtml'
  36485. end
  36486. def test_scaffold_invoke_method_params_with_struct
  36487. get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'HelloStructParam'
  36488. assert_rendered_file 'parameters.rhtml'
  36489. assert_tag :tag => 'input', :attributes => {:name => "method_params[0][name]"}
  36490. end
  36491. def test_scaffold_invoke_submit_hello
  36492. post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Hello', :method_params => {'0' => '5', '1' => 'hello world'}
  36493. assert_rendered_file 'result.rhtml'
  36494. assert_equal false, @controller.instance_eval{ @method_return_value }
  36495. end
  36496. def test_scaffold_invoke_submit_bye
  36497. post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Bye'
  36498. assert_rendered_file 'result.rhtml'
  36499. persons = [ScaffoldPerson.new(:id => 1, :name => "leon"), ScaffoldPerson.new(:id => 2, :name => "paul")]
  36500. assert_equal persons, @controller.instance_eval{ @method_return_value }
  36501. end
  36502. def test_scaffold_date_params
  36503. get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'DateDiff'
  36504. (0..1).each do |param|
  36505. (1..3).each do |date_part|
  36506. assert_tag :tag => 'select', :attributes => {:name => "method_params[#{param}][#{date_part}]"},
  36507. :children => {:greater_than => 1, :only => {:tag => 'option'}}
  36508. end
  36509. end
  36510. post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'DateDiff',
  36511. :method_params => {'0' => {'1' => '2006', '2' => '2', '3' => '1'}, '1' => {'1' => '2006', '2' => '2', '3' => '2'}}
  36512. assert_equal 1, @controller.instance_eval{ @method_return_value }
  36513. end
  36514. def test_scaffold_struct_date_params
  36515. post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'DateOfBirth',
  36516. :method_params => {'0' => {'birth' => {'1' => '2006', '2' => '2', '3' => '1'}, 'id' => '1', 'name' => 'person'}}
  36517. assert_equal '2006-02-01', @controller.instance_eval{ @method_return_value }
  36518. end
  36519. def test_scaffold_time_params
  36520. get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'TimeDiff'
  36521. (0..1).each do |param|
  36522. (1..6).each do |date_part|
  36523. assert_tag :tag => 'select', :attributes => {:name => "method_params[#{param}][#{date_part}]"},
  36524. :children => {:greater_than => 1, :only => {:tag => 'option'}}
  36525. end
  36526. end
  36527. post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'TimeDiff',
  36528. :method_params => {'0' => {'1' => '2006', '2' => '2', '3' => '1', '4' => '1', '5' => '1', '6' => '1'},
  36529. '1' => {'1' => '2006', '2' => '2', '3' => '2', '4' => '1', '5' => '1', '6' => '1'}}
  36530. assert_equal 86400, @controller.instance_eval{ @method_return_value }
  36531. end
  36532. def test_scaffold_base64
  36533. get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'Base64Upcase'
  36534. assert_tag :tag => 'textarea', :attributes => {:name => 'method_params[0]'}
  36535. post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Base64Upcase', :method_params => {'0' => 'scaffold'}
  36536. assert_equal 'SCAFFOLD', @controller.instance_eval{ @method_return_value }
  36537. end
  36538. end
  36539. require File.dirname(__FILE__) + '/abstract_unit'
  36540. module StructTest
  36541. class Struct < ActionWebService::Struct
  36542. member :id, Integer
  36543. member :name, String
  36544. member :items, [String]
  36545. member :deleted, :bool
  36546. member :emails, [:string]
  36547. end
  36548. end
  36549. class TC_Struct < Test::Unit::TestCase
  36550. include StructTest
  36551. def setup
  36552. @struct = Struct.new(:id => 5,
  36553. :name => 'hello',
  36554. :items => ['one', 'two'],
  36555. :deleted => true,
  36556. :emails => ['test@test.com'])
  36557. end
  36558. def test_members
  36559. assert_equal(5, Struct.members.size)
  36560. assert_equal(Integer, Struct.members[:id].type_class)
  36561. assert_equal(String, Struct.members[:name].type_class)
  36562. assert_equal(String, Struct.members[:items].element_type.type_class)
  36563. assert_equal(TrueClass, Struct.members[:deleted].type_class)
  36564. assert_equal(String, Struct.members[:emails].element_type.type_class)
  36565. end
  36566. def test_initializer_and_lookup
  36567. assert_equal(5, @struct.id)
  36568. assert_equal('hello', @struct.name)
  36569. assert_equal(['one', 'two'], @struct.items)
  36570. assert_equal(true, @struct.deleted)
  36571. assert_equal(['test@test.com'], @struct.emails)
  36572. assert_equal(5, @struct['id'])
  36573. assert_equal('hello', @struct['name'])
  36574. assert_equal(['one', 'two'], @struct['items'])
  36575. assert_equal(true, @struct['deleted'])
  36576. assert_equal(['test@test.com'], @struct['emails'])
  36577. end
  36578. def test_each_pair
  36579. @struct.each_pair do |name, value|
  36580. assert_equal @struct.__send__(name), value
  36581. assert_equal @struct[name], value
  36582. end
  36583. end
  36584. end
  36585. require File.dirname(__FILE__) + '/abstract_unit'
  36586. require 'action_web_service/test_invoke'
  36587. class TestInvokeAPI < ActionWebService::API::Base
  36588. api_method :add, :expects => [:int, :int], :returns => [:int]
  36589. end
  36590. class TestInvokeService < ActionWebService::Base
  36591. web_service_api TestInvokeAPI
  36592. attr :invoked
  36593. def add(a, b)
  36594. @invoked = true
  36595. a + b
  36596. end
  36597. end
  36598. class TestController < ActionController::Base
  36599. def rescue_action(e); raise e; end
  36600. end
  36601. class TestInvokeDirectController < TestController
  36602. web_service_api TestInvokeAPI
  36603. attr :invoked
  36604. def add
  36605. @invoked = true
  36606. @method_params[0] + @method_params[1]
  36607. end
  36608. end
  36609. class TestInvokeDelegatedController < TestController
  36610. web_service_dispatching_mode :delegated
  36611. web_service :service, TestInvokeService.new
  36612. end
  36613. class TestInvokeLayeredController < TestController
  36614. web_service_dispatching_mode :layered
  36615. web_service(:one) { @service_one ||= TestInvokeService.new }
  36616. web_service(:two) { @service_two ||= TestInvokeService.new }
  36617. end
  36618. class TestInvokeTest < Test::Unit::TestCase
  36619. def setup
  36620. @request = ActionController::TestRequest.new
  36621. @response = ActionController::TestResponse.new
  36622. end
  36623. def test_direct_add
  36624. @controller = TestInvokeDirectController.new
  36625. assert_equal nil, @controller.invoked
  36626. result = invoke :add, 25, 25
  36627. assert_equal 50, result
  36628. assert_equal true, @controller.invoked
  36629. end
  36630. def test_delegated_add
  36631. @controller = TestInvokeDelegatedController.new
  36632. assert_equal nil, @controller.web_service_object(:service).invoked
  36633. result = invoke_delegated :service, :add, 100, 50
  36634. assert_equal 150, result
  36635. assert_equal true, @controller.web_service_object(:service).invoked
  36636. end
  36637. def test_layered_add
  36638. [:soap, :xmlrpc].each do |protocol|
  36639. @protocol = protocol
  36640. [:one, :two].each do |service|
  36641. @controller = TestInvokeLayeredController.new
  36642. assert_equal nil, @controller.web_service_object(service).invoked
  36643. result = invoke_layered service, :add, 200, -50
  36644. assert_equal 150, result
  36645. assert_equal true, @controller.web_service_object(service).invoked
  36646. end
  36647. end
  36648. end
  36649. def test_layered_fail_with_wrong_number_of_arguments
  36650. [:soap, :xmlrpc].each do |protocol|
  36651. @protocol = protocol
  36652. [:one, :two].each do |service|
  36653. @controller = TestInvokeLayeredController.new
  36654. assert_raise(ArgumentError) { invoke_layered service, :add, 1 }
  36655. end
  36656. end
  36657. end
  36658. def test_delegated_fail_with_wrong_number_of_arguments
  36659. @controller = TestInvokeDelegatedController.new
  36660. assert_raise(ArgumentError) { invoke_delegated :service, :add, 1 }
  36661. end
  36662. def test_direct_fail_with_wrong_number_of_arguments
  36663. @controller = TestInvokeDirectController.new
  36664. assert_raise(ArgumentError) { invoke :add, 1 }
  36665. end
  36666. end
  36667. require File.dirname(__FILE__) + '/shared_setup'
  36668. logger = Logger.new(STDOUT)
  36669. # Database setup ---------------
  36670. logger.info "\nCreate tables"
  36671. [ "DROP TABLE companies", "DROP TABLE people", "DROP TABLE people_companies",
  36672. "CREATE TABLE companies (id int(11) auto_increment, client_of int(11), name varchar(255), type varchar(100), PRIMARY KEY (id))",
  36673. "CREATE TABLE people (id int(11) auto_increment, name varchar(100), PRIMARY KEY (id))",
  36674. "CREATE TABLE people_companies (person_id int(11), company_id int(11), PRIMARY KEY (person_id, company_id))",
  36675. ].each { |statement|
  36676. # Tables doesn't necessarily already exist
  36677. begin; ActiveRecord::Base.connection.execute(statement); rescue ActiveRecord::StatementInvalid; end
  36678. }
  36679. # Class setup ---------------
  36680. class Company < ActiveRecord::Base
  36681. has_and_belongs_to_many :people, :class_name => "Person", :join_table => "people_companies", :table_name => "people"
  36682. end
  36683. class Firm < Company
  36684. has_many :clients, :foreign_key => "client_of"
  36685. def people_with_all_clients
  36686. clients.inject([]) { |people, client| people + client.people }
  36687. end
  36688. end
  36689. class Client < Company
  36690. belongs_to :firm, :foreign_key => "client_of"
  36691. end
  36692. class Person < ActiveRecord::Base
  36693. has_and_belongs_to_many :companies, :join_table => "people_companies"
  36694. def self.table_name() "people" end
  36695. end
  36696. # Usage ---------------
  36697. logger.info "\nCreate fixtures"
  36698. Firm.new("name" => "Next Angle").save
  36699. Client.new("name" => "37signals", "client_of" => 1).save
  36700. Person.new("name" => "David").save
  36701. logger.info "\nUsing Finders"
  36702. next_angle = Company.find(1)
  36703. next_angle = Firm.find(1)
  36704. next_angle = Company.find_first "name = 'Next Angle'"
  36705. next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first
  36706. Firm === next_angle
  36707. logger.info "\nUsing has_many association"
  36708. next_angle.has_clients?
  36709. next_angle.clients_count
  36710. all_clients = next_angle.clients
  36711. thirty_seven_signals = next_angle.find_in_clients(2)
  36712. logger.info "\nUsing belongs_to association"
  36713. thirty_seven_signals.has_firm?
  36714. thirty_seven_signals.firm?(next_angle)
  36715. logger.info "\nUsing has_and_belongs_to_many association"
  36716. david = Person.find(1)
  36717. david.add_companies(thirty_seven_signals, next_angle)
  36718. david.companies.include?(next_angle)
  36719. david.companies_count == 2
  36720. david.remove_companies(next_angle)
  36721. david.companies_count == 1
  36722. thirty_seven_signals.people.include?(david)# Be sure to change the mysql_connection details and create a database for the example
  36723. $: << File.dirname(__FILE__) + '/../lib'
  36724. require 'active_record'
  36725. require 'logger'; class Logger; def format_message(severity, timestamp, msg, progname) "#{msg}\n" end; end
  36726. ActiveRecord::Base.logger = Logger.new(STDOUT)
  36727. ActiveRecord::Base.establish_connection(
  36728. :adapter => "mysql",
  36729. :host => "localhost",
  36730. :username => "root",
  36731. :password => "",
  36732. :database => "activerecord_examples"
  36733. )
  36734. require File.dirname(__FILE__) + '/shared_setup'
  36735. logger = Logger.new(STDOUT)
  36736. # Database setup ---------------
  36737. logger.info "\nCreate tables"
  36738. [ "DROP TABLE people",
  36739. "CREATE TABLE people (id int(11) auto_increment, name varchar(100), pass varchar(100), email varchar(100), PRIMARY KEY (id))"
  36740. ].each { |statement|
  36741. begin; ActiveRecord::Base.connection.execute(statement); rescue ActiveRecord::StatementInvalid; end # Tables doesn't necessarily already exist
  36742. }
  36743. # Class setup ---------------
  36744. class Person < ActiveRecord::Base
  36745. # Using
  36746. def self.authenticate(name, pass)
  36747. # find_first "name = '#{name}' AND pass = '#{pass}'" would be open to sql-injection (in a web-app scenario)
  36748. find_first [ "name = '%s' AND pass = '%s'", name, pass ]
  36749. end
  36750. def self.name_exists?(name, id = nil)
  36751. if id.nil?
  36752. condition = [ "name = '%s'", name ]
  36753. else
  36754. # Check if anyone else than the person identified by person_id has that user_name
  36755. condition = [ "name = '%s' AND id <> %d", name, id ]
  36756. end
  36757. !find_first(condition).nil?
  36758. end
  36759. def email_address_with_name
  36760. "\"#{name}\" <#{email}>"
  36761. end
  36762. protected
  36763. def validate
  36764. errors.add_on_empty(%w(name pass email))
  36765. errors.add("email", "must be valid") unless email_address_valid?
  36766. end
  36767. def validate_on_create
  36768. if attribute_present?("name") && Person.name_exists?(name)
  36769. errors.add("name", "is already taken by another person")
  36770. end
  36771. end
  36772. def validate_on_update
  36773. if attribute_present?("name") && Person.name_exists?(name, id)
  36774. errors.add("name", "is already taken by another person")
  36775. end
  36776. end
  36777. private
  36778. def email_address_valid?() email =~ /\w[-.\w]*\@[-\w]+[-.\w]*\.\w+/ end
  36779. end
  36780. # Usage ---------------
  36781. logger.info "\nCreate fixtures"
  36782. david = Person.new("name" => "David Heinemeier Hansson", "pass" => "", "email" => "")
  36783. unless david.save
  36784. puts "There was #{david.errors.count} error(s)"
  36785. david.errors.each_full { |error| puts error }
  36786. end
  36787. david.pass = "something"
  36788. david.email = "invalid_address"
  36789. unless david.save
  36790. puts "There was #{david.errors.count} error(s)"
  36791. puts "It was email with: " + david.errors.on("email")
  36792. end
  36793. david.email = "david@loudthinking.com"
  36794. if david.save then puts "David finally made it!" end
  36795. another_david = Person.new("name" => "David Heinemeier Hansson", "pass" => "xc", "email" => "david@loudthinking")
  36796. unless another_david.save
  36797. puts "Error on name: " + another_david.errors.on("name")
  36798. endrequire 'rbconfig'
  36799. require 'find'
  36800. require 'ftools'
  36801. include Config
  36802. # this was adapted from rdoc's install.rb by ways of Log4r
  36803. $sitedir = CONFIG["sitelibdir"]
  36804. unless $sitedir
  36805. version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  36806. $libdir = File.join(CONFIG["libdir"], "ruby", version)
  36807. $sitedir = $:.find {|x| x =~ /site_ruby/ }
  36808. if !$sitedir
  36809. $sitedir = File.join($libdir, "site_ruby")
  36810. elsif $sitedir !~ Regexp.quote(version)
  36811. $sitedir = File.join($sitedir, version)
  36812. end
  36813. end
  36814. # the acual gruntwork
  36815. Dir.chdir("lib")
  36816. Find.find("active_record", "active_record.rb") { |f|
  36817. if f[-3..-1] == ".rb"
  36818. File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  36819. else
  36820. File::makedirs(File.join($sitedir, *f.split(/\//)))
  36821. end
  36822. }
  36823. module ActiveRecord
  36824. module Acts #:nodoc:
  36825. module List #:nodoc:
  36826. def self.append_features(base)
  36827. super
  36828. base.extend(ClassMethods)
  36829. end
  36830. # This act provides the capabilities for sorting and reordering a number of objects in a list.
  36831. # The class that has this specified needs to have a "position" column defined as an integer on
  36832. # the mapped database table.
  36833. #
  36834. # Todo list example:
  36835. #
  36836. # class TodoList < ActiveRecord::Base
  36837. # has_many :todo_items, :order => "position"
  36838. # end
  36839. #
  36840. # class TodoItem < ActiveRecord::Base
  36841. # belongs_to :todo_list
  36842. # acts_as_list :scope => :todo_list
  36843. # end
  36844. #
  36845. # todo_list.first.move_to_bottom
  36846. # todo_list.last.move_higher
  36847. module ClassMethods
  36848. # Configuration options are:
  36849. #
  36850. # * +column+ - specifies the column name to use for keeping the position integer (default: position)
  36851. # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
  36852. # (if that hasn't been already) and use that as the foreign key restriction. It's also possible
  36853. # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
  36854. # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
  36855. def acts_as_list(options = {})
  36856. configuration = { :column => "position", :scope => "1 = 1" }
  36857. configuration.update(options) if options.is_a?(Hash)
  36858. configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
  36859. if configuration[:scope].is_a?(Symbol)
  36860. scope_condition_method = %(
  36861. def scope_condition
  36862. if #{configuration[:scope].to_s}.nil?
  36863. "#{configuration[:scope].to_s} IS NULL"
  36864. else
  36865. "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
  36866. end
  36867. end
  36868. )
  36869. else
  36870. scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
  36871. end
  36872. class_eval <<-EOV
  36873. include ActiveRecord::Acts::List::InstanceMethods
  36874. def acts_as_list_class
  36875. ::#{self.name}
  36876. end
  36877. def position_column
  36878. '#{configuration[:column]}'
  36879. end
  36880. #{scope_condition_method}
  36881. after_destroy :remove_from_list
  36882. before_create :add_to_list_bottom
  36883. EOV
  36884. end
  36885. end
  36886. # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
  36887. # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
  36888. # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return true if that chapter is
  36889. # the first in the list of all chapters.
  36890. module InstanceMethods
  36891. def insert_at(position = 1)
  36892. insert_at_position(position)
  36893. end
  36894. def move_lower
  36895. return unless lower_item
  36896. acts_as_list_class.transaction do
  36897. lower_item.decrement_position
  36898. increment_position
  36899. end
  36900. end
  36901. def move_higher
  36902. return unless higher_item
  36903. acts_as_list_class.transaction do
  36904. higher_item.increment_position
  36905. decrement_position
  36906. end
  36907. end
  36908. def move_to_bottom
  36909. return unless in_list?
  36910. acts_as_list_class.transaction do
  36911. decrement_positions_on_lower_items
  36912. assume_bottom_position
  36913. end
  36914. end
  36915. def move_to_top
  36916. return unless in_list?
  36917. acts_as_list_class.transaction do
  36918. increment_positions_on_higher_items
  36919. assume_top_position
  36920. end
  36921. end
  36922. def remove_from_list
  36923. decrement_positions_on_lower_items if in_list?
  36924. end
  36925. def increment_position
  36926. return unless in_list?
  36927. update_attribute position_column, self.send(position_column).to_i + 1
  36928. end
  36929. def decrement_position
  36930. return unless in_list?
  36931. update_attribute position_column, self.send(position_column).to_i - 1
  36932. end
  36933. def first?
  36934. return false unless in_list?
  36935. self.send(position_column) == 1
  36936. end
  36937. def last?
  36938. return false unless in_list?
  36939. self.send(position_column) == bottom_position_in_list
  36940. end
  36941. def higher_item
  36942. return nil unless in_list?
  36943. acts_as_list_class.find(:first, :conditions =>
  36944. "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
  36945. )
  36946. end
  36947. def lower_item
  36948. return nil unless in_list?
  36949. acts_as_list_class.find(:first, :conditions =>
  36950. "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
  36951. )
  36952. end
  36953. def in_list?
  36954. !send(position_column).nil?
  36955. end
  36956. private
  36957. def add_to_list_top
  36958. increment_positions_on_all_items
  36959. end
  36960. def add_to_list_bottom
  36961. self[position_column] = bottom_position_in_list.to_i + 1
  36962. end
  36963. # Overwrite this method to define the scope of the list changes
  36964. def scope_condition() "1" end
  36965. def bottom_position_in_list(except = nil)
  36966. item = bottom_item(except)
  36967. item ? item.send(position_column) : 0
  36968. end
  36969. def bottom_item(except = nil)
  36970. conditions = scope_condition
  36971. conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
  36972. acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
  36973. end
  36974. def assume_bottom_position
  36975. update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
  36976. end
  36977. def assume_top_position
  36978. update_attribute(position_column, 1)
  36979. end
  36980. # This has the effect of moving all the higher items up one.
  36981. def decrement_positions_on_higher_items(position)
  36982. acts_as_list_class.update_all(
  36983. "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
  36984. )
  36985. end
  36986. # This has the effect of moving all the lower items up one.
  36987. def decrement_positions_on_lower_items
  36988. return unless in_list?
  36989. acts_as_list_class.update_all(
  36990. "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
  36991. )
  36992. end
  36993. # This has the effect of moving all the higher items down one.
  36994. def increment_positions_on_higher_items
  36995. return unless in_list?
  36996. acts_as_list_class.update_all(
  36997. "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
  36998. )
  36999. end
  37000. # This has the effect of moving all the lower items down one.
  37001. def increment_positions_on_lower_items(position)
  37002. acts_as_list_class.update_all(
  37003. "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
  37004. )
  37005. end
  37006. def increment_positions_on_all_items
  37007. acts_as_list_class.update_all(
  37008. "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
  37009. )
  37010. end
  37011. def insert_at_position(position)
  37012. remove_from_list
  37013. increment_positions_on_lower_items(position)
  37014. self.update_attribute(position_column, position)
  37015. end
  37016. end
  37017. end
  37018. end
  37019. end
  37020. module ActiveRecord
  37021. module Acts #:nodoc:
  37022. module NestedSet #:nodoc:
  37023. def self.append_features(base)
  37024. super
  37025. base.extend(ClassMethods)
  37026. end
  37027. # This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
  37028. # the added feature that you can select the children and all of their descendents with
  37029. # a single query. A good use case for this is a threaded post system, where you want
  37030. # to display every reply to a comment without multiple selects.
  37031. #
  37032. # A google search for "Nested Set" should point you in the direction to explain the
  37033. # database theory. I figured out a bunch of this from
  37034. # http://threebit.net/tutorials/nestedset/tutorial1.html
  37035. #
  37036. # Instead of picturing a leaf node structure with children pointing back to their parent,
  37037. # the best way to imagine how this works is to think of the parent entity surrounding all
  37038. # of its children, and its parent surrounding it, etc. Assuming that they are lined up
  37039. # horizontally, we store the left and right boundries in the database.
  37040. #
  37041. # Imagine:
  37042. # root
  37043. # |_ Child 1
  37044. # |_ Child 1.1
  37045. # |_ Child 1.2
  37046. # |_ Child 2
  37047. # |_ Child 2.1
  37048. # |_ Child 2.2
  37049. #
  37050. # If my cirlces in circles description didn't make sense, check out this sweet
  37051. # ASCII art:
  37052. #
  37053. # ___________________________________________________________________
  37054. # | Root |
  37055. # | ____________________________ ____________________________ |
  37056. # | | Child 1 | | Child 2 | |
  37057. # | | __________ _________ | | __________ _________ | |
  37058. # | | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
  37059. # 1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
  37060. # | |___________________________| |___________________________| |
  37061. # |___________________________________________________________________|
  37062. #
  37063. # The numbers represent the left and right boundries. The table then might
  37064. # look like this:
  37065. # ID | PARENT | LEFT | RIGHT | DATA
  37066. # 1 | 0 | 1 | 14 | root
  37067. # 2 | 1 | 2 | 7 | Child 1
  37068. # 3 | 2 | 3 | 4 | Child 1.1
  37069. # 4 | 2 | 5 | 6 | Child 1.2
  37070. # 5 | 1 | 8 | 13 | Child 2
  37071. # 6 | 5 | 9 | 10 | Child 2.1
  37072. # 7 | 5 | 11 | 12 | Child 2.2
  37073. #
  37074. # So, to get all children of an entry, you
  37075. # SELECT * WHERE CHILD.LEFT IS BETWEEN PARENT.LEFT AND PARENT.RIGHT
  37076. #
  37077. # To get the count, it's (LEFT - RIGHT + 1)/2, etc.
  37078. #
  37079. # To get the direct parent, it falls back to using the PARENT_ID field.
  37080. #
  37081. # There are instance methods for all of these.
  37082. #
  37083. # The structure is good if you need to group things together; the downside is that
  37084. # keeping data integrity is a pain, and both adding and removing an entry
  37085. # require a full table write.
  37086. #
  37087. # This sets up a before_destroy trigger to prune the tree correctly if one of its
  37088. # elements gets deleted.
  37089. #
  37090. module ClassMethods
  37091. # Configuration options are:
  37092. #
  37093. # * +parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
  37094. # * +left_column+ - column name for left boundry data, default "lft"
  37095. # * +right_column+ - column name for right boundry data, default "rgt"
  37096. # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
  37097. # (if that hasn't been already) and use that as the foreign key restriction. It's also possible
  37098. # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
  37099. # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
  37100. def acts_as_nested_set(options = {})
  37101. configuration = { :parent_column => "parent_id", :left_column => "lft", :right_column => "rgt", :scope => "1 = 1" }
  37102. configuration.update(options) if options.is_a?(Hash)
  37103. configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
  37104. if configuration[:scope].is_a?(Symbol)
  37105. scope_condition_method = %(
  37106. def scope_condition
  37107. if #{configuration[:scope].to_s}.nil?
  37108. "#{configuration[:scope].to_s} IS NULL"
  37109. else
  37110. "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
  37111. end
  37112. end
  37113. )
  37114. else
  37115. scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
  37116. end
  37117. class_eval <<-EOV
  37118. include ActiveRecord::Acts::NestedSet::InstanceMethods
  37119. #{scope_condition_method}
  37120. def left_col_name() "#{configuration[:left_column]}" end
  37121. def right_col_name() "#{configuration[:right_column]}" end
  37122. def parent_column() "#{configuration[:parent_column]}" end
  37123. EOV
  37124. end
  37125. end
  37126. module InstanceMethods
  37127. # Returns true is this is a root node.
  37128. def root?
  37129. parent_id = self[parent_column]
  37130. (parent_id == 0 || parent_id.nil?) && (self[left_col_name] == 1) && (self[right_col_name] > self[left_col_name])
  37131. end
  37132. # Returns true is this is a child node
  37133. def child?
  37134. parent_id = self[parent_column]
  37135. !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
  37136. end
  37137. # Returns true if we have no idea what this is
  37138. def unknown?
  37139. !root? && !child?
  37140. end
  37141. # Adds a child to this object in the tree. If this object hasn't been initialized,
  37142. # it gets set up as a root node. Otherwise, this method will update all of the
  37143. # other elements in the tree and shift them to the right, keeping everything
  37144. # balanced.
  37145. def add_child( child )
  37146. self.reload
  37147. child.reload
  37148. if child.root?
  37149. raise "Adding sub-tree isn\'t currently supported"
  37150. else
  37151. if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
  37152. # Looks like we're now the root node! Woo
  37153. self[left_col_name] = 1
  37154. self[right_col_name] = 4
  37155. # What do to do about validation?
  37156. return nil unless self.save
  37157. child[parent_column] = self.id
  37158. child[left_col_name] = 2
  37159. child[right_col_name]= 3
  37160. return child.save
  37161. else
  37162. # OK, we need to add and shift everything else to the right
  37163. child[parent_column] = self.id
  37164. right_bound = self[right_col_name]
  37165. child[left_col_name] = right_bound
  37166. child[right_col_name] = right_bound + 1
  37167. self[right_col_name] += 2
  37168. self.class.transaction {
  37169. self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
  37170. self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
  37171. self.save
  37172. child.save
  37173. }
  37174. end
  37175. end
  37176. end
  37177. # Returns the number of nested children of this object.
  37178. def children_count
  37179. return (self[right_col_name] - self[left_col_name] - 1)/2
  37180. end
  37181. # Returns a set of itself and all of its nested children
  37182. def full_set
  37183. self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
  37184. end
  37185. # Returns a set of all of its children and nested children
  37186. def all_children
  37187. self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
  37188. end
  37189. # Returns a set of only this entry's immediate children
  37190. def direct_children
  37191. self.class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}")
  37192. end
  37193. # Prunes a branch off of the tree, shifting all of the elements on the right
  37194. # back to the left so the counts still work.
  37195. def before_destroy
  37196. return if self[right_col_name].nil? || self[left_col_name].nil?
  37197. dif = self[right_col_name] - self[left_col_name] + 1
  37198. self.class.transaction {
  37199. self.class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
  37200. self.class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})", "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
  37201. self.class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )", "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
  37202. }
  37203. end
  37204. end
  37205. end
  37206. end
  37207. end
  37208. module ActiveRecord
  37209. module Acts #:nodoc:
  37210. module Tree #:nodoc:
  37211. def self.append_features(base)
  37212. super
  37213. base.extend(ClassMethods)
  37214. end
  37215. # Specify this act if you want to model a tree structure by providing a parent association and a children
  37216. # association. This act requires that you have a foreign key column, which by default is called parent_id.
  37217. #
  37218. # class Category < ActiveRecord::Base
  37219. # acts_as_tree :order => "name"
  37220. # end
  37221. #
  37222. # Example :
  37223. # root
  37224. # \_ child1
  37225. # \_ subchild1
  37226. # \_ subchild2
  37227. #
  37228. # root = Category.create("name" => "root")
  37229. # child1 = root.children.create("name" => "child1")
  37230. # subchild1 = child1.children.create("name" => "subchild1")
  37231. #
  37232. # root.parent # => nil
  37233. # child1.parent # => root
  37234. # root.children # => [child1]
  37235. # root.children.first.children.first # => subchild1
  37236. #
  37237. # In addition to the parent and children associations, the following instance methods are added to the class
  37238. # after specifying the act:
  37239. # * siblings : Returns all the children of the parent, excluding the current node ([ subchild2 ] when called from subchild1)
  37240. # * self_and_siblings : Returns all the children of the parent, including the current node ([ subchild1, subchild2 ] when called from subchild1)
  37241. # * ancestors : Returns all the ancestors of the current node ([child1, root] when called from subchild2)
  37242. # * root : Returns the root of the current node (root when called from subchild2)
  37243. module ClassMethods
  37244. # Configuration options are:
  37245. #
  37246. # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: parent_id)
  37247. # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
  37248. # * <tt>counter_cache</tt> - keeps a count in a children_count column if set to true (default: false).
  37249. def acts_as_tree(options = {})
  37250. configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
  37251. configuration.update(options) if options.is_a?(Hash)
  37252. belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
  37253. has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
  37254. class_eval <<-EOV
  37255. include ActiveRecord::Acts::Tree::InstanceMethods
  37256. def self.roots
  37257. find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
  37258. end
  37259. def self.root
  37260. find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
  37261. end
  37262. EOV
  37263. end
  37264. end
  37265. module InstanceMethods
  37266. # Returns list of ancestors, starting from parent until root.
  37267. #
  37268. # subchild1.ancestors # => [child1, root]
  37269. def ancestors
  37270. node, nodes = self, []
  37271. nodes << node = node.parent until not node.has_parent?
  37272. nodes
  37273. end
  37274. def root
  37275. node = self
  37276. node = node.parent until not node.has_parent?
  37277. node
  37278. end
  37279. def siblings
  37280. self_and_siblings - [self]
  37281. end
  37282. def self_and_siblings
  37283. has_parent? ? parent.children : self.class.roots
  37284. end
  37285. end
  37286. end
  37287. end
  37288. end
  37289. module ActiveRecord
  37290. module Aggregations # :nodoc:
  37291. def self.included(base)
  37292. base.extend(ClassMethods)
  37293. end
  37294. def clear_aggregation_cache #:nodoc:
  37295. self.class.reflect_on_all_aggregations.to_a.each do |assoc|
  37296. instance_variable_set "@#{assoc.name}", nil
  37297. end unless self.new_record?
  37298. end
  37299. # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
  37300. # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
  37301. # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
  37302. # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
  37303. # and how it can be turned back into attributes (when the entity is saved to the database). Example:
  37304. #
  37305. # class Customer < ActiveRecord::Base
  37306. # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
  37307. # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
  37308. # end
  37309. #
  37310. # The customer class now has the following methods to manipulate the value objects:
  37311. # * <tt>Customer#balance, Customer#balance=(money)</tt>
  37312. # * <tt>Customer#address, Customer#address=(address)</tt>
  37313. #
  37314. # These methods will operate with value objects like the ones described below:
  37315. #
  37316. # class Money
  37317. # include Comparable
  37318. # attr_reader :amount, :currency
  37319. # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
  37320. #
  37321. # def initialize(amount, currency = "USD")
  37322. # @amount, @currency = amount, currency
  37323. # end
  37324. #
  37325. # def exchange_to(other_currency)
  37326. # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
  37327. # Money.new(exchanged_amount, other_currency)
  37328. # end
  37329. #
  37330. # def ==(other_money)
  37331. # amount == other_money.amount && currency == other_money.currency
  37332. # end
  37333. #
  37334. # def <=>(other_money)
  37335. # if currency == other_money.currency
  37336. # amount <=> amount
  37337. # else
  37338. # amount <=> other_money.exchange_to(currency).amount
  37339. # end
  37340. # end
  37341. # end
  37342. #
  37343. # class Address
  37344. # attr_reader :street, :city
  37345. # def initialize(street, city)
  37346. # @street, @city = street, city
  37347. # end
  37348. #
  37349. # def close_to?(other_address)
  37350. # city == other_address.city
  37351. # end
  37352. #
  37353. # def ==(other_address)
  37354. # city == other_address.city && street == other_address.street
  37355. # end
  37356. # end
  37357. #
  37358. # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
  37359. # composition the same as the attributes name, it will be the only way to access that attribute. That's the case with our
  37360. # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
  37361. #
  37362. # customer.balance = Money.new(20) # sets the Money value object and the attribute
  37363. # customer.balance # => Money value object
  37364. # customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
  37365. # customer.balance > Money.new(10) # => true
  37366. # customer.balance == Money.new(20) # => true
  37367. # customer.balance < Money.new(5) # => false
  37368. #
  37369. # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
  37370. # determine the order of the parameters. Example:
  37371. #
  37372. # customer.address_street = "Hyancintvej"
  37373. # customer.address_city = "Copenhagen"
  37374. # customer.address # => Address.new("Hyancintvej", "Copenhagen")
  37375. # customer.address = Address.new("May Street", "Chicago")
  37376. # customer.address_street # => "May Street"
  37377. # customer.address_city # => "Chicago"
  37378. #
  37379. # == Writing value objects
  37380. #
  37381. # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
  37382. # $5. Two Money objects both representing $5 should be equal (through methods such as == and <=> from Comparable if ranking
  37383. # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
  37384. # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
  37385. # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
  37386. #
  37387. # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
  37388. # creation. Create a new money object with the new value instead. This is exemplified by the Money#exchanged_to method that
  37389. # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
  37390. # changed through other means than the writer method.
  37391. #
  37392. # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
  37393. # change it afterwards will result in a TypeError.
  37394. #
  37395. # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
  37396. # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
  37397. module ClassMethods
  37398. # Adds the a reader and writer method for manipulating a value object, so
  37399. # <tt>composed_of :address</tt> would add <tt>address</tt> and <tt>address=(new_address)</tt>.
  37400. #
  37401. # Options are:
  37402. # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
  37403. # from the part id. So <tt>composed_of :address</tt> will by default be linked to the +Address+ class, but
  37404. # if the real class name is +CompanyAddress+, you'll have to specify it with this option.
  37405. # * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
  37406. # to a constructor parameter on the value class.
  37407. #
  37408. # Option examples:
  37409. # composed_of :temperature, :mapping => %w(reading celsius)
  37410. # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
  37411. # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
  37412. # composed_of :gps_location
  37413. def composed_of(part_id, options = {})
  37414. options.assert_valid_keys(:class_name, :mapping)
  37415. name = part_id.id2name
  37416. class_name = options[:class_name] || name_to_class_name(name)
  37417. mapping = options[:mapping] || [ name, name ]
  37418. reader_method(name, class_name, mapping)
  37419. writer_method(name, class_name, mapping)
  37420. create_reflection(:composed_of, part_id, options, self)
  37421. end
  37422. private
  37423. def name_to_class_name(name)
  37424. name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
  37425. end
  37426. def reader_method(name, class_name, mapping)
  37427. module_eval <<-end_eval
  37428. def #{name}(force_reload = false)
  37429. if @#{name}.nil? || force_reload
  37430. @#{name} = #{class_name}.new(#{(Array === mapping.first ? mapping : [ mapping ]).collect{ |pair| "read_attribute(\"#{pair.first}\")"}.join(", ")})
  37431. end
  37432. return @#{name}
  37433. end
  37434. end_eval
  37435. end
  37436. def writer_method(name, class_name, mapping)
  37437. module_eval <<-end_eval
  37438. def #{name}=(part)
  37439. @#{name} = part.freeze
  37440. #{(Array === mapping.first ? mapping : [ mapping ]).collect{ |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")}
  37441. end
  37442. end_eval
  37443. end
  37444. end
  37445. end
  37446. end
  37447. require 'set'
  37448. module ActiveRecord
  37449. module Associations
  37450. class AssociationCollection < AssociationProxy #:nodoc:
  37451. def to_ary
  37452. load_target
  37453. @target.to_ary
  37454. end
  37455. def reset
  37456. @target = []
  37457. @loaded = false
  37458. end
  37459. # Add +records+ to this association. Returns +self+ so method calls may be chained.
  37460. # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
  37461. def <<(*records)
  37462. result = true
  37463. load_target
  37464. @owner.transaction do
  37465. flatten_deeper(records).each do |record|
  37466. raise_on_type_mismatch(record)
  37467. callback(:before_add, record)
  37468. result &&= insert_record(record) unless @owner.new_record?
  37469. @target << record
  37470. callback(:after_add, record)
  37471. end
  37472. end
  37473. result && self
  37474. end
  37475. alias_method :push, :<<
  37476. alias_method :concat, :<<
  37477. # Remove all records from this association
  37478. def delete_all
  37479. load_target
  37480. delete(@target)
  37481. @target = []
  37482. end
  37483. # Remove +records+ from this association. Does not destroy +records+.
  37484. def delete(*records)
  37485. records = flatten_deeper(records)
  37486. records.each { |record| raise_on_type_mismatch(record) }
  37487. records.reject! { |record| @target.delete(record) if record.new_record? }
  37488. return if records.empty?
  37489. @owner.transaction do
  37490. records.each { |record| callback(:before_remove, record) }
  37491. delete_records(records)
  37492. records.each do |record|
  37493. @target.delete(record)
  37494. callback(:after_remove, record)
  37495. end
  37496. end
  37497. end
  37498. # Removes all records from this association. Returns +self+ so method calls may be chained.
  37499. def clear
  37500. return self if length.zero? # forces load_target if hasn't happened already
  37501. if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all
  37502. destroy_all
  37503. else
  37504. delete_all
  37505. end
  37506. self
  37507. end
  37508. def destroy_all
  37509. @owner.transaction do
  37510. each { |record| record.destroy }
  37511. end
  37512. @target = []
  37513. end
  37514. def create(attributes = {})
  37515. # Can't use Base.create since the foreign key may be a protected attribute.
  37516. if attributes.is_a?(Array)
  37517. attributes.collect { |attr| create(attr) }
  37518. else
  37519. record = build(attributes)
  37520. record.save unless @owner.new_record?
  37521. record
  37522. end
  37523. end
  37524. # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
  37525. # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
  37526. # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
  37527. def size
  37528. if loaded? then @target.size else count_records end
  37529. end
  37530. # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
  37531. # whether the collection is empty, use collection.length.zero? instead of collection.empty?
  37532. def length
  37533. load_target.size
  37534. end
  37535. def empty?
  37536. size.zero?
  37537. end
  37538. def uniq(collection = self)
  37539. collection.inject([]) { |uniq_records, record| uniq_records << record unless uniq_records.include?(record); uniq_records }
  37540. end
  37541. # Replace this collection with +other_array+
  37542. # This will perform a diff and delete/add only records that have changed.
  37543. def replace(other_array)
  37544. other_array.each { |val| raise_on_type_mismatch(val) }
  37545. load_target
  37546. other = other_array.size < 100 ? other_array : other_array.to_set
  37547. current = @target.size < 100 ? @target : @target.to_set
  37548. @owner.transaction do
  37549. delete(@target.select { |v| !other.include?(v) })
  37550. concat(other_array.select { |v| !current.include?(v) })
  37551. end
  37552. end
  37553. private
  37554. # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
  37555. def flatten_deeper(array)
  37556. array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
  37557. end
  37558. def callback(method, record)
  37559. callbacks_for(method).each do |callback|
  37560. case callback
  37561. when Symbol
  37562. @owner.send(callback, record)
  37563. when Proc, Method
  37564. callback.call(@owner, record)
  37565. else
  37566. if callback.respond_to?(method)
  37567. callback.send(method, @owner, record)
  37568. else
  37569. raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
  37570. end
  37571. end
  37572. end
  37573. end
  37574. def callbacks_for(callback_name)
  37575. full_callback_name = "#{callback_name}_for_#{@reflection.name}"
  37576. @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
  37577. end
  37578. end
  37579. end
  37580. end
  37581. module ActiveRecord
  37582. module Associations
  37583. class AssociationProxy #:nodoc:
  37584. attr_reader :reflection
  37585. alias_method :proxy_respond_to?, :respond_to?
  37586. alias_method :proxy_extend, :extend
  37587. instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }
  37588. def initialize(owner, reflection)
  37589. @owner, @reflection = owner, reflection
  37590. proxy_extend(reflection.options[:extend]) if reflection.options[:extend]
  37591. reset
  37592. end
  37593. def respond_to?(symbol, include_priv = false)
  37594. proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
  37595. end
  37596. # Explicitly proxy === because the instance method removal above
  37597. # doesn't catch it.
  37598. def ===(other)
  37599. load_target
  37600. other === @target
  37601. end
  37602. def aliased_table_name
  37603. @reflection.klass.table_name
  37604. end
  37605. def conditions
  37606. @conditions ||= eval("%(#{@reflection.active_record.send :sanitize_sql, @reflection.options[:conditions]})") if @reflection.options[:conditions]
  37607. end
  37608. alias :sql_conditions :conditions
  37609. def reset
  37610. @target = nil
  37611. @loaded = false
  37612. end
  37613. def reload
  37614. reset
  37615. load_target
  37616. end
  37617. def loaded?
  37618. @loaded
  37619. end
  37620. def loaded
  37621. @loaded = true
  37622. end
  37623. def target
  37624. @target
  37625. end
  37626. def target=(target)
  37627. @target = target
  37628. loaded
  37629. end
  37630. protected
  37631. def dependent?
  37632. @reflection.options[:dependent] || false
  37633. end
  37634. def quoted_record_ids(records)
  37635. records.map { |record| record.quoted_id }.join(',')
  37636. end
  37637. def interpolate_sql_options!(options, *keys)
  37638. keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
  37639. end
  37640. def interpolate_sql(sql, record = nil)
  37641. @owner.send(:interpolate_sql, sql, record)
  37642. end
  37643. def sanitize_sql(sql)
  37644. @reflection.klass.send(:sanitize_sql, sql)
  37645. end
  37646. def extract_options_from_args!(args)
  37647. @owner.send(:extract_options_from_args!, args)
  37648. end
  37649. def set_belongs_to_association_for(record)
  37650. if @reflection.options[:as]
  37651. record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
  37652. record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
  37653. else
  37654. record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
  37655. end
  37656. end
  37657. def merge_options_from_reflection!(options)
  37658. options.reverse_merge!(
  37659. :group => @reflection.options[:group],
  37660. :limit => @reflection.options[:limit],
  37661. :offset => @reflection.options[:offset],
  37662. :joins => @reflection.options[:joins],
  37663. :include => @reflection.options[:include],
  37664. :select => @reflection.options[:select]
  37665. )
  37666. end
  37667. private
  37668. def method_missing(method, *args, &block)
  37669. load_target
  37670. @target.send(method, *args, &block)
  37671. end
  37672. def load_target
  37673. if !@owner.new_record? || foreign_key_present
  37674. begin
  37675. @target = find_target if !loaded?
  37676. rescue ActiveRecord::RecordNotFound
  37677. reset
  37678. end
  37679. end
  37680. loaded if target
  37681. target
  37682. end
  37683. # Can be overwritten by associations that might have the foreign key available for an association without
  37684. # having the object itself (and still being a new record). Currently, only belongs_to present this scenario.
  37685. def foreign_key_present
  37686. false
  37687. end
  37688. def raise_on_type_mismatch(record)
  37689. unless record.is_a?(@reflection.klass)
  37690. raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
  37691. end
  37692. end
  37693. end
  37694. end
  37695. end
  37696. module ActiveRecord
  37697. module Associations
  37698. class BelongsToAssociation < AssociationProxy #:nodoc:
  37699. def create(attributes = {})
  37700. replace(@reflection.klass.create(attributes))
  37701. end
  37702. def build(attributes = {})
  37703. replace(@reflection.klass.new(attributes))
  37704. end
  37705. def replace(record)
  37706. counter_cache_name = @reflection.counter_cache_column
  37707. if record.nil?
  37708. if counter_cache_name && @owner[counter_cache_name] && !@owner.new_record?
  37709. @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
  37710. end
  37711. @target = @owner[@reflection.primary_key_name] = nil
  37712. else
  37713. raise_on_type_mismatch(record)
  37714. if counter_cache_name && !@owner.new_record?
  37715. @reflection.klass.increment_counter(counter_cache_name, record.id)
  37716. @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
  37717. end
  37718. @target = (AssociationProxy === record ? record.target : record)
  37719. @owner[@reflection.primary_key_name] = record.id unless record.new_record?
  37720. @updated = true
  37721. end
  37722. loaded
  37723. record
  37724. end
  37725. def updated?
  37726. @updated
  37727. end
  37728. private
  37729. def find_target
  37730. @reflection.klass.find(
  37731. @owner[@reflection.primary_key_name],
  37732. :conditions => conditions,
  37733. :include => @reflection.options[:include]
  37734. )
  37735. end
  37736. def foreign_key_present
  37737. !@owner[@reflection.primary_key_name].nil?
  37738. end
  37739. end
  37740. end
  37741. end
  37742. module ActiveRecord
  37743. module Associations
  37744. class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
  37745. def replace(record)
  37746. if record.nil?
  37747. @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
  37748. else
  37749. @target = (AssociationProxy === record ? record.target : record)
  37750. unless record.new_record?
  37751. @owner[@reflection.primary_key_name] = record.id
  37752. @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
  37753. end
  37754. @updated = true
  37755. end
  37756. loaded
  37757. record
  37758. end
  37759. def updated?
  37760. @updated
  37761. end
  37762. private
  37763. def find_target
  37764. return nil if association_class.nil?
  37765. if @reflection.options[:conditions]
  37766. association_class.find(
  37767. @owner[@reflection.primary_key_name],
  37768. :conditions => conditions,
  37769. :include => @reflection.options[:include]
  37770. )
  37771. else
  37772. association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
  37773. end
  37774. end
  37775. def foreign_key_present
  37776. !@owner[@reflection.primary_key_name].nil?
  37777. end
  37778. def association_class
  37779. @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
  37780. end
  37781. end
  37782. end
  37783. end
  37784. module ActiveRecord
  37785. module Associations
  37786. class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
  37787. def initialize(owner, reflection)
  37788. super
  37789. construct_sql
  37790. end
  37791. def build(attributes = {})
  37792. load_target
  37793. record = @reflection.klass.new(attributes)
  37794. @target << record
  37795. record
  37796. end
  37797. def find_first
  37798. load_target.first
  37799. end
  37800. def find(*args)
  37801. options = Base.send(:extract_options_from_args!, args)
  37802. # If using a custom finder_sql, scan the entire collection.
  37803. if @reflection.options[:finder_sql]
  37804. expects_array = args.first.kind_of?(Array)
  37805. ids = args.flatten.compact.uniq
  37806. if ids.size == 1
  37807. id = ids.first.to_i
  37808. record = load_target.detect { |record| id == record.id }
  37809. expects_array ? [record] : record
  37810. else
  37811. load_target.select { |record| ids.include?(record.id) }
  37812. end
  37813. else
  37814. conditions = "#{@finder_sql}"
  37815. if sanitized_conditions = sanitize_sql(options[:conditions])
  37816. conditions << " AND (#{sanitized_conditions})"
  37817. end
  37818. options[:conditions] = conditions
  37819. options[:joins] = @join_sql
  37820. options[:readonly] = finding_with_ambigious_select?(options[:select])
  37821. if options[:order] && @reflection.options[:order]
  37822. options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
  37823. elsif @reflection.options[:order]
  37824. options[:order] = @reflection.options[:order]
  37825. end
  37826. merge_options_from_reflection!(options)
  37827. # Pass through args exactly as we received them.
  37828. args << options
  37829. @reflection.klass.find(*args)
  37830. end
  37831. end
  37832. def push_with_attributes(record, join_attributes = {})
  37833. raise_on_type_mismatch(record)
  37834. join_attributes.each { |key, value| record[key.to_s] = value }
  37835. callback(:before_add, record)
  37836. insert_record(record) unless @owner.new_record?
  37837. @target << record
  37838. callback(:after_add, record)
  37839. self
  37840. end
  37841. alias :concat_with_attributes :push_with_attributes
  37842. def size
  37843. @reflection.options[:uniq] ? count_records : super
  37844. end
  37845. protected
  37846. def method_missing(method, *args, &block)
  37847. if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
  37848. super
  37849. else
  37850. @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
  37851. @reflection.klass.send(method, *args, &block)
  37852. end
  37853. end
  37854. end
  37855. def find_target
  37856. if @reflection.options[:finder_sql]
  37857. records = @reflection.klass.find_by_sql(@finder_sql)
  37858. else
  37859. records = find(:all)
  37860. end
  37861. @reflection.options[:uniq] ? uniq(records) : records
  37862. end
  37863. def count_records
  37864. load_target.size
  37865. end
  37866. def insert_record(record)
  37867. if record.new_record?
  37868. return false unless record.save
  37869. end
  37870. if @reflection.options[:insert_sql]
  37871. @owner.connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
  37872. else
  37873. columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
  37874. attributes = columns.inject({}) do |attributes, column|
  37875. case column.name
  37876. when @reflection.primary_key_name
  37877. attributes[column.name] = @owner.quoted_id
  37878. when @reflection.association_foreign_key
  37879. attributes[column.name] = record.quoted_id
  37880. else
  37881. if record.attributes.has_key?(column.name)
  37882. value = @owner.send(:quote, record[column.name], column)
  37883. attributes[column.name] = value unless value.nil?
  37884. end
  37885. end
  37886. attributes
  37887. end
  37888. sql =
  37889. "INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
  37890. "VALUES (#{attributes.values.join(', ')})"
  37891. @owner.connection.execute(sql)
  37892. end
  37893. return true
  37894. end
  37895. def delete_records(records)
  37896. if sql = @reflection.options[:delete_sql]
  37897. records.each { |record| @owner.connection.execute(interpolate_sql(sql, record)) }
  37898. else
  37899. ids = quoted_record_ids(records)
  37900. sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
  37901. @owner.connection.execute(sql)
  37902. end
  37903. end
  37904. def construct_sql
  37905. interpolate_sql_options!(@reflection.options, :finder_sql)
  37906. if @reflection.options[:finder_sql]
  37907. @finder_sql = @reflection.options[:finder_sql]
  37908. else
  37909. @finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
  37910. @finder_sql << " AND (#{conditions})" if conditions
  37911. end
  37912. @join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
  37913. end
  37914. # Join tables with additional columns on top of the two foreign keys must be considered ambigious unless a select
  37915. # clause has been explicitly defined. Otherwise you can get broken records back, if, say, the join column also has
  37916. # and id column, which will then overwrite the id column of the records coming back.
  37917. def finding_with_ambigious_select?(select_clause)
  37918. !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
  37919. end
  37920. end
  37921. end
  37922. end
  37923. module ActiveRecord
  37924. module Associations
  37925. class HasManyAssociation < AssociationCollection #:nodoc:
  37926. def initialize(owner, reflection)
  37927. super
  37928. construct_sql
  37929. end
  37930. def build(attributes = {})
  37931. if attributes.is_a?(Array)
  37932. attributes.collect { |attr| build(attr) }
  37933. else
  37934. load_target
  37935. record = @reflection.klass.new(attributes)
  37936. set_belongs_to_association_for(record)
  37937. @target << record
  37938. record
  37939. end
  37940. end
  37941. # DEPRECATED.
  37942. def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
  37943. if @reflection.options[:finder_sql]
  37944. @reflection.klass.find_by_sql(@finder_sql)
  37945. else
  37946. conditions = @finder_sql
  37947. conditions += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
  37948. orderings ||= @reflection.options[:order]
  37949. @reflection.klass.find_all(conditions, orderings, limit, joins)
  37950. end
  37951. end
  37952. # DEPRECATED. Find the first associated record. All arguments are optional.
  37953. def find_first(conditions = nil, orderings = nil)
  37954. find_all(conditions, orderings, 1).first
  37955. end
  37956. # Count the number of associated records. All arguments are optional.
  37957. def count(runtime_conditions = nil)
  37958. if @reflection.options[:counter_sql]
  37959. @reflection.klass.count_by_sql(@counter_sql)
  37960. elsif @reflection.options[:finder_sql]
  37961. @reflection.klass.count_by_sql(@finder_sql)
  37962. else
  37963. sql = @finder_sql
  37964. sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
  37965. @reflection.klass.count(sql)
  37966. end
  37967. end
  37968. def find(*args)
  37969. options = Base.send(:extract_options_from_args!, args)
  37970. # If using a custom finder_sql, scan the entire collection.
  37971. if @reflection.options[:finder_sql]
  37972. expects_array = args.first.kind_of?(Array)
  37973. ids = args.flatten.compact.uniq
  37974. if ids.size == 1
  37975. id = ids.first
  37976. record = load_target.detect { |record| id == record.id }
  37977. expects_array ? [ record ] : record
  37978. else
  37979. load_target.select { |record| ids.include?(record.id) }
  37980. end
  37981. else
  37982. conditions = "#{@finder_sql}"
  37983. if sanitized_conditions = sanitize_sql(options[:conditions])
  37984. conditions << " AND (#{sanitized_conditions})"
  37985. end
  37986. options[:conditions] = conditions
  37987. if options[:order] && @reflection.options[:order]
  37988. options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
  37989. elsif @reflection.options[:order]
  37990. options[:order] = @reflection.options[:order]
  37991. end
  37992. merge_options_from_reflection!(options)
  37993. # Pass through args exactly as we received them.
  37994. args << options
  37995. @reflection.klass.find(*args)
  37996. end
  37997. end
  37998. protected
  37999. def method_missing(method, *args, &block)
  38000. if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
  38001. super
  38002. else
  38003. @reflection.klass.with_scope(
  38004. :find => {
  38005. :conditions => @finder_sql,
  38006. :joins => @join_sql,
  38007. :readonly => false
  38008. },
  38009. :create => {
  38010. @reflection.primary_key_name => @owner.id
  38011. }
  38012. ) do
  38013. @reflection.klass.send(method, *args, &block)
  38014. end
  38015. end
  38016. end
  38017. def find_target
  38018. if @reflection.options[:finder_sql]
  38019. @reflection.klass.find_by_sql(@finder_sql)
  38020. else
  38021. find(:all)
  38022. end
  38023. end
  38024. def count_records
  38025. count = if has_cached_counter?
  38026. @owner.send(:read_attribute, cached_counter_attribute_name)
  38027. elsif @reflection.options[:counter_sql]
  38028. @reflection.klass.count_by_sql(@counter_sql)
  38029. else
  38030. @reflection.klass.count(@counter_sql)
  38031. end
  38032. @target = [] and loaded if count == 0
  38033. if @reflection.options[:limit]
  38034. count = [ @reflection.options[:limit], count ].min
  38035. end
  38036. return count
  38037. end
  38038. def has_cached_counter?
  38039. @owner.attribute_present?(cached_counter_attribute_name)
  38040. end
  38041. def cached_counter_attribute_name
  38042. "#{@reflection.name}_count"
  38043. end
  38044. def insert_record(record)
  38045. set_belongs_to_association_for(record)
  38046. record.save
  38047. end
  38048. def delete_records(records)
  38049. if @reflection.options[:dependent]
  38050. records.each { |r| r.destroy }
  38051. else
  38052. ids = quoted_record_ids(records)
  38053. @reflection.klass.update_all(
  38054. "#{@reflection.primary_key_name} = NULL",
  38055. "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
  38056. )
  38057. end
  38058. end
  38059. def target_obsolete?
  38060. false
  38061. end
  38062. def construct_sql
  38063. case
  38064. when @reflection.options[:finder_sql]
  38065. @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
  38066. when @reflection.options[:as]
  38067. @finder_sql =
  38068. "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
  38069. "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
  38070. @finder_sql << " AND (#{conditions})" if conditions
  38071. else
  38072. @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
  38073. @finder_sql << " AND (#{conditions})" if conditions
  38074. end
  38075. if @reflection.options[:counter_sql]
  38076. @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
  38077. elsif @reflection.options[:finder_sql]
  38078. # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
  38079. @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
  38080. @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
  38081. else
  38082. @counter_sql = @finder_sql
  38083. end
  38084. end
  38085. end
  38086. end
  38087. end
  38088. module ActiveRecord
  38089. module Associations
  38090. class HasManyThroughAssociation < AssociationProxy #:nodoc:
  38091. def initialize(owner, reflection)
  38092. super
  38093. reflection.check_validity!
  38094. @finder_sql = construct_conditions
  38095. construct_sql
  38096. end
  38097. def find(*args)
  38098. options = Base.send(:extract_options_from_args!, args)
  38099. conditions = "#{@finder_sql}"
  38100. if sanitized_conditions = sanitize_sql(options[:conditions])
  38101. conditions << " AND (#{sanitized_conditions})"
  38102. end
  38103. options[:conditions] = conditions
  38104. if options[:order] && @reflection.options[:order]
  38105. options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
  38106. elsif @reflection.options[:order]
  38107. options[:order] = @reflection.options[:order]
  38108. end
  38109. options[:select] = construct_select(options[:select])
  38110. options[:from] ||= construct_from
  38111. options[:joins] = construct_joins(options[:joins])
  38112. options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
  38113. merge_options_from_reflection!(options)
  38114. # Pass through args exactly as we received them.
  38115. args << options
  38116. @reflection.klass.find(*args)
  38117. end
  38118. def reset
  38119. @target = []
  38120. @loaded = false
  38121. end
  38122. protected
  38123. def method_missing(method, *args, &block)
  38124. if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
  38125. super
  38126. else
  38127. @reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
  38128. end
  38129. end
  38130. def find_target
  38131. @reflection.klass.find(:all,
  38132. :select => construct_select,
  38133. :conditions => construct_conditions,
  38134. :from => construct_from,
  38135. :joins => construct_joins,
  38136. :order => @reflection.options[:order],
  38137. :limit => @reflection.options[:limit],
  38138. :group => @reflection.options[:group],
  38139. :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
  38140. )
  38141. end
  38142. def construct_conditions
  38143. conditions = if @reflection.through_reflection.options[:as]
  38144. "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_id = #{@owner.quoted_id} " +
  38145. "AND #{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
  38146. else
  38147. "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.primary_key_name} = #{@owner.quoted_id}"
  38148. end
  38149. conditions << " AND (#{sql_conditions})" if sql_conditions
  38150. return conditions
  38151. end
  38152. def construct_from
  38153. @reflection.table_name
  38154. end
  38155. def construct_select(custom_select = nil)
  38156. selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
  38157. end
  38158. def construct_joins(custom_joins = nil)
  38159. polymorphic_join = nil
  38160. if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
  38161. reflection_primary_key = @reflection.klass.primary_key
  38162. source_primary_key = @reflection.source_reflection.primary_key_name
  38163. else
  38164. reflection_primary_key = @reflection.source_reflection.primary_key_name
  38165. source_primary_key = @reflection.klass.primary_key
  38166. if @reflection.source_reflection.options[:as]
  38167. polymorphic_join = "AND %s.%s = %s" % [
  38168. @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
  38169. @owner.class.quote(@reflection.through_reflection.klass.name)
  38170. ]
  38171. end
  38172. end
  38173. "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
  38174. @reflection.through_reflection.table_name,
  38175. @reflection.table_name, reflection_primary_key,
  38176. @reflection.through_reflection.table_name, source_primary_key,
  38177. polymorphic_join
  38178. ]
  38179. end
  38180. def construct_scope
  38181. {
  38182. :find => { :from => construct_from, :conditions => construct_conditions, :joins => construct_joins, :select => construct_select },
  38183. :create => { @reflection.primary_key_name => @owner.id }
  38184. }
  38185. end
  38186. def construct_sql
  38187. case
  38188. when @reflection.options[:finder_sql]
  38189. @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
  38190. @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
  38191. @finder_sql << " AND (#{conditions})" if conditions
  38192. end
  38193. if @reflection.options[:counter_sql]
  38194. @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
  38195. elsif @reflection.options[:finder_sql]
  38196. # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
  38197. @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
  38198. @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
  38199. else
  38200. @counter_sql = @finder_sql
  38201. end
  38202. end
  38203. def conditions
  38204. @conditions ||= [
  38205. (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
  38206. (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions])
  38207. ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions])
  38208. end
  38209. alias_method :sql_conditions, :conditions
  38210. end
  38211. end
  38212. end
  38213. module ActiveRecord
  38214. module Associations
  38215. class HasOneAssociation < BelongsToAssociation #:nodoc:
  38216. def initialize(owner, reflection)
  38217. super
  38218. construct_sql
  38219. end
  38220. def create(attributes = {}, replace_existing = true)
  38221. record = build(attributes, replace_existing)
  38222. record.save
  38223. record
  38224. end
  38225. def build(attributes = {}, replace_existing = true)
  38226. record = @reflection.klass.new(attributes)
  38227. if replace_existing
  38228. replace(record, true)
  38229. else
  38230. record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
  38231. self.target = record
  38232. end
  38233. record
  38234. end
  38235. def replace(obj, dont_save = false)
  38236. load_target
  38237. unless @target.nil?
  38238. if dependent? && !dont_save && @target != obj
  38239. @target.destroy unless @target.new_record?
  38240. @owner.clear_association_cache
  38241. else
  38242. @target[@reflection.primary_key_name] = nil
  38243. @target.save unless @owner.new_record? || @target.new_record?
  38244. end
  38245. end
  38246. if obj.nil?
  38247. @target = nil
  38248. else
  38249. raise_on_type_mismatch(obj)
  38250. set_belongs_to_association_for(obj)
  38251. @target = (AssociationProxy === obj ? obj.target : obj)
  38252. end
  38253. @loaded = true
  38254. unless @owner.new_record? or obj.nil? or dont_save
  38255. return (obj.save ? self : false)
  38256. else
  38257. return (obj.nil? ? nil : self)
  38258. end
  38259. end
  38260. private
  38261. def find_target
  38262. @reflection.klass.find(:first,
  38263. :conditions => @finder_sql,
  38264. :order => @reflection.options[:order],
  38265. :include => @reflection.options[:include]
  38266. )
  38267. end
  38268. def construct_sql
  38269. case
  38270. when @reflection.options[:as]
  38271. @finder_sql =
  38272. "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
  38273. "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
  38274. else
  38275. @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
  38276. end
  38277. @finder_sql << " AND (#{conditions})" if conditions
  38278. end
  38279. end
  38280. end
  38281. end
  38282. require 'active_record/associations/association_proxy'
  38283. require 'active_record/associations/association_collection'
  38284. require 'active_record/associations/belongs_to_association'
  38285. require 'active_record/associations/belongs_to_polymorphic_association'
  38286. require 'active_record/associations/has_one_association'
  38287. require 'active_record/associations/has_many_association'
  38288. require 'active_record/associations/has_many_through_association'
  38289. require 'active_record/associations/has_and_belongs_to_many_association'
  38290. require 'active_record/deprecated_associations'
  38291. module ActiveRecord
  38292. class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
  38293. def initialize(reflection)
  38294. @reflection = reflection
  38295. end
  38296. def message
  38297. "Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}"
  38298. end
  38299. end
  38300. class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
  38301. def initialize(owner_class_name, reflection, source_reflection)
  38302. @owner_class_name = owner_class_name
  38303. @reflection = reflection
  38304. @source_reflection = source_reflection
  38305. end
  38306. def message
  38307. "Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
  38308. end
  38309. end
  38310. class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
  38311. def initialize(reflection)
  38312. @reflection = reflection
  38313. @through_reflection = reflection.through_reflection
  38314. @source_reflection_names = reflection.source_reflection_names
  38315. @source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
  38316. end
  38317. def message
  38318. "Could not find the source association(s) #{@source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{@through_reflection.klass}. Try 'has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}, :source => <name>'. Is it one of #{@source_associations.to_sentence :connector => 'or'}?"
  38319. end
  38320. end
  38321. class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
  38322. def initialize(reflection)
  38323. @reflection = reflection
  38324. @through_reflection = reflection.through_reflection
  38325. @source_reflection = reflection.source_reflection
  38326. end
  38327. def message
  38328. "Invalid source reflection macro :#{@source_reflection.macro}#{" :through" if @source_reflection.options[:through]} for has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}. Use :source to specify the source reflection."
  38329. end
  38330. end
  38331. class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
  38332. def initialize(reflection)
  38333. @reflection = reflection
  38334. end
  38335. def message
  38336. "Can not eagerly load the polymorphic association #{@reflection.name.inspect}"
  38337. end
  38338. end
  38339. module Associations # :nodoc:
  38340. def self.append_features(base)
  38341. super
  38342. base.extend(ClassMethods)
  38343. end
  38344. # Clears out the association cache
  38345. def clear_association_cache #:nodoc:
  38346. self.class.reflect_on_all_associations.to_a.each do |assoc|
  38347. instance_variable_set "@#{assoc.name}", nil
  38348. end unless self.new_record?
  38349. end
  38350. # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
  38351. # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
  38352. # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr*
  38353. # methods. Example:
  38354. #
  38355. # class Project < ActiveRecord::Base
  38356. # belongs_to :portfolio
  38357. # has_one :project_manager
  38358. # has_many :milestones
  38359. # has_and_belongs_to_many :categories
  38360. # end
  38361. #
  38362. # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
  38363. # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
  38364. # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
  38365. # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
  38366. # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
  38367. # <tt>Project#milestones.build, Project#milestones.create</tt>
  38368. # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
  38369. # <tt>Project#categories.delete(category1)</tt>
  38370. #
  38371. # == Example
  38372. #
  38373. # link:files/examples/associations.png
  38374. #
  38375. # == Is it belongs_to or has_one?
  38376. #
  38377. # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
  38378. # saying belongs_to. Example:
  38379. #
  38380. # class Post < ActiveRecord::Base
  38381. # has_one :author
  38382. # end
  38383. #
  38384. # class Author < ActiveRecord::Base
  38385. # belongs_to :post
  38386. # end
  38387. #
  38388. # The tables for these classes could look something like:
  38389. #
  38390. # CREATE TABLE posts (
  38391. # id int(11) NOT NULL auto_increment,
  38392. # title varchar default NULL,
  38393. # PRIMARY KEY (id)
  38394. # )
  38395. #
  38396. # CREATE TABLE authors (
  38397. # id int(11) NOT NULL auto_increment,
  38398. # post_id int(11) default NULL,
  38399. # name varchar default NULL,
  38400. # PRIMARY KEY (id)
  38401. # )
  38402. #
  38403. # == Unsaved objects and associations
  38404. #
  38405. # You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
  38406. # aware of, mostly involving the saving of associated objects.
  38407. #
  38408. # === One-to-one associations
  38409. #
  38410. # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
  38411. # order to update their primary keys - except if the parent object is unsaved (new_record? == true).
  38412. # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
  38413. # is cancelled.
  38414. # * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
  38415. # * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
  38416. # not save the parent either.
  38417. #
  38418. # === Collections
  38419. #
  38420. # * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
  38421. # (the owner of the collection) is not yet stored in the database.
  38422. # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
  38423. # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
  38424. # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
  38425. #
  38426. # === Association callbacks
  38427. #
  38428. # Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
  38429. # trigged when you add an object to or removing an object from a association collection. Example:
  38430. #
  38431. # class Project
  38432. # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
  38433. #
  38434. # def evaluate_velocity(developer)
  38435. # ...
  38436. # end
  38437. # end
  38438. #
  38439. # It's possible to stack callbacks by passing them as an array. Example:
  38440. #
  38441. # class Project
  38442. # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
  38443. # end
  38444. #
  38445. # Possible callbacks are: before_add, after_add, before_remove and after_remove.
  38446. #
  38447. # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
  38448. # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
  38449. #
  38450. # === Association extensions
  38451. #
  38452. # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
  38453. # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
  38454. # Example:
  38455. #
  38456. # class Account < ActiveRecord::Base
  38457. # has_many :people do
  38458. # def find_or_create_by_name(name)
  38459. # first_name, last_name = name.split(" ", 2)
  38460. # find_or_create_by_first_name_and_last_name(first_name, last_name)
  38461. # end
  38462. # end
  38463. # end
  38464. #
  38465. # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
  38466. # person.first_name # => "David"
  38467. # person.last_name # => "Heinemeier Hansson"
  38468. #
  38469. # If you need to share the same extensions between many associations, you can use a named extension module. Example:
  38470. #
  38471. # module FindOrCreateByNameExtension
  38472. # def find_or_create_by_name(name)
  38473. # first_name, last_name = name.split(" ", 2)
  38474. # find_or_create_by_first_name_and_last_name(first_name, last_name)
  38475. # end
  38476. # end
  38477. #
  38478. # class Account < ActiveRecord::Base
  38479. # has_many :people, :extend => FindOrCreateByNameExtension
  38480. # end
  38481. #
  38482. # class Company < ActiveRecord::Base
  38483. # has_many :people, :extend => FindOrCreateByNameExtension
  38484. # end
  38485. #
  38486. # === Association Join Models
  38487. #
  38488. # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
  38489. # operates similarly to a <tt>has_and_belongs_to_many</tt> association. The advantage is that you're able to add validations,
  38490. # callbacks, and extra attributes on the join model. Consider the following schema:
  38491. #
  38492. # class Author < ActiveRecord::Base
  38493. # has_many :authorships
  38494. # has_many :books, :through => :authorships
  38495. # end
  38496. #
  38497. # class Authorship < ActiveRecord::Base
  38498. # belongs_to :author
  38499. # belongs_to :book
  38500. # end
  38501. #
  38502. # @author = Author.find :first
  38503. # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
  38504. # @author.books # selects all books by using the Authorship join model
  38505. #
  38506. # You can also go through a has_many association on the join model:
  38507. #
  38508. # class Firm < ActiveRecord::Base
  38509. # has_many :clients
  38510. # has_many :invoices, :through => :clients
  38511. # end
  38512. #
  38513. # class Client < ActiveRecord::Base
  38514. # belongs_to :firm
  38515. # has_many :invoices
  38516. # end
  38517. #
  38518. # class Invoice < ActiveRecord::Base
  38519. # belongs_to :client
  38520. # end
  38521. #
  38522. # @firm = Firm.find :first
  38523. # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
  38524. # @firm.invoices # selects all invoices by going through the Client join model.
  38525. #
  38526. # === Polymorphic Associations
  38527. #
  38528. # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
  38529. # specify an interface that a has_many association must adhere to.
  38530. #
  38531. # class Asset < ActiveRecord::Base
  38532. # belongs_to :attachable, :polymorphic => true
  38533. # end
  38534. #
  38535. # class Post < ActiveRecord::Base
  38536. # has_many :assets, :as => :attachable # The <tt>:as</tt> option specifies the polymorphic interface to use.
  38537. # end
  38538. #
  38539. # @asset.attachable = @post
  38540. #
  38541. # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
  38542. # an attachable_id integer column and an attachable_type string column.
  38543. #
  38544. # == Caching
  38545. #
  38546. # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
  38547. # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
  38548. # worrying too much about performance at the first go. Example:
  38549. #
  38550. # project.milestones # fetches milestones from the database
  38551. # project.milestones.size # uses the milestone cache
  38552. # project.milestones.empty? # uses the milestone cache
  38553. # project.milestones(true).size # fetches milestones from the database
  38554. # project.milestones # uses the milestone cache
  38555. #
  38556. # == Eager loading of associations
  38557. #
  38558. # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
  38559. # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
  38560. # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
  38561. #
  38562. # class Post < ActiveRecord::Base
  38563. # belongs_to :author
  38564. # has_many :comments
  38565. # end
  38566. #
  38567. # Consider the following loop using the class above:
  38568. #
  38569. # for post in Post.find(:all)
  38570. # puts "Post: " + post.title
  38571. # puts "Written by: " + post.author.name
  38572. # puts "Last comment on: " + post.comments.first.created_on
  38573. # end
  38574. #
  38575. # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
  38576. #
  38577. # for post in Post.find(:all, :include => :author)
  38578. #
  38579. # This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
  38580. # like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
  38581. #
  38582. # We can improve upon the situation further by referencing both associations in the finder with:
  38583. #
  38584. # for post in Post.find(:all, :include => [ :author, :comments ])
  38585. #
  38586. # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
  38587. # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
  38588. # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
  38589. # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
  38590. #
  38591. # Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions
  38592. # on these eager tables. This will work:
  38593. #
  38594. # Post.find(:all, :include => :comments, :conditions => "posts.title = 'magic forest'", :limit => 2)
  38595. #
  38596. # ...but this will not (and an ArgumentError will be raised):
  38597. #
  38598. # Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2)
  38599. #
  38600. # Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references
  38601. # in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
  38602. # you alter the :order and :conditions on the association definitions themselves.
  38603. #
  38604. # It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull
  38605. # additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
  38606. #
  38607. # == Table Aliasing
  38608. #
  38609. # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
  38610. # the standard table name is used. The second time, the table is aliased as #{reflection_name}_#{parent_table_name}. Indexes are appended
  38611. # for any more successive uses of the table name.
  38612. #
  38613. # Post.find :all, :include => :comments
  38614. # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
  38615. # Post.find :all, :include => :special_comments # STI
  38616. # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
  38617. # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
  38618. # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
  38619. #
  38620. # Acts as tree example:
  38621. #
  38622. # TreeMixin.find :all, :include => :children
  38623. # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
  38624. # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
  38625. # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
  38626. # LEFT OUTER JOIN parents_mixins ...
  38627. # TreeMixin.find :all, :include => {:children => {:parent => :children}}
  38628. # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
  38629. # LEFT OUTER JOIN parents_mixins ...
  38630. # LEFT OUTER JOIN mixins childrens_mixins_2
  38631. #
  38632. # Has and Belongs to Many join tables use the same idea, but add a _join suffix:
  38633. #
  38634. # Post.find :all, :include => :categories
  38635. # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
  38636. # Post.find :all, :include => {:categories => :posts}
  38637. # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
  38638. # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
  38639. # Post.find :all, :include => {:categories => {:posts => :categories}}
  38640. # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
  38641. # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
  38642. # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
  38643. #
  38644. # If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations..
  38645. #
  38646. # Post.find :all, :include => :comments, :joins => "inner join comments ..."
  38647. # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
  38648. # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
  38649. # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
  38650. # LEFT OUTER JOIN comments special_comments_posts ...
  38651. # INNER JOIN comments ...
  38652. #
  38653. # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
  38654. #
  38655. # == Modules
  38656. #
  38657. # By default, associations will look for objects within the current module scope. Consider:
  38658. #
  38659. # module MyApplication
  38660. # module Business
  38661. # class Firm < ActiveRecord::Base
  38662. # has_many :clients
  38663. # end
  38664. #
  38665. # class Company < ActiveRecord::Base; end
  38666. # end
  38667. # end
  38668. #
  38669. # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
  38670. # with a class in another module scope this can be done by specifying the complete class name, such as:
  38671. #
  38672. # module MyApplication
  38673. # module Business
  38674. # class Firm < ActiveRecord::Base; end
  38675. # end
  38676. #
  38677. # module Billing
  38678. # class Account < ActiveRecord::Base
  38679. # belongs_to :firm, :class_name => "MyApplication::Business::Firm"
  38680. # end
  38681. # end
  38682. # end
  38683. #
  38684. # == Type safety with ActiveRecord::AssociationTypeMismatch
  38685. #
  38686. # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
  38687. # get a ActiveRecord::AssociationTypeMismatch.
  38688. #
  38689. # == Options
  38690. #
  38691. # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
  38692. # possible.
  38693. module ClassMethods
  38694. # Adds the following methods for retrieval and query of collections of associated objects.
  38695. # +collection+ is replaced with the symbol passed as the first argument, so
  38696. # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
  38697. # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
  38698. # An empty array is returned if none are found.
  38699. # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
  38700. # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
  38701. # This will also destroy the objects if they're declared as belongs_to and dependent on this model.
  38702. # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
  38703. # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
  38704. # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
  38705. # are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
  38706. # and sets their foreign keys to NULL otherwise.
  38707. # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
  38708. # * <tt>collection.size</tt> - returns the number of associated objects.
  38709. # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
  38710. # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
  38711. # with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an
  38712. # associated object already exists, not if it's nil!
  38713. # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
  38714. # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
  38715. # *Note:* This only works if an associated object already exists, not if it's nil!
  38716. #
  38717. # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
  38718. # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
  38719. # * <tt>Firm#clients<<</tt>
  38720. # * <tt>Firm#clients.delete</tt>
  38721. # * <tt>Firm#clients=</tt>
  38722. # * <tt>Firm#client_ids=</tt>
  38723. # * <tt>Firm#clients.clear</tt>
  38724. # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
  38725. # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
  38726. # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
  38727. # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
  38728. # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
  38729. # The declaration can also include an options hash to specialize the behavior of the association.
  38730. #
  38731. # Options are:
  38732. # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
  38733. # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
  38734. # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
  38735. # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
  38736. # sql fragment, such as "price > 5 AND name LIKE 'B%'".
  38737. # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
  38738. # such as "last_name, first_name DESC"
  38739. # * <tt>:group</tt> - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment,
  38740. # such as "category"
  38741. # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
  38742. # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
  38743. # as the default foreign_key.
  38744. # * <tt>:dependent</tt> - if set to :destroy all the associated objects are destroyed
  38745. # alongside this object by calling their destroy method. If set to :delete_all all associated
  38746. # objects are deleted *without* calling their destroy method. If set to :nullify all associated
  38747. # objects' foreign keys are set to NULL *without* calling their save callbacks.
  38748. # NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy.
  38749. # May not be set if :exclusively_dependent is also set.
  38750. # * <tt>:exclusively_dependent</tt> - Deprecated; equivalent to :dependent => :delete_all. If set to true all
  38751. # the associated object are deleted in one SQL statement without having their
  38752. # before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
  38753. # clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
  38754. # May not be set if :dependent is also set.
  38755. # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
  38756. # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
  38757. # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
  38758. # specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
  38759. # * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
  38760. # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
  38761. # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  38762. # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
  38763. # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
  38764. # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
  38765. # include the joined columns.
  38766. # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
  38767. # * <tt>:through</tt>: Specifies a Join Model to perform the query through. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
  38768. # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
  38769. # or <tt>has_many</tt> association.
  38770. # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
  38771. # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
  38772. # +:subscriber+ on +Subscription+, unless a +:source+ is given.
  38773. #
  38774. # Option examples:
  38775. # has_many :comments, :order => "posted_on"
  38776. # has_many :comments, :include => :author
  38777. # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
  38778. # has_many :tracks, :order => "position", :dependent => :destroy
  38779. # has_many :comments, :dependent => :nullify
  38780. # has_many :tags, :as => :taggable
  38781. # has_many :subscribers, :through => :subscriptions, :source => :user
  38782. # has_many :subscribers, :class_name => "Person", :finder_sql =>
  38783. # 'SELECT DISTINCT people.* ' +
  38784. # 'FROM people p, post_subscriptions ps ' +
  38785. # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
  38786. # 'ORDER BY p.first_name'
  38787. def has_many(association_id, options = {}, &extension)
  38788. reflection = create_has_many_reflection(association_id, options, &extension)
  38789. configure_dependency_for_has_many(reflection)
  38790. if options[:through]
  38791. collection_reader_method(reflection, HasManyThroughAssociation)
  38792. else
  38793. add_multiple_associated_save_callbacks(reflection.name)
  38794. add_association_callbacks(reflection.name, reflection.options)
  38795. collection_accessor_methods(reflection, HasManyAssociation)
  38796. end
  38797. add_deprecated_api_for_has_many(reflection.name)
  38798. end
  38799. # Adds the following methods for retrieval and query of a single associated object.
  38800. # +association+ is replaced with the symbol passed as the first argument, so
  38801. # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
  38802. # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
  38803. # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
  38804. # and saves the associate object.
  38805. # * <tt>association.nil?</tt> - returns true if there is no associated object.
  38806. # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
  38807. # with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
  38808. # an association already exists. It will NOT work if the association is nil.
  38809. # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
  38810. # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
  38811. #
  38812. # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
  38813. # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
  38814. # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
  38815. # * <tt>Account#beneficiary.nil?</tt>
  38816. # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
  38817. # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
  38818. #
  38819. # The declaration can also include an options hash to specialize the behavior of the association.
  38820. #
  38821. # Options are:
  38822. # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
  38823. # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
  38824. # if the real class name is +Person+, you'll have to specify it with this option.
  38825. # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
  38826. # sql fragment, such as "rank = 5".
  38827. # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
  38828. # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
  38829. # * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also,
  38830. # association is assigned.
  38831. # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
  38832. # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
  38833. # as the default foreign_key.
  38834. # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
  38835. #
  38836. # Option examples:
  38837. # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
  38838. # has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
  38839. # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
  38840. # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
  38841. def has_one(association_id, options = {})
  38842. reflection = create_has_one_reflection(association_id, options)
  38843. module_eval do
  38844. after_save <<-EOF
  38845. association = instance_variable_get("@#{reflection.name}")
  38846. unless association.nil?
  38847. association["#{reflection.primary_key_name}"] = id
  38848. association.save(true)
  38849. end
  38850. EOF
  38851. end
  38852. association_accessor_methods(reflection, HasOneAssociation)
  38853. association_constructor_method(:build, reflection, HasOneAssociation)
  38854. association_constructor_method(:create, reflection, HasOneAssociation)
  38855. configure_dependency_for_has_one(reflection)
  38856. # deprecated api
  38857. deprecated_has_association_method(reflection.name)
  38858. deprecated_association_comparison_method(reflection.name, reflection.class_name)
  38859. end
  38860. # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
  38861. # +association+ is replaced with the symbol passed as the first argument, so
  38862. # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
  38863. # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
  38864. # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
  38865. # * <tt>association.nil?</tt> - returns true if there is no associated object.
  38866. # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
  38867. # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
  38868. # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
  38869. # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
  38870. #
  38871. # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
  38872. # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
  38873. # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
  38874. # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
  38875. # * <tt>Post#author.nil?</tt>
  38876. # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
  38877. # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
  38878. # The declaration can also include an options hash to specialize the behavior of the association.
  38879. #
  38880. # Options are:
  38881. # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
  38882. # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
  38883. # if the real class name is +Person+, you'll have to specify it with this option.
  38884. # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
  38885. # sql fragment, such as "authorized = 1".
  38886. # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
  38887. # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
  38888. # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
  38889. # of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
  38890. # +Boss+ class will use "boss_id" as the default foreign_key.
  38891. # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter
  38892. # and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
  38893. # destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
  38894. # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
  38895. # name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
  38896. # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
  38897. # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing true.
  38898. #
  38899. # Option examples:
  38900. # belongs_to :firm, :foreign_key => "client_of"
  38901. # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
  38902. # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
  38903. # :conditions => 'discounts > #{payments_count}'
  38904. # belongs_to :attachable, :polymorphic => true
  38905. def belongs_to(association_id, options = {})
  38906. reflection = create_belongs_to_reflection(association_id, options)
  38907. if reflection.options[:polymorphic]
  38908. association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
  38909. module_eval do
  38910. before_save <<-EOF
  38911. association = instance_variable_get("@#{reflection.name}")
  38912. if !association.nil?
  38913. if association.new_record?
  38914. association.save(true)
  38915. end
  38916. if association.updated?
  38917. self["#{reflection.primary_key_name}"] = association.id
  38918. self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
  38919. end
  38920. end
  38921. EOF
  38922. end
  38923. else
  38924. association_accessor_methods(reflection, BelongsToAssociation)
  38925. association_constructor_method(:build, reflection, BelongsToAssociation)
  38926. association_constructor_method(:create, reflection, BelongsToAssociation)
  38927. module_eval do
  38928. before_save <<-EOF
  38929. association = instance_variable_get("@#{reflection.name}")
  38930. if !association.nil?
  38931. if association.new_record?
  38932. association.save(true)
  38933. end
  38934. if association.updated?
  38935. self["#{reflection.primary_key_name}"] = association.id
  38936. end
  38937. end
  38938. EOF
  38939. end
  38940. # deprecated api
  38941. deprecated_has_association_method(reflection.name)
  38942. deprecated_association_comparison_method(reflection.name, reflection.class_name)
  38943. end
  38944. if options[:counter_cache]
  38945. cache_column = options[:counter_cache] == true ?
  38946. "#{self.to_s.underscore.pluralize}_count" :
  38947. options[:counter_cache]
  38948. module_eval(
  38949. "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
  38950. " unless #{reflection.name}.nil?'"
  38951. )
  38952. module_eval(
  38953. "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
  38954. " unless #{reflection.name}.nil?'"
  38955. )
  38956. end
  38957. end
  38958. # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
  38959. # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
  38960. # will give the default join table name of "developers_projects" because "D" outranks "P".
  38961. #
  38962. # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
  38963. # has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
  38964. # ReadOnly (because we can't save changes to the additional attrbutes). It's strongly recommended that you upgrade any
  38965. # associations with attributes to a real join model (see introduction).
  38966. #
  38967. # Adds the following methods for retrieval and query.
  38968. # +collection+ is replaced with the symbol passed as the first argument, so
  38969. # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
  38970. # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
  38971. # An empty array is returned if none is found.
  38972. # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
  38973. # (collection.push and collection.concat are aliases to this method).
  38974. # * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that
  38975. # also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional
  38976. # attributes on the join, which will be injected into the associated objects when they are retrieved through the collection.
  38977. # (collection.concat_with_attributes is an alias to this method). This method is now deprecated.
  38978. # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
  38979. # This does not destroy the objects.
  38980. # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
  38981. # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
  38982. # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
  38983. # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
  38984. # * <tt>collection.size</tt> - returns the number of associated objects.
  38985. # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
  38986. # meets the condition that it has to be associated with this object.
  38987. #
  38988. # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
  38989. # * <tt>Developer#projects</tt>
  38990. # * <tt>Developer#projects<<</tt>
  38991. # * <tt>Developer#projects.push_with_attributes</tt>
  38992. # * <tt>Developer#projects.delete</tt>
  38993. # * <tt>Developer#projects=</tt>
  38994. # * <tt>Developer#project_ids=</tt>
  38995. # * <tt>Developer#projects.clear</tt>
  38996. # * <tt>Developer#projects.empty?</tt>
  38997. # * <tt>Developer#projects.size</tt>
  38998. # * <tt>Developer#projects.find(id)</tt>
  38999. # The declaration may include an options hash to specialize the behavior of the association.
  39000. #
  39001. # Options are:
  39002. # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
  39003. # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
  39004. # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
  39005. # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
  39006. # WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
  39007. # has_and_belongs_to_many declaration in order to work.
  39008. # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
  39009. # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
  39010. # will use "person_id" as the default foreign_key.
  39011. # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
  39012. # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
  39013. # the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
  39014. # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
  39015. # sql fragment, such as "authorized = 1".
  39016. # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
  39017. # * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods
  39018. # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
  39019. # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated
  39020. # classes with a manual one
  39021. # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
  39022. # with a manual one
  39023. # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
  39024. # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
  39025. # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  39026. # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
  39027. # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
  39028. # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
  39029. # include the joined columns.
  39030. #
  39031. # Option examples:
  39032. # has_and_belongs_to_many :projects
  39033. # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
  39034. # has_and_belongs_to_many :nations, :class_name => "Country"
  39035. # has_and_belongs_to_many :categories, :join_table => "prods_cats"
  39036. # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
  39037. # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
  39038. def has_and_belongs_to_many(association_id, options = {}, &extension)
  39039. reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
  39040. add_multiple_associated_save_callbacks(reflection.name)
  39041. collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
  39042. # Don't use a before_destroy callback since users' before_destroy
  39043. # callbacks will be executed after the association is wiped out.
  39044. old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
  39045. class_eval <<-end_eval
  39046. alias_method :#{old_method}, :destroy_without_callbacks
  39047. def destroy_without_callbacks
  39048. #{reflection.name}.clear
  39049. #{old_method}
  39050. end
  39051. end_eval
  39052. add_association_callbacks(reflection.name, options)
  39053. # deprecated api
  39054. deprecated_collection_count_method(reflection.name)
  39055. deprecated_add_association_relation(reflection.name)
  39056. deprecated_remove_association_relation(reflection.name)
  39057. deprecated_has_collection_method(reflection.name)
  39058. end
  39059. private
  39060. def join_table_name(first_table_name, second_table_name)
  39061. if first_table_name < second_table_name
  39062. join_table = "#{first_table_name}_#{second_table_name}"
  39063. else
  39064. join_table = "#{second_table_name}_#{first_table_name}"
  39065. end
  39066. table_name_prefix + join_table + table_name_suffix
  39067. end
  39068. def association_accessor_methods(reflection, association_proxy_class)
  39069. define_method(reflection.name) do |*params|
  39070. force_reload = params.first unless params.empty?
  39071. association = instance_variable_get("@#{reflection.name}")
  39072. if association.nil? || force_reload
  39073. association = association_proxy_class.new(self, reflection)
  39074. retval = association.reload
  39075. unless retval.nil?
  39076. instance_variable_set("@#{reflection.name}", association)
  39077. else
  39078. instance_variable_set("@#{reflection.name}", nil)
  39079. return nil
  39080. end
  39081. end
  39082. association
  39083. end
  39084. define_method("#{reflection.name}=") do |new_value|
  39085. association = instance_variable_get("@#{reflection.name}")
  39086. if association.nil?
  39087. association = association_proxy_class.new(self, reflection)
  39088. end
  39089. association.replace(new_value)
  39090. unless new_value.nil?
  39091. instance_variable_set("@#{reflection.name}", association)
  39092. else
  39093. instance_variable_set("@#{reflection.name}", nil)
  39094. return nil
  39095. end
  39096. association
  39097. end
  39098. define_method("set_#{reflection.name}_target") do |target|
  39099. return if target.nil?
  39100. association = association_proxy_class.new(self, reflection)
  39101. association.target = target
  39102. instance_variable_set("@#{reflection.name}", association)
  39103. end
  39104. end
  39105. def collection_reader_method(reflection, association_proxy_class)
  39106. define_method(reflection.name) do |*params|
  39107. force_reload = params.first unless params.empty?
  39108. association = instance_variable_get("@#{reflection.name}")
  39109. unless association.respond_to?(:loaded?)
  39110. association = association_proxy_class.new(self, reflection)
  39111. instance_variable_set("@#{reflection.name}", association)
  39112. end
  39113. association.reload if force_reload
  39114. association
  39115. end
  39116. end
  39117. def collection_accessor_methods(reflection, association_proxy_class)
  39118. collection_reader_method(reflection, association_proxy_class)
  39119. define_method("#{reflection.name}=") do |new_value|
  39120. association = instance_variable_get("@#{reflection.name}")
  39121. unless association.respond_to?(:loaded?)
  39122. association = association_proxy_class.new(self, reflection)
  39123. instance_variable_set("@#{reflection.name}", association)
  39124. end
  39125. association.replace(new_value)
  39126. association
  39127. end
  39128. define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
  39129. send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
  39130. end
  39131. end
  39132. def require_association_class(class_name)
  39133. require_association(Inflector.underscore(class_name)) if class_name
  39134. end
  39135. def add_multiple_associated_save_callbacks(association_name)
  39136. method_name = "validate_associated_records_for_#{association_name}".to_sym
  39137. define_method(method_name) do
  39138. association = instance_variable_get("@#{association_name}")
  39139. if association.respond_to?(:loaded?)
  39140. if new_record?
  39141. association
  39142. else
  39143. association.select { |record| record.new_record? }
  39144. end.each do |record|
  39145. errors.add "#{association_name}" unless record.valid?
  39146. end
  39147. end
  39148. end
  39149. validate method_name
  39150. before_save("@new_record_before_save = new_record?; true")
  39151. after_callback = <<-end_eval
  39152. association = instance_variable_get("@#{association_name}")
  39153. if association.respond_to?(:loaded?)
  39154. if @new_record_before_save
  39155. records_to_save = association
  39156. else
  39157. records_to_save = association.select { |record| record.new_record? }
  39158. end
  39159. records_to_save.each { |record| association.send(:insert_record, record) }
  39160. association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
  39161. end
  39162. end_eval
  39163. # Doesn't use after_save as that would save associations added in after_create/after_update twice
  39164. after_create(after_callback)
  39165. after_update(after_callback)
  39166. end
  39167. def association_constructor_method(constructor, reflection, association_proxy_class)
  39168. define_method("#{constructor}_#{reflection.name}") do |*params|
  39169. attributees = params.first unless params.empty?
  39170. replace_existing = params[1].nil? ? true : params[1]
  39171. association = instance_variable_get("@#{reflection.name}")
  39172. if association.nil?
  39173. association = association_proxy_class.new(self, reflection)
  39174. instance_variable_set("@#{reflection.name}", association)
  39175. end
  39176. if association_proxy_class == HasOneAssociation
  39177. association.send(constructor, attributees, replace_existing)
  39178. else
  39179. association.send(constructor, attributees)
  39180. end
  39181. end
  39182. end
  39183. def count_with_associations(options = {})
  39184. catch :invalid_query do
  39185. join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
  39186. return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
  39187. end
  39188. 0
  39189. end
  39190. def find_with_associations(options = {})
  39191. catch :invalid_query do
  39192. join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
  39193. rows = select_all_rows(options, join_dependency)
  39194. return join_dependency.instantiate(rows)
  39195. end
  39196. []
  39197. end
  39198. def configure_dependency_for_has_many(reflection)
  39199. if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
  39200. raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
  39201. end
  39202. if reflection.options[:exclusively_dependent]
  39203. reflection.options[:dependent] = :delete_all
  39204. #warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.")
  39205. end
  39206. # See HasManyAssociation#delete_records. Dependent associations
  39207. # delete children, otherwise foreign key is set to NULL.
  39208. # Add polymorphic type if the :as option is present
  39209. dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
  39210. if reflection.options[:as]
  39211. dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
  39212. end
  39213. case reflection.options[:dependent]
  39214. when :destroy, true
  39215. module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
  39216. when :delete_all
  39217. module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
  39218. when :nullify
  39219. module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
  39220. when nil, false
  39221. # pass
  39222. else
  39223. raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
  39224. end
  39225. end
  39226. def configure_dependency_for_has_one(reflection)
  39227. case reflection.options[:dependent]
  39228. when :destroy, true
  39229. module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
  39230. when :nullify
  39231. module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
  39232. when nil, false
  39233. # pass
  39234. else
  39235. raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
  39236. end
  39237. end
  39238. def add_deprecated_api_for_has_many(association_name)
  39239. deprecated_collection_count_method(association_name)
  39240. deprecated_add_association_relation(association_name)
  39241. deprecated_remove_association_relation(association_name)
  39242. deprecated_has_collection_method(association_name)
  39243. deprecated_find_in_collection_method(association_name)
  39244. deprecated_find_all_in_collection_method(association_name)
  39245. deprecated_collection_create_method(association_name)
  39246. deprecated_collection_build_method(association_name)
  39247. end
  39248. def create_has_many_reflection(association_id, options, &extension)
  39249. options.assert_valid_keys(
  39250. :class_name, :table_name, :foreign_key,
  39251. :exclusively_dependent, :dependent,
  39252. :select, :conditions, :include, :order, :group, :limit, :offset,
  39253. :as, :through, :source,
  39254. :finder_sql, :counter_sql,
  39255. :before_add, :after_add, :before_remove, :after_remove,
  39256. :extend
  39257. )
  39258. options[:extend] = create_extension_module(association_id, extension) if block_given?
  39259. create_reflection(:has_many, association_id, options, self)
  39260. end
  39261. def create_has_one_reflection(association_id, options)
  39262. options.assert_valid_keys(
  39263. :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
  39264. )
  39265. create_reflection(:has_one, association_id, options, self)
  39266. end
  39267. def create_belongs_to_reflection(association_id, options)
  39268. options.assert_valid_keys(
  39269. :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
  39270. :counter_cache, :extend, :polymorphic
  39271. )
  39272. reflection = create_reflection(:belongs_to, association_id, options, self)
  39273. if options[:polymorphic]
  39274. reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
  39275. end
  39276. reflection
  39277. end
  39278. def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
  39279. options.assert_valid_keys(
  39280. :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
  39281. :select, :conditions, :include, :order, :group, :limit, :offset,
  39282. :finder_sql, :delete_sql, :insert_sql, :uniq,
  39283. :before_add, :after_add, :before_remove, :after_remove,
  39284. :extend
  39285. )
  39286. options[:extend] = create_extension_module(association_id, extension) if block_given?
  39287. reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
  39288. reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
  39289. reflection
  39290. end
  39291. def reflect_on_included_associations(associations)
  39292. [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
  39293. end
  39294. def guard_against_unlimitable_reflections(reflections, options)
  39295. if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
  39296. raise(
  39297. ConfigurationError,
  39298. "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
  39299. )
  39300. end
  39301. end
  39302. def select_all_rows(options, join_dependency)
  39303. connection.select_all(
  39304. construct_finder_sql_with_included_associations(options, join_dependency),
  39305. "#{name} Load Including Associations"
  39306. )
  39307. end
  39308. def construct_counter_sql_with_included_associations(options, join_dependency)
  39309. scope = scope(:find)
  39310. sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
  39311. # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
  39312. if !Base.connection.supports_count_distinct?
  39313. sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
  39314. end
  39315. sql << " FROM #{table_name} "
  39316. sql << join_dependency.join_associations.collect{|join| join.association_join }.join
  39317. add_joins!(sql, options, scope)
  39318. add_conditions!(sql, options[:conditions], scope)
  39319. add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
  39320. add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
  39321. if !Base.connection.supports_count_distinct?
  39322. sql << ")"
  39323. end
  39324. return sanitize_sql(sql)
  39325. end
  39326. def construct_finder_sql_with_included_associations(options, join_dependency)
  39327. scope = scope(:find)
  39328. sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
  39329. sql << join_dependency.join_associations.collect{|join| join.association_join }.join
  39330. add_joins!(sql, options, scope)
  39331. add_conditions!(sql, options[:conditions], scope)
  39332. add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
  39333. sql << "ORDER BY #{options[:order]} " if options[:order]
  39334. add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
  39335. return sanitize_sql(sql)
  39336. end
  39337. def add_limited_ids_condition!(sql, options, join_dependency)
  39338. unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
  39339. sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
  39340. else
  39341. throw :invalid_query
  39342. end
  39343. end
  39344. def select_limited_ids_list(options, join_dependency)
  39345. connection.select_all(
  39346. construct_finder_sql_for_association_limiting(options, join_dependency),
  39347. "#{name} Load IDs For Limited Eager Loading"
  39348. ).collect { |row| connection.quote(row[primary_key]) }.join(", ")
  39349. end
  39350. def construct_finder_sql_for_association_limiting(options, join_dependency)
  39351. scope = scope(:find)
  39352. sql = "SELECT "
  39353. sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
  39354. sql << primary_key
  39355. sql << ", #{options[:order].split(',').collect { |s| s.split.first } * ', '}" if options[:order] && (include_eager_conditions?(options) || include_eager_order?(options))
  39356. sql << " FROM #{table_name} "
  39357. if include_eager_conditions?(options) || include_eager_order?(options)
  39358. sql << join_dependency.join_associations.collect{|join| join.association_join }.join
  39359. add_joins!(sql, options, scope)
  39360. end
  39361. add_conditions!(sql, options[:conditions], scope)
  39362. sql << "ORDER BY #{options[:order]} " if options[:order]
  39363. add_limit!(sql, options, scope)
  39364. return sanitize_sql(sql)
  39365. end
  39366. # Checks if the conditions reference a table other than the current model table
  39367. def include_eager_conditions?(options)
  39368. # look in both sets of conditions
  39369. conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
  39370. case cond
  39371. when nil then all
  39372. when Array then all << cond.first
  39373. else all << cond
  39374. end
  39375. end
  39376. return false unless conditions.any?
  39377. conditions.join(' ').scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name|
  39378. condition_table_name != table_name
  39379. end
  39380. end
  39381. # Checks if the query order references a table other than the current model's table.
  39382. def include_eager_order?(options)
  39383. order = options[:order]
  39384. return false unless order
  39385. order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name|
  39386. order_table_name != table_name
  39387. end
  39388. end
  39389. def using_limitable_reflections?(reflections)
  39390. reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
  39391. end
  39392. def column_aliases(join_dependency)
  39393. join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
  39394. "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
  39395. end
  39396. def add_association_callbacks(association_name, options)
  39397. callbacks = %w(before_add after_add before_remove after_remove)
  39398. callbacks.each do |callback_name|
  39399. full_callback_name = "#{callback_name.to_s}_for_#{association_name.to_s}"
  39400. defined_callbacks = options[callback_name.to_sym]
  39401. if options.has_key?(callback_name.to_sym)
  39402. class_inheritable_reader full_callback_name.to_sym
  39403. write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
  39404. end
  39405. end
  39406. end
  39407. def condition_word(sql)
  39408. sql =~ /where/i ? " AND " : "WHERE "
  39409. end
  39410. def create_extension_module(association_id, extension)
  39411. extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
  39412. silence_warnings do
  39413. Object.const_set(extension_module_name, Module.new(&extension))
  39414. end
  39415. extension_module_name.constantize
  39416. end
  39417. class JoinDependency
  39418. attr_reader :joins, :reflections, :table_aliases
  39419. def initialize(base, associations, joins)
  39420. @joins = [JoinBase.new(base, joins)]
  39421. @associations = associations
  39422. @reflections = []
  39423. @base_records_hash = {}
  39424. @base_records_in_order = []
  39425. @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
  39426. @table_aliases[base.table_name] = 1
  39427. build(associations)
  39428. end
  39429. def join_associations
  39430. @joins[1..-1].to_a
  39431. end
  39432. def join_base
  39433. @joins[0]
  39434. end
  39435. def instantiate(rows)
  39436. rows.each_with_index do |row, i|
  39437. primary_id = join_base.record_id(row)
  39438. unless @base_records_hash[primary_id]
  39439. @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
  39440. end
  39441. construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
  39442. end
  39443. return @base_records_in_order
  39444. end
  39445. def aliased_table_names_for(table_name)
  39446. joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
  39447. end
  39448. protected
  39449. def build(associations, parent = nil)
  39450. parent ||= @joins.last
  39451. case associations
  39452. when Symbol, String
  39453. reflection = parent.reflections[associations.to_s.intern] or
  39454. raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
  39455. @reflections << reflection
  39456. @joins << JoinAssociation.new(reflection, self, parent)
  39457. when Array
  39458. associations.each do |association|
  39459. build(association, parent)
  39460. end
  39461. when Hash
  39462. associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
  39463. build(name, parent)
  39464. build(associations[name])
  39465. end
  39466. else
  39467. raise ConfigurationError, associations.inspect
  39468. end
  39469. end
  39470. def construct(parent, associations, joins, row)
  39471. case associations
  39472. when Symbol, String
  39473. while (join = joins.shift).reflection.name.to_s != associations.to_s
  39474. raise ConfigurationError, "Not Enough Associations" if joins.empty?
  39475. end
  39476. construct_association(parent, join, row)
  39477. when Array
  39478. associations.each do |association|
  39479. construct(parent, association, joins, row)
  39480. end
  39481. when Hash
  39482. associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
  39483. association = construct_association(parent, joins.shift, row)
  39484. construct(association, associations[name], joins, row) if association
  39485. end
  39486. else
  39487. raise ConfigurationError, associations.inspect
  39488. end
  39489. end
  39490. def construct_association(record, join, row)
  39491. case join.reflection.macro
  39492. when :has_many, :has_and_belongs_to_many
  39493. collection = record.send(join.reflection.name)
  39494. collection.loaded
  39495. return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
  39496. association = join.instantiate(row)
  39497. collection.target.push(association) unless collection.target.include?(association)
  39498. when :has_one, :belongs_to
  39499. return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
  39500. association = join.instantiate(row)
  39501. record.send("set_#{join.reflection.name}_target", association)
  39502. else
  39503. raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
  39504. end
  39505. return association
  39506. end
  39507. class JoinBase
  39508. attr_reader :active_record, :table_joins
  39509. delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
  39510. def initialize(active_record, joins = nil)
  39511. @active_record = active_record
  39512. @cached_record = {}
  39513. @table_joins = joins
  39514. end
  39515. def aliased_prefix
  39516. "t0"
  39517. end
  39518. def aliased_primary_key
  39519. "#{ aliased_prefix }_r0"
  39520. end
  39521. def aliased_table_name
  39522. active_record.table_name
  39523. end
  39524. def column_names_with_alias
  39525. unless @column_names_with_alias
  39526. @column_names_with_alias = []
  39527. ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
  39528. @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
  39529. end
  39530. end
  39531. return @column_names_with_alias
  39532. end
  39533. def extract_record(row)
  39534. column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
  39535. end
  39536. def record_id(row)
  39537. row[aliased_primary_key]
  39538. end
  39539. def instantiate(row)
  39540. @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
  39541. end
  39542. end
  39543. class JoinAssociation < JoinBase
  39544. attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
  39545. delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
  39546. def initialize(reflection, join_dependency, parent = nil)
  39547. reflection.check_validity!
  39548. if reflection.options[:polymorphic]
  39549. raise EagerLoadPolymorphicError.new(reflection)
  39550. end
  39551. super(reflection.klass)
  39552. @parent = parent
  39553. @reflection = reflection
  39554. @aliased_prefix = "t#{ join_dependency.joins.size }"
  39555. @aliased_table_name = table_name # start with the table name
  39556. @parent_table_name = parent.active_record.table_name
  39557. if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
  39558. join_dependency.table_aliases[aliased_table_name] += 1
  39559. end
  39560. unless join_dependency.table_aliases[aliased_table_name].zero?
  39561. # if the table name has been used, then use an alias
  39562. @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
  39563. table_index = join_dependency.table_aliases[aliased_table_name]
  39564. @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
  39565. end
  39566. join_dependency.table_aliases[aliased_table_name] += 1
  39567. if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
  39568. @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
  39569. unless join_dependency.table_aliases[aliased_join_table_name].zero?
  39570. @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
  39571. table_index = join_dependency.table_aliases[aliased_join_table_name]
  39572. @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
  39573. end
  39574. join_dependency.table_aliases[aliased_join_table_name] += 1
  39575. end
  39576. end
  39577. def association_join
  39578. join = case reflection.macro
  39579. when :has_and_belongs_to_many
  39580. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39581. table_alias_for(options[:join_table], aliased_join_table_name),
  39582. aliased_join_table_name,
  39583. options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
  39584. reflection.active_record.table_name, reflection.active_record.primary_key] +
  39585. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39586. table_name_and_alias, aliased_table_name, klass.primary_key,
  39587. aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
  39588. ]
  39589. when :has_many, :has_one
  39590. case
  39591. when reflection.macro == :has_many && reflection.options[:through]
  39592. through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
  39593. if through_reflection.options[:as] # has_many :through against a polymorphic join
  39594. polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
  39595. polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
  39596. " LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
  39597. table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
  39598. aliased_join_table_name, polymorphic_foreign_key,
  39599. parent.aliased_table_name, parent.primary_key,
  39600. aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] +
  39601. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
  39602. aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
  39603. ]
  39604. else
  39605. if source_reflection.macro == :has_many && source_reflection.options[:as]
  39606. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39607. table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
  39608. through_reflection.primary_key_name,
  39609. parent.aliased_table_name, parent.primary_key] +
  39610. " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
  39611. table_name_and_alias,
  39612. aliased_table_name, "#{source_reflection.options[:as]}_id",
  39613. aliased_join_table_name, options[:foreign_key] || primary_key,
  39614. aliased_table_name, "#{source_reflection.options[:as]}_type",
  39615. klass.quote(source_reflection.active_record.base_class.name)
  39616. ]
  39617. else
  39618. case source_reflection.macro
  39619. when :belongs_to
  39620. first_key = primary_key
  39621. second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
  39622. when :has_many
  39623. first_key = through_reflection.klass.to_s.classify.foreign_key
  39624. second_key = options[:foreign_key] || primary_key
  39625. end
  39626. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39627. table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
  39628. through_reflection.primary_key_name,
  39629. parent.aliased_table_name, parent.primary_key] +
  39630. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39631. table_name_and_alias,
  39632. aliased_table_name, first_key,
  39633. aliased_join_table_name, second_key
  39634. ]
  39635. end
  39636. end
  39637. when reflection.macro == :has_many && reflection.options[:as]
  39638. " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
  39639. table_name_and_alias,
  39640. aliased_table_name, "#{reflection.options[:as]}_id",
  39641. parent.aliased_table_name, parent.primary_key,
  39642. aliased_table_name, "#{reflection.options[:as]}_type",
  39643. klass.quote(parent.active_record.base_class.name)
  39644. ]
  39645. when reflection.macro == :has_one && reflection.options[:as]
  39646. " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
  39647. table_name_and_alias,
  39648. aliased_table_name, "#{reflection.options[:as]}_id",
  39649. parent.aliased_table_name, parent.primary_key,
  39650. aliased_table_name, "#{reflection.options[:as]}_type",
  39651. klass.quote(reflection.active_record.base_class.name)
  39652. ]
  39653. else
  39654. foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
  39655. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39656. table_name_and_alias,
  39657. aliased_table_name, foreign_key,
  39658. parent.aliased_table_name, parent.primary_key
  39659. ]
  39660. end
  39661. when :belongs_to
  39662. " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  39663. table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
  39664. parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
  39665. ]
  39666. else
  39667. ""
  39668. end || ''
  39669. join << %(AND %s.%s = %s ) % [
  39670. aliased_table_name,
  39671. reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
  39672. klass.quote(klass.name)] unless klass.descends_from_active_record?
  39673. join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
  39674. join
  39675. end
  39676. protected
  39677. def pluralize(table_name)
  39678. ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
  39679. end
  39680. def table_alias_for(table_name, table_alias)
  39681. "#{table_name} #{table_alias if table_name != table_alias}".strip
  39682. end
  39683. def table_name_and_alias
  39684. table_alias_for table_name, @aliased_table_name
  39685. end
  39686. def interpolate_sql(sql)
  39687. instance_eval("%@#{sql.gsub('@', '\@')}@")
  39688. end
  39689. end
  39690. end
  39691. end
  39692. end
  39693. end
  39694. require 'yaml'
  39695. require 'set'
  39696. require 'active_record/deprecated_finders'
  39697. module ActiveRecord #:nodoc:
  39698. class ActiveRecordError < StandardError #:nodoc:
  39699. end
  39700. class SubclassNotFound < ActiveRecordError #:nodoc:
  39701. end
  39702. class AssociationTypeMismatch < ActiveRecordError #:nodoc:
  39703. end
  39704. class SerializationTypeMismatch < ActiveRecordError #:nodoc:
  39705. end
  39706. class AdapterNotSpecified < ActiveRecordError # :nodoc:
  39707. end
  39708. class AdapterNotFound < ActiveRecordError # :nodoc:
  39709. end
  39710. class ConnectionNotEstablished < ActiveRecordError #:nodoc:
  39711. end
  39712. class ConnectionFailed < ActiveRecordError #:nodoc:
  39713. end
  39714. class RecordNotFound < ActiveRecordError #:nodoc:
  39715. end
  39716. class RecordNotSaved < ActiveRecordError #:nodoc:
  39717. end
  39718. class StatementInvalid < ActiveRecordError #:nodoc:
  39719. end
  39720. class PreparedStatementInvalid < ActiveRecordError #:nodoc:
  39721. end
  39722. class StaleObjectError < ActiveRecordError #:nodoc:
  39723. end
  39724. class ConfigurationError < StandardError #:nodoc:
  39725. end
  39726. class ReadOnlyRecord < StandardError #:nodoc:
  39727. end
  39728. class AttributeAssignmentError < ActiveRecordError #:nodoc:
  39729. attr_reader :exception, :attribute
  39730. def initialize(message, exception, attribute)
  39731. @exception = exception
  39732. @attribute = attribute
  39733. @message = message
  39734. end
  39735. end
  39736. class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
  39737. attr_reader :errors
  39738. def initialize(errors)
  39739. @errors = errors
  39740. end
  39741. end
  39742. # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
  39743. # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
  39744. # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
  39745. # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
  39746. #
  39747. # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
  39748. #
  39749. # == Creation
  39750. #
  39751. # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
  39752. # you're receiving the data from somewhere else, like a HTTP request. It works like this:
  39753. #
  39754. # user = User.new(:name => "David", :occupation => "Code Artist")
  39755. # user.name # => "David"
  39756. #
  39757. # You can also use block initialization:
  39758. #
  39759. # user = User.new do |u|
  39760. # u.name = "David"
  39761. # u.occupation = "Code Artist"
  39762. # end
  39763. #
  39764. # And of course you can just create a bare object and specify the attributes after the fact:
  39765. #
  39766. # user = User.new
  39767. # user.name = "David"
  39768. # user.occupation = "Code Artist"
  39769. #
  39770. # == Conditions
  39771. #
  39772. # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
  39773. # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
  39774. # be used for statements that don't involve tainted data. Examples:
  39775. #
  39776. # User < ActiveRecord::Base
  39777. # def self.authenticate_unsafely(user_name, password)
  39778. # find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
  39779. # end
  39780. #
  39781. # def self.authenticate_safely(user_name, password)
  39782. # find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
  39783. # end
  39784. # end
  39785. #
  39786. # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
  39787. # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
  39788. # on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
  39789. # an attacker can't escape the query and fake the login (or worse).
  39790. #
  39791. # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
  39792. # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
  39793. # the question marks with symbols and supplying a hash with values for the matching symbol keys:
  39794. #
  39795. # Company.find(:first, [
  39796. # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
  39797. # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
  39798. # ])
  39799. #
  39800. # == Overwriting default accessors
  39801. #
  39802. # All column values are automatically available through basic accessors on the Active Record object, but some times you
  39803. # want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
  39804. # name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
  39805. # Example:
  39806. #
  39807. # class Song < ActiveRecord::Base
  39808. # # Uses an integer of seconds to hold the length of the song
  39809. #
  39810. # def length=(minutes)
  39811. # write_attribute(:length, minutes * 60)
  39812. # end
  39813. #
  39814. # def length
  39815. # read_attribute(:length) / 60
  39816. # end
  39817. # end
  39818. #
  39819. # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
  39820. # read_attribute(:attribute) as a shorter form.
  39821. #
  39822. # == Accessing attributes before they have been typecasted
  39823. #
  39824. # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
  39825. # That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
  39826. # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
  39827. #
  39828. # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
  39829. # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
  39830. # want.
  39831. #
  39832. # == Dynamic attribute-based finders
  39833. #
  39834. # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
  39835. # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
  39836. # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
  39837. # <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
  39838. # And instead of writing <tt>Person.find(:all, ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
  39839. #
  39840. # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
  39841. # <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
  39842. # <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
  39843. # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
  39844. #
  39845. # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
  39846. # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
  39847. # actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
  39848. #
  39849. # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
  39850. # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Example:
  39851. #
  39852. # # No 'Summer' tag exists
  39853. # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
  39854. #
  39855. # # Now the 'Summer' tag does exist
  39856. # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
  39857. #
  39858. # == Saving arrays, hashes, and other non-mappable objects in text columns
  39859. #
  39860. # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
  39861. # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
  39862. #
  39863. # class User < ActiveRecord::Base
  39864. # serialize :preferences
  39865. # end
  39866. #
  39867. # user = User.create(:preferences => { "background" => "black", "display" => large })
  39868. # User.find(user.id).preferences # => { "background" => "black", "display" => large }
  39869. #
  39870. # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
  39871. # descendent of a class not in the hierarchy. Example:
  39872. #
  39873. # class User < ActiveRecord::Base
  39874. # serialize :preferences, Hash
  39875. # end
  39876. #
  39877. # user = User.create(:preferences => %w( one two three ))
  39878. # User.find(user.id).preferences # raises SerializationTypeMismatch
  39879. #
  39880. # == Single table inheritance
  39881. #
  39882. # Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
  39883. # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
  39884. #
  39885. # class Company < ActiveRecord::Base; end
  39886. # class Firm < Company; end
  39887. # class Client < Company; end
  39888. # class PriorityClient < Client; end
  39889. #
  39890. # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
  39891. # fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
  39892. #
  39893. # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
  39894. # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
  39895. #
  39896. # Note, all the attributes for all the cases are kept in the same table. Read more:
  39897. # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
  39898. #
  39899. # == Connection to multiple databases in different models
  39900. #
  39901. # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
  39902. # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
  39903. # For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
  39904. # and Course *and all its subclasses* will use this connection instead.
  39905. #
  39906. # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
  39907. # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
  39908. #
  39909. # == Exceptions
  39910. #
  39911. # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
  39912. # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
  39913. # <tt>:adapter</tt> key.
  39914. # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
  39915. # (or a bad spelling of an existing one).
  39916. # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
  39917. # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
  39918. # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
  39919. # * +RecordNotFound+ -- no record responded to the find* method.
  39920. # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
  39921. # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
  39922. # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
  39923. # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
  39924. # +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
  39925. # objects that should be inspected to determine which attributes triggered the errors.
  39926. # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
  39927. # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
  39928. #
  39929. # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
  39930. # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
  39931. # instances in the current object space.
  39932. class Base
  39933. # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
  39934. # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
  39935. cattr_accessor :logger
  39936. include Reloadable::Subclasses
  39937. def self.inherited(child) #:nodoc:
  39938. @@subclasses[self] ||= []
  39939. @@subclasses[self] << child
  39940. super
  39941. end
  39942. def self.reset_subclasses #:nodoc:
  39943. nonreloadables = []
  39944. subclasses.each do |klass|
  39945. unless klass.reloadable?
  39946. nonreloadables << klass
  39947. next
  39948. end
  39949. klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
  39950. klass.instance_methods(false).each { |m| klass.send :undef_method, m }
  39951. end
  39952. @@subclasses = {}
  39953. nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
  39954. end
  39955. @@subclasses = {}
  39956. cattr_accessor :configurations
  39957. @@configurations = {}
  39958. # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
  39959. # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
  39960. # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
  39961. # that this is a global setting for all Active Records.
  39962. cattr_accessor :primary_key_prefix_type
  39963. @@primary_key_prefix_type = nil
  39964. # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
  39965. # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
  39966. # for tables in a shared database. By default, the prefix is the empty string.
  39967. cattr_accessor :table_name_prefix
  39968. @@table_name_prefix = ""
  39969. # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
  39970. # "people_basecamp"). By default, the suffix is the empty string.
  39971. cattr_accessor :table_name_suffix
  39972. @@table_name_suffix = ""
  39973. # Indicates whether or not table names should be the pluralized versions of the corresponding class names.
  39974. # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
  39975. # See table_name for the full rules on table/class naming. This is true, by default.
  39976. cattr_accessor :pluralize_table_names
  39977. @@pluralize_table_names = true
  39978. # Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
  39979. # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
  39980. # may complicate matters if you use software like syslog. This is true, by default.
  39981. cattr_accessor :colorize_logging
  39982. @@colorize_logging = true
  39983. # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
  39984. # This is set to :local by default.
  39985. cattr_accessor :default_timezone
  39986. @@default_timezone = :local
  39987. # Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
  39988. # Defaults to false. Set to true if you're writing a threaded application.
  39989. cattr_accessor :allow_concurrency
  39990. @@allow_concurrency = false
  39991. # Determines whether to speed up access by generating optimized reader
  39992. # methods to avoid expensive calls to method_missing when accessing
  39993. # attributes by name. You might want to set this to false in development
  39994. # mode, because the methods would be regenerated on each request.
  39995. cattr_accessor :generate_read_methods
  39996. @@generate_read_methods = true
  39997. # Specifies the format to use when dumping the database schema with Rails'
  39998. # Rakefile. If :sql, the schema is dumped as (potentially database-
  39999. # specific) SQL statements. If :ruby, the schema is dumped as an
  40000. # ActiveRecord::Schema file which can be loaded into any database that
  40001. # supports migrations. Use :ruby if you want to have different database
  40002. # adapters for, e.g., your development and test environments.
  40003. cattr_accessor :schema_format
  40004. @@schema_format = :ruby
  40005. class << self # Class methods
  40006. # Find operates with three different retrieval approaches:
  40007. #
  40008. # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
  40009. # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
  40010. # * Find first: This will return the first record matched by the options used. These options can either be specific
  40011. # conditions or merely an order. If no record can matched, nil is returned.
  40012. # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
  40013. #
  40014. # All approaches accept an option hash as their last parameter. The options are:
  40015. #
  40016. # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
  40017. # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
  40018. # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  40019. # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
  40020. # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
  40021. # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
  40022. # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
  40023. # Pass :readonly => false to override.
  40024. # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
  40025. # to already defined associations. See eager loading under Associations.
  40026. # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
  40027. # include the joined columns.
  40028. # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
  40029. #
  40030. # Examples for find by id:
  40031. # Person.find(1) # returns the object for ID = 1
  40032. # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
  40033. # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
  40034. # Person.find([1]) # returns an array for objects the object with ID = 1
  40035. # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
  40036. #
  40037. # Examples for find first:
  40038. # Person.find(:first) # returns the first object fetched by SELECT * FROM people
  40039. # Person.find(:first, :conditions => [ "user_name = ?", user_name])
  40040. # Person.find(:first, :order => "created_on DESC", :offset => 5)
  40041. #
  40042. # Examples for find all:
  40043. # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
  40044. # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
  40045. # Person.find(:all, :offset => 10, :limit => 10)
  40046. # Person.find(:all, :include => [ :account, :friends ])
  40047. # Person.find(:all, :group => "category")
  40048. def find(*args)
  40049. options = extract_options_from_args!(args)
  40050. validate_find_options(options)
  40051. set_readonly_option!(options)
  40052. case args.first
  40053. when :first then find_initial(options)
  40054. when :all then find_every(options)
  40055. else find_from_ids(args, options)
  40056. end
  40057. end
  40058. # Works like find(:all), but requires a complete SQL string. Examples:
  40059. # Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
  40060. # Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
  40061. def find_by_sql(sql)
  40062. connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
  40063. end
  40064. # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
  40065. # Example:
  40066. # Person.exists?(5)
  40067. def exists?(id)
  40068. !find(:first, :conditions => ["#{primary_key} = ?", id]).nil? rescue false
  40069. end
  40070. # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
  40071. # fails under validations, the unsaved object is still returned.
  40072. def create(attributes = nil)
  40073. if attributes.is_a?(Array)
  40074. attributes.collect { |attr| create(attr) }
  40075. else
  40076. object = new(attributes)
  40077. scope(:create).each { |att,value| object.send("#{att}=", value) } if scoped?(:create)
  40078. object.save
  40079. object
  40080. end
  40081. end
  40082. # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
  40083. # and returns it. If the save fails under validations, the unsaved object is still returned.
  40084. #
  40085. # The arguments may also be given as arrays in which case the update method is called for each pair of +id+ and
  40086. # +attributes+ and an array of objects is returned.
  40087. #
  40088. # Example of updating one record:
  40089. # Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
  40090. #
  40091. # Example of updating multiple records:
  40092. # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
  40093. # Person.update(people.keys, people.values)
  40094. def update(id, attributes)
  40095. if id.is_a?(Array)
  40096. idx = -1
  40097. id.collect { |id| idx += 1; update(id, attributes[idx]) }
  40098. else
  40099. object = find(id)
  40100. object.update_attributes(attributes)
  40101. object
  40102. end
  40103. end
  40104. # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
  40105. # are deleted.
  40106. def delete(id)
  40107. delete_all([ "#{primary_key} IN (?)", id ])
  40108. end
  40109. # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
  40110. # If an array of ids is provided, all of them are destroyed.
  40111. def destroy(id)
  40112. id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
  40113. end
  40114. # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
  40115. # A subset of the records can be selected by specifying +conditions+. Example:
  40116. # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
  40117. def update_all(updates, conditions = nil)
  40118. sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
  40119. add_conditions!(sql, conditions, scope(:find))
  40120. connection.update(sql, "#{name} Update")
  40121. end
  40122. # Destroys the objects for all the records that match the +condition+ by instantiating each object and calling
  40123. # the destroy method. Example:
  40124. # Person.destroy_all "last_login < '2004-04-04'"
  40125. def destroy_all(conditions = nil)
  40126. find(:all, :conditions => conditions).each { |object| object.destroy }
  40127. end
  40128. # Deletes all the records that match the +condition+ without instantiating the objects first (and hence not
  40129. # calling the destroy method). Example:
  40130. # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
  40131. def delete_all(conditions = nil)
  40132. sql = "DELETE FROM #{table_name} "
  40133. add_conditions!(sql, conditions, scope(:find))
  40134. connection.delete(sql, "#{name} Delete all")
  40135. end
  40136. # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
  40137. # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
  40138. def count_by_sql(sql)
  40139. sql = sanitize_conditions(sql)
  40140. connection.select_value(sql, "#{name} Count").to_i
  40141. end
  40142. # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
  40143. # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
  40144. # This is used for caching aggregate values, so that they don't need to be computed every time. Especially important
  40145. # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
  40146. # that needs to list both the number of posts and comments.
  40147. def increment_counter(counter_name, id)
  40148. update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote(id)}"
  40149. end
  40150. # Works like increment_counter, but decrements instead.
  40151. def decrement_counter(counter_name, id)
  40152. update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote(id)}"
  40153. end
  40154. # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
  40155. # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
  40156. # methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
  40157. #
  40158. # class Customer < ActiveRecord::Base
  40159. # attr_protected :credit_rating
  40160. # end
  40161. #
  40162. # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
  40163. # customer.credit_rating # => nil
  40164. # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
  40165. # customer.credit_rating # => nil
  40166. #
  40167. # customer.credit_rating = "Average"
  40168. # customer.credit_rating # => "Average"
  40169. def attr_protected(*attributes)
  40170. write_inheritable_array("attr_protected", attributes - (protected_attributes || []))
  40171. end
  40172. # Returns an array of all the attributes that have been protected from mass-assignment.
  40173. def protected_attributes # :nodoc:
  40174. read_inheritable_attribute("attr_protected")
  40175. end
  40176. # If this macro is used, only those attributes named in it will be accessible for mass-assignment, such as
  40177. # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
  40178. # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
  40179. # attr_protected.
  40180. def attr_accessible(*attributes)
  40181. write_inheritable_array("attr_accessible", attributes - (accessible_attributes || []))
  40182. end
  40183. # Returns an array of all the attributes that have been made accessible to mass-assignment.
  40184. def accessible_attributes # :nodoc:
  40185. read_inheritable_attribute("attr_accessible")
  40186. end
  40187. # Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
  40188. # after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
  40189. # object must be of that class on retrieval or +SerializationTypeMismatch+ will be raised.
  40190. def serialize(attr_name, class_name = Object)
  40191. serialized_attributes[attr_name.to_s] = class_name
  40192. end
  40193. # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
  40194. def serialized_attributes
  40195. read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
  40196. end
  40197. # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
  40198. # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
  40199. # to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
  40200. # in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
  40201. #
  40202. # Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
  40203. # So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
  40204. #
  40205. # You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
  40206. # "mice" table. Example:
  40207. #
  40208. # class Mouse < ActiveRecord::Base
  40209. # set_table_name "mice"
  40210. # end
  40211. def table_name
  40212. reset_table_name
  40213. end
  40214. def reset_table_name #:nodoc:
  40215. name = "#{table_name_prefix}#{undecorated_table_name(base_class.name)}#{table_name_suffix}"
  40216. set_table_name(name)
  40217. name
  40218. end
  40219. # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
  40220. # primary_key_prefix_type setting, though.
  40221. def primary_key
  40222. reset_primary_key
  40223. end
  40224. def reset_primary_key #:nodoc:
  40225. key = 'id'
  40226. case primary_key_prefix_type
  40227. when :table_name
  40228. key = Inflector.foreign_key(base_class.name, false)
  40229. when :table_name_with_underscore
  40230. key = Inflector.foreign_key(base_class.name)
  40231. end
  40232. set_primary_key(key)
  40233. key
  40234. end
  40235. # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
  40236. def inheritance_column
  40237. "type"
  40238. end
  40239. # Lazy-set the sequence name to the connection's default. This method
  40240. # is only ever called once since set_sequence_name overrides it.
  40241. def sequence_name #:nodoc:
  40242. reset_sequence_name
  40243. end
  40244. def reset_sequence_name #:nodoc:
  40245. default = connection.default_sequence_name(table_name, primary_key)
  40246. set_sequence_name(default)
  40247. default
  40248. end
  40249. # Sets the table name to use to the given value, or (if the value
  40250. # is nil or false) to the value returned by the given block.
  40251. #
  40252. # Example:
  40253. #
  40254. # class Project < ActiveRecord::Base
  40255. # set_table_name "project"
  40256. # end
  40257. def set_table_name(value = nil, &block)
  40258. define_attr_method :table_name, value, &block
  40259. end
  40260. alias :table_name= :set_table_name
  40261. # Sets the name of the primary key column to use to the given value,
  40262. # or (if the value is nil or false) to the value returned by the given
  40263. # block.
  40264. #
  40265. # Example:
  40266. #
  40267. # class Project < ActiveRecord::Base
  40268. # set_primary_key "sysid"
  40269. # end
  40270. def set_primary_key(value = nil, &block)
  40271. define_attr_method :primary_key, value, &block
  40272. end
  40273. alias :primary_key= :set_primary_key
  40274. # Sets the name of the inheritance column to use to the given value,
  40275. # or (if the value # is nil or false) to the value returned by the
  40276. # given block.
  40277. #
  40278. # Example:
  40279. #
  40280. # class Project < ActiveRecord::Base
  40281. # set_inheritance_column do
  40282. # original_inheritance_column + "_id"
  40283. # end
  40284. # end
  40285. def set_inheritance_column(value = nil, &block)
  40286. define_attr_method :inheritance_column, value, &block
  40287. end
  40288. alias :inheritance_column= :set_inheritance_column
  40289. # Sets the name of the sequence to use when generating ids to the given
  40290. # value, or (if the value is nil or false) to the value returned by the
  40291. # given block. This is required for Oracle and is useful for any
  40292. # database which relies on sequences for primary key generation.
  40293. #
  40294. # If a sequence name is not explicitly set when using Oracle or Firebird,
  40295. # it will default to the commonly used pattern of: #{table_name}_seq
  40296. #
  40297. # If a sequence name is not explicitly set when using PostgreSQL, it
  40298. # will discover the sequence corresponding to your primary key for you.
  40299. #
  40300. # Example:
  40301. #
  40302. # class Project < ActiveRecord::Base
  40303. # set_sequence_name "projectseq" # default would have been "project_seq"
  40304. # end
  40305. def set_sequence_name(value = nil, &block)
  40306. define_attr_method :sequence_name, value, &block
  40307. end
  40308. alias :sequence_name= :set_sequence_name
  40309. # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
  40310. def class_name(table_name = table_name) # :nodoc:
  40311. # remove any prefix and/or suffix from the table name
  40312. class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
  40313. class_name = class_name.singularize if pluralize_table_names
  40314. class_name
  40315. end
  40316. # Indicates whether the table associated with this class exists
  40317. def table_exists?
  40318. if connection.respond_to?(:tables)
  40319. connection.tables.include? table_name
  40320. else
  40321. # if the connection adapter hasn't implemented tables, there are two crude tests that can be
  40322. # used - see if getting column info raises an error, or if the number of columns returned is zero
  40323. begin
  40324. reset_column_information
  40325. columns.size > 0
  40326. rescue ActiveRecord::StatementInvalid
  40327. false
  40328. end
  40329. end
  40330. end
  40331. # Returns an array of column objects for the table associated with this class.
  40332. def columns
  40333. unless @columns
  40334. @columns = connection.columns(table_name, "#{name} Columns")
  40335. @columns.each {|column| column.primary = column.name == primary_key}
  40336. end
  40337. @columns
  40338. end
  40339. # Returns an array of column objects for the table associated with this class.
  40340. def columns_hash
  40341. @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
  40342. end
  40343. # Returns an array of column names as strings.
  40344. def column_names
  40345. @column_names ||= columns.map { |column| column.name }
  40346. end
  40347. # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
  40348. # and columns used for single table inheritance have been removed.
  40349. def content_columns
  40350. @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
  40351. end
  40352. # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
  40353. # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
  40354. # is available.
  40355. def column_methods_hash #:nodoc:
  40356. @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
  40357. attr_name = attr.to_s
  40358. methods[attr.to_sym] = attr_name
  40359. methods["#{attr}=".to_sym] = attr_name
  40360. methods["#{attr}?".to_sym] = attr_name
  40361. methods["#{attr}_before_type_cast".to_sym] = attr_name
  40362. methods
  40363. end
  40364. end
  40365. # Contains the names of the generated reader methods.
  40366. def read_methods #:nodoc:
  40367. @read_methods ||= Set.new
  40368. end
  40369. # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
  40370. def reset_column_information
  40371. read_methods.each { |name| undef_method(name) }
  40372. @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
  40373. end
  40374. def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
  40375. subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
  40376. end
  40377. # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
  40378. # Person.human_attribute_name("first_name") # => "First name"
  40379. # Deprecated in favor of just calling "first_name".humanize
  40380. def human_attribute_name(attribute_key_name) #:nodoc:
  40381. attribute_key_name.humanize
  40382. end
  40383. def descends_from_active_record? # :nodoc:
  40384. superclass == Base || !columns_hash.include?(inheritance_column)
  40385. end
  40386. def quote(object) #:nodoc:
  40387. connection.quote(object)
  40388. end
  40389. # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
  40390. def sanitize(object) #:nodoc:
  40391. connection.quote(object)
  40392. end
  40393. # Log and benchmark multiple statements in a single block. Example:
  40394. #
  40395. # Project.benchmark("Creating project") do
  40396. # project = Project.create("name" => "stuff")
  40397. # project.create_manager("name" => "David")
  40398. # project.milestones << Milestone.find(:all)
  40399. # end
  40400. #
  40401. # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
  40402. # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
  40403. # will only be conducted if the log level is low enough.
  40404. #
  40405. # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
  40406. def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
  40407. if logger && logger.level == log_level
  40408. result = nil
  40409. seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
  40410. logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
  40411. result
  40412. else
  40413. yield
  40414. end
  40415. end
  40416. # Silences the logger for the duration of the block.
  40417. def silence
  40418. old_logger_level, logger.level = logger.level, Logger::ERROR if logger
  40419. yield
  40420. ensure
  40421. logger.level = old_logger_level if logger
  40422. end
  40423. # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
  40424. # method_name may be :find or :create. :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
  40425. # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash.
  40426. #
  40427. # Article.with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
  40428. # Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
  40429. # a = Article.create(1)
  40430. # a.blog_id # => 1
  40431. # end
  40432. #
  40433. # In nested scopings, all previous parameters are overwritten by inner rule
  40434. # except :conditions in :find, that are merged as hash.
  40435. #
  40436. # Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
  40437. # Article.with_scope(:find => { :limit => 10})
  40438. # Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
  40439. # end
  40440. # Article.with_scope(:find => { :conditions => "author_id = 3" })
  40441. # Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
  40442. # end
  40443. # end
  40444. #
  40445. # You can ignore any previous scopings by using <tt>with_exclusive_scope</tt> method.
  40446. #
  40447. # Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
  40448. # Article.with_exclusive_scope(:find => { :limit => 10 })
  40449. # Article.find(:all) # => SELECT * from articles LIMIT 10
  40450. # end
  40451. # end
  40452. def with_scope(method_scoping = {}, action = :merge, &block)
  40453. method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
  40454. # Dup first and second level of hash (method and params).
  40455. method_scoping = method_scoping.inject({}) do |hash, (method, params)|
  40456. hash[method] = (params == true) ? params : params.dup
  40457. hash
  40458. end
  40459. method_scoping.assert_valid_keys([ :find, :create ])
  40460. if f = method_scoping[:find]
  40461. f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :readonly ])
  40462. f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
  40463. end
  40464. # Merge scopings
  40465. if action == :merge && current_scoped_methods
  40466. method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
  40467. case hash[method]
  40468. when Hash
  40469. if method == :find
  40470. (hash[method].keys + params.keys).uniq.each do |key|
  40471. merge = hash[method][key] && params[key] # merge if both scopes have the same key
  40472. if key == :conditions && merge
  40473. hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
  40474. elsif key == :include && merge
  40475. hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
  40476. else
  40477. hash[method][key] = hash[method][key] || params[key]
  40478. end
  40479. end
  40480. else
  40481. hash[method] = params.merge(hash[method])
  40482. end
  40483. else
  40484. hash[method] = params
  40485. end
  40486. hash
  40487. end
  40488. end
  40489. self.scoped_methods << method_scoping
  40490. begin
  40491. yield
  40492. ensure
  40493. self.scoped_methods.pop
  40494. end
  40495. end
  40496. # Works like with_scope, but discards any nested properties.
  40497. def with_exclusive_scope(method_scoping = {}, &block)
  40498. with_scope(method_scoping, :overwrite, &block)
  40499. end
  40500. # Overwrite the default class equality method to provide support for association proxies.
  40501. def ===(object)
  40502. object.is_a?(self)
  40503. end
  40504. # Deprecated
  40505. def threaded_connections #:nodoc:
  40506. allow_concurrency
  40507. end
  40508. # Deprecated
  40509. def threaded_connections=(value) #:nodoc:
  40510. self.allow_concurrency = value
  40511. end
  40512. # Returns the base AR subclass that this class descends from. If A
  40513. # extends AR::Base, A.base_class will return A. If B descends from A
  40514. # through some arbitrarily deep hierarchy, B.base_class will return A.
  40515. def base_class
  40516. class_of_active_record_descendant(self)
  40517. end
  40518. # Set this to true if this is an abstract class (see #abstract_class?).
  40519. attr_accessor :abstract_class
  40520. # Returns whether this class is a base AR class. If A is a base class and
  40521. # B descends from A, then B.base_class will return B.
  40522. def abstract_class?
  40523. abstract_class == true
  40524. end
  40525. private
  40526. def find_initial(options)
  40527. options.update(:limit => 1) unless options[:include]
  40528. find_every(options).first
  40529. end
  40530. def find_every(options)
  40531. records = scoped?(:find, :include) || options[:include] ?
  40532. find_with_associations(options) :
  40533. find_by_sql(construct_finder_sql(options))
  40534. records.each { |record| record.readonly! } if options[:readonly]
  40535. records
  40536. end
  40537. def find_from_ids(ids, options)
  40538. expects_array = ids.first.kind_of?(Array)
  40539. return ids.first if expects_array && ids.first.empty?
  40540. ids = ids.flatten.compact.uniq
  40541. case ids.size
  40542. when 0
  40543. raise RecordNotFound, "Couldn't find #{name} without an ID"
  40544. when 1
  40545. result = find_one(ids.first, options)
  40546. expects_array ? [ result ] : result
  40547. else
  40548. find_some(ids, options)
  40549. end
  40550. end
  40551. def find_one(id, options)
  40552. conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
  40553. options.update :conditions => "#{table_name}.#{primary_key} = #{sanitize(id)}#{conditions}"
  40554. if result = find_initial(options)
  40555. result
  40556. else
  40557. raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
  40558. end
  40559. end
  40560. def find_some(ids, options)
  40561. conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
  40562. ids_list = ids.map { |id| sanitize(id) }.join(',')
  40563. options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"
  40564. result = find_every(options)
  40565. if result.size == ids.size
  40566. result
  40567. else
  40568. raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
  40569. end
  40570. end
  40571. # Finder methods must instantiate through this method to work with the single-table inheritance model
  40572. # that makes it possible to create objects of different types from the same table.
  40573. def instantiate(record)
  40574. object =
  40575. if subclass_name = record[inheritance_column]
  40576. if subclass_name.empty?
  40577. allocate
  40578. else
  40579. require_association_class(subclass_name)
  40580. begin
  40581. compute_type(subclass_name).allocate
  40582. rescue NameError
  40583. raise SubclassNotFound,
  40584. "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
  40585. "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
  40586. "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
  40587. "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
  40588. end
  40589. end
  40590. else
  40591. allocate
  40592. end
  40593. object.instance_variable_set("@attributes", record)
  40594. object
  40595. end
  40596. # Nest the type name in the same module as this class.
  40597. # Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
  40598. def type_name_with_module(type_name)
  40599. (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
  40600. end
  40601. def construct_finder_sql(options)
  40602. scope = scope(:find)
  40603. sql = "SELECT #{(scope && scope[:select]) || options[:select] || '*'} "
  40604. sql << "FROM #{(scope && scope[:from]) || options[:from] || table_name} "
  40605. add_joins!(sql, options, scope)
  40606. add_conditions!(sql, options[:conditions], scope)
  40607. sql << " GROUP BY #{options[:group]} " if options[:group]
  40608. sql << " ORDER BY #{options[:order]} " if options[:order]
  40609. add_limit!(sql, options, scope)
  40610. sql
  40611. end
  40612. # Merges includes so that the result is a valid +include+
  40613. def merge_includes(first, second)
  40614. safe_to_array(first) + safe_to_array(second)
  40615. end
  40616. # Object#to_a is deprecated, though it does have the desired behaviour
  40617. def safe_to_array(o)
  40618. case o
  40619. when NilClass
  40620. []
  40621. when Array
  40622. o
  40623. else
  40624. [o]
  40625. end
  40626. end
  40627. # The optional scope argument is for the current :find scope.
  40628. def add_limit!(sql, options, scope = :auto)
  40629. scope = scope(:find) if :auto == scope
  40630. if scope
  40631. options[:limit] ||= scope[:limit]
  40632. options[:offset] ||= scope[:offset]
  40633. end
  40634. connection.add_limit_offset!(sql, options)
  40635. end
  40636. # The optional scope argument is for the current :find scope.
  40637. def add_joins!(sql, options, scope = :auto)
  40638. scope = scope(:find) if :auto == scope
  40639. join = (scope && scope[:joins]) || options[:joins]
  40640. sql << " #{join} " if join
  40641. end
  40642. # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
  40643. # The optional scope argument is for the current :find scope.
  40644. def add_conditions!(sql, conditions, scope = :auto)
  40645. scope = scope(:find) if :auto == scope
  40646. segments = []
  40647. segments << sanitize_sql(scope[:conditions]) if scope && scope[:conditions]
  40648. segments << sanitize_sql(conditions) unless conditions.nil?
  40649. segments << type_condition unless descends_from_active_record?
  40650. segments.compact!
  40651. sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
  40652. end
  40653. def type_condition
  40654. quoted_inheritance_column = connection.quote_column_name(inheritance_column)
  40655. type_condition = subclasses.inject("#{table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
  40656. condition << "OR #{table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
  40657. end
  40658. " (#{type_condition}) "
  40659. end
  40660. # Guesses the table name, but does not decorate it with prefix and suffix information.
  40661. def undecorated_table_name(class_name = base_class.name)
  40662. table_name = Inflector.underscore(Inflector.demodulize(class_name))
  40663. table_name = Inflector.pluralize(table_name) if pluralize_table_names
  40664. table_name
  40665. end
  40666. # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
  40667. # find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
  40668. # respectively. Also works for find(:all), but using find_all_by_amount(50) that are turned into find(:all, :conditions => ["amount = ?", 50]).
  40669. #
  40670. # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
  40671. # is actually find_all_by_amount(amount, options).
  40672. def method_missing(method_id, *arguments)
  40673. if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
  40674. finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)
  40675. attribute_names = extract_attribute_names_from_match(match)
  40676. super unless all_attributes_exists?(attribute_names)
  40677. conditions = construct_conditions_from_arguments(attribute_names, arguments)
  40678. case extra_options = arguments[attribute_names.size]
  40679. when nil
  40680. options = { :conditions => conditions }
  40681. set_readonly_option!(options)
  40682. send(finder, options)
  40683. when Hash
  40684. finder_options = extra_options.merge(:conditions => conditions)
  40685. validate_find_options(finder_options)
  40686. set_readonly_option!(finder_options)
  40687. if extra_options[:conditions]
  40688. with_scope(:find => { :conditions => extra_options[:conditions] }) do
  40689. send(finder, finder_options)
  40690. end
  40691. else
  40692. send(finder, finder_options)
  40693. end
  40694. else
  40695. send(deprecated_finder, conditions, *arguments[attribute_names.length..-1]) # deprecated API
  40696. end
  40697. elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
  40698. attribute_names = extract_attribute_names_from_match(match)
  40699. super unless all_attributes_exists?(attribute_names)
  40700. options = { :conditions => construct_conditions_from_arguments(attribute_names, arguments) }
  40701. set_readonly_option!(options)
  40702. find_initial(options) || create(construct_attributes_from_arguments(attribute_names, arguments))
  40703. else
  40704. super
  40705. end
  40706. end
  40707. def determine_finder(match)
  40708. match.captures.first == 'all_by' ? :find_every : :find_initial
  40709. end
  40710. def determine_deprecated_finder(match)
  40711. match.captures.first == 'all_by' ? :find_all : :find_first
  40712. end
  40713. def extract_attribute_names_from_match(match)
  40714. match.captures.last.split('_and_')
  40715. end
  40716. def construct_conditions_from_arguments(attribute_names, arguments)
  40717. conditions = []
  40718. attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} " }
  40719. [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
  40720. end
  40721. def construct_attributes_from_arguments(attribute_names, arguments)
  40722. attributes = {}
  40723. attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
  40724. attributes
  40725. end
  40726. def all_attributes_exists?(attribute_names)
  40727. attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
  40728. end
  40729. def attribute_condition(argument)
  40730. case argument
  40731. when nil then "IS ?"
  40732. when Array then "IN (?)"
  40733. else "= ?"
  40734. end
  40735. end
  40736. # Defines an "attribute" method (like #inheritance_column or
  40737. # #table_name). A new (class) method will be created with the
  40738. # given name. If a value is specified, the new method will
  40739. # return that value (as a string). Otherwise, the given block
  40740. # will be used to compute the value of the method.
  40741. #
  40742. # The original method will be aliased, with the new name being
  40743. # prefixed with "original_". This allows the new method to
  40744. # access the original value.
  40745. #
  40746. # Example:
  40747. #
  40748. # class A < ActiveRecord::Base
  40749. # define_attr_method :primary_key, "sysid"
  40750. # define_attr_method( :inheritance_column ) do
  40751. # original_inheritance_column + "_id"
  40752. # end
  40753. # end
  40754. def define_attr_method(name, value=nil, &block)
  40755. sing = class << self; self; end
  40756. sing.send :alias_method, "original_#{name}", name
  40757. if block_given?
  40758. sing.send :define_method, name, &block
  40759. else
  40760. # use eval instead of a block to work around a memory leak in dev
  40761. # mode in fcgi
  40762. sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
  40763. end
  40764. end
  40765. protected
  40766. def subclasses #:nodoc:
  40767. @@subclasses[self] ||= []
  40768. @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
  40769. end
  40770. # Test whether the given method and optional key are scoped.
  40771. def scoped?(method, key = nil) #:nodoc:
  40772. if current_scoped_methods && (scope = current_scoped_methods[method])
  40773. !key || scope.has_key?(key)
  40774. end
  40775. end
  40776. # Retrieve the scope for the given method and optional key.
  40777. def scope(method, key = nil) #:nodoc:
  40778. if current_scoped_methods && (scope = current_scoped_methods[method])
  40779. key ? scope[key] : scope
  40780. end
  40781. end
  40782. def thread_safe_scoped_methods #:nodoc:
  40783. scoped_methods = (Thread.current[:scoped_methods] ||= {})
  40784. scoped_methods[self] ||= []
  40785. end
  40786. def single_threaded_scoped_methods #:nodoc:
  40787. @scoped_methods ||= []
  40788. end
  40789. # pick up the correct scoped_methods version from @@allow_concurrency
  40790. if @@allow_concurrency
  40791. alias_method :scoped_methods, :thread_safe_scoped_methods
  40792. else
  40793. alias_method :scoped_methods, :single_threaded_scoped_methods
  40794. end
  40795. def current_scoped_methods #:nodoc:
  40796. scoped_methods.last
  40797. end
  40798. # Returns the class type of the record using the current module as a prefix. So descendents of
  40799. # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
  40800. def compute_type(type_name)
  40801. modularized_name = type_name_with_module(type_name)
  40802. begin
  40803. instance_eval(modularized_name)
  40804. rescue NameError => e
  40805. instance_eval(type_name)
  40806. end
  40807. end
  40808. # Returns the class descending directly from ActiveRecord in the inheritance hierarchy.
  40809. def class_of_active_record_descendant(klass)
  40810. if klass.superclass == Base || klass.superclass.abstract_class?
  40811. klass
  40812. elsif klass.superclass.nil?
  40813. raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
  40814. else
  40815. class_of_active_record_descendant(klass.superclass)
  40816. end
  40817. end
  40818. # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
  40819. def class_name_of_active_record_descendant(klass) #:nodoc:
  40820. klass.base_class.name
  40821. end
  40822. # Accepts an array or string. The string is returned untouched, but the array has each value
  40823. # sanitized and interpolated into the sql statement.
  40824. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
  40825. def sanitize_sql(ary)
  40826. return ary unless ary.is_a?(Array)
  40827. statement, *values = ary
  40828. if values.first.is_a?(Hash) and statement =~ /:\w+/
  40829. replace_named_bind_variables(statement, values.first)
  40830. elsif statement.include?('?')
  40831. replace_bind_variables(statement, values)
  40832. else
  40833. statement % values.collect { |value| connection.quote_string(value.to_s) }
  40834. end
  40835. end
  40836. alias_method :sanitize_conditions, :sanitize_sql
  40837. def replace_bind_variables(statement, values) #:nodoc:
  40838. raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
  40839. bound = values.dup
  40840. statement.gsub('?') { quote_bound_value(bound.shift) }
  40841. end
  40842. def replace_named_bind_variables(statement, bind_vars) #:nodoc:
  40843. statement.gsub(/:(\w+)/) do
  40844. match = $1.to_sym
  40845. if bind_vars.include?(match)
  40846. quote_bound_value(bind_vars[match])
  40847. else
  40848. raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
  40849. end
  40850. end
  40851. end
  40852. def quote_bound_value(value) #:nodoc:
  40853. if (value.respond_to?(:map) && !value.is_a?(String))
  40854. value.map { |v| connection.quote(v) }.join(',')
  40855. else
  40856. connection.quote(value)
  40857. end
  40858. end
  40859. def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
  40860. unless expected == provided
  40861. raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
  40862. end
  40863. end
  40864. def extract_options_from_args!(args) #:nodoc:
  40865. args.last.is_a?(Hash) ? args.pop : {}
  40866. end
  40867. VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
  40868. :order, :select, :readonly, :group, :from ]
  40869. def validate_find_options(options) #:nodoc:
  40870. options.assert_valid_keys(VALID_FIND_OPTIONS)
  40871. end
  40872. def set_readonly_option!(options) #:nodoc:
  40873. # Inherit :readonly from finder scope if set. Otherwise,
  40874. # if :joins is not blank then :readonly defaults to true.
  40875. unless options.has_key?(:readonly)
  40876. if scoped?(:find, :readonly)
  40877. options[:readonly] = scope(:find, :readonly)
  40878. elsif !options[:joins].blank? && !options[:select]
  40879. options[:readonly] = true
  40880. end
  40881. end
  40882. end
  40883. def encode_quoted_value(value) #:nodoc:
  40884. quoted_value = connection.quote(value)
  40885. quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
  40886. quoted_value
  40887. end
  40888. end
  40889. public
  40890. # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
  40891. # attributes but not yet saved (pass a hash with key names matching the associated table column names).
  40892. # In both instances, valid attribute keys are determined by the column names of the associated table --
  40893. # hence you can't have attributes that aren't part of the table columns.
  40894. def initialize(attributes = nil)
  40895. @attributes = attributes_from_column_definition
  40896. @new_record = true
  40897. ensure_proper_type
  40898. self.attributes = attributes unless attributes.nil?
  40899. yield self if block_given?
  40900. end
  40901. # A model instance's primary key is always available as model.id
  40902. # whether you name it the default 'id' or set it to something else.
  40903. def id
  40904. attr_name = self.class.primary_key
  40905. column = column_for_attribute(attr_name)
  40906. define_read_method(:id, attr_name, column) if self.class.generate_read_methods
  40907. read_attribute(attr_name)
  40908. end
  40909. # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
  40910. alias_method :to_param, :id
  40911. def id_before_type_cast #:nodoc:
  40912. read_attribute_before_type_cast(self.class.primary_key)
  40913. end
  40914. def quoted_id #:nodoc:
  40915. quote(id, column_for_attribute(self.class.primary_key))
  40916. end
  40917. # Sets the primary ID.
  40918. def id=(value)
  40919. write_attribute(self.class.primary_key, value)
  40920. end
  40921. # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
  40922. def new_record?
  40923. @new_record
  40924. end
  40925. # * No record exists: Creates a new record with values matching those of the object attributes.
  40926. # * A record does exist: Updates the record with values matching those of the object attributes.
  40927. def save
  40928. raise ReadOnlyRecord if readonly?
  40929. create_or_update
  40930. end
  40931. # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
  40932. # RecordNotSaved exception
  40933. def save!
  40934. save || raise(RecordNotSaved)
  40935. end
  40936. # Deletes the record in the database and freezes this instance to reflect that no changes should
  40937. # be made (since they can't be persisted).
  40938. def destroy
  40939. unless new_record?
  40940. connection.delete <<-end_sql, "#{self.class.name} Destroy"
  40941. DELETE FROM #{self.class.table_name}
  40942. WHERE #{self.class.primary_key} = #{quoted_id}
  40943. end_sql
  40944. end
  40945. freeze
  40946. end
  40947. # Returns a clone of the record that hasn't been assigned an id yet and
  40948. # is treated as a new record. Note that this is a "shallow" clone:
  40949. # it copies the object's attributes only, not its associations.
  40950. # The extent of a "deep" clone is application-specific and is therefore
  40951. # left to the application to implement according to its need.
  40952. def clone
  40953. attrs = self.attributes_before_type_cast
  40954. attrs.delete(self.class.primary_key)
  40955. self.class.new do |record|
  40956. record.send :instance_variable_set, '@attributes', attrs
  40957. end
  40958. end
  40959. # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
  40960. # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
  40961. # doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
  40962. def update_attribute(name, value)
  40963. send(name.to_s + '=', value)
  40964. save
  40965. end
  40966. # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
  40967. # fail and false will be returned.
  40968. def update_attributes(attributes)
  40969. self.attributes = attributes
  40970. save
  40971. end
  40972. # Initializes the +attribute+ to zero if nil and adds one. Only makes sense for number-based attributes. Returns self.
  40973. def increment(attribute)
  40974. self[attribute] ||= 0
  40975. self[attribute] += 1
  40976. self
  40977. end
  40978. # Increments the +attribute+ and saves the record.
  40979. def increment!(attribute)
  40980. increment(attribute).update_attribute(attribute, self[attribute])
  40981. end
  40982. # Initializes the +attribute+ to zero if nil and subtracts one. Only makes sense for number-based attributes. Returns self.
  40983. def decrement(attribute)
  40984. self[attribute] ||= 0
  40985. self[attribute] -= 1
  40986. self
  40987. end
  40988. # Decrements the +attribute+ and saves the record.
  40989. def decrement!(attribute)
  40990. decrement(attribute).update_attribute(attribute, self[attribute])
  40991. end
  40992. # Turns an +attribute+ that's currently true into false and vice versa. Returns self.
  40993. def toggle(attribute)
  40994. self[attribute] = !send("#{attribute}?")
  40995. self
  40996. end
  40997. # Toggles the +attribute+ and saves the record.
  40998. def toggle!(attribute)
  40999. toggle(attribute).update_attribute(attribute, self[attribute])
  41000. end
  41001. # Reloads the attributes of this object from the database.
  41002. def reload
  41003. clear_aggregation_cache
  41004. clear_association_cache
  41005. @attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
  41006. self
  41007. end
  41008. # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
  41009. # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
  41010. # (Alias for the protected read_attribute method).
  41011. def [](attr_name)
  41012. read_attribute(attr_name)
  41013. end
  41014. # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
  41015. # (Alias for the protected write_attribute method).
  41016. def []=(attr_name, value)
  41017. write_attribute(attr_name, value)
  41018. end
  41019. # Allows you to set all the attributes at once by passing in a hash with keys
  41020. # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
  41021. # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
  41022. # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
  41023. # attributes not included in that won't be allowed to be mass-assigned.
  41024. def attributes=(new_attributes)
  41025. return if new_attributes.nil?
  41026. attributes = new_attributes.dup
  41027. attributes.stringify_keys!
  41028. multi_parameter_attributes = []
  41029. remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
  41030. k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
  41031. end
  41032. assign_multiparameter_attributes(multi_parameter_attributes)
  41033. end
  41034. # Returns a hash of all the attributes with their names as keys and clones of their objects as values.
  41035. def attributes(options = nil)
  41036. attributes = clone_attributes :read_attribute
  41037. if options.nil?
  41038. attributes
  41039. else
  41040. if except = options[:except]
  41041. except = Array(except).collect { |attribute| attribute.to_s }
  41042. except.each { |attribute_name| attributes.delete(attribute_name) }
  41043. attributes
  41044. elsif only = options[:only]
  41045. only = Array(only).collect { |attribute| attribute.to_s }
  41046. attributes.delete_if { |key, value| !only.include?(key) }
  41047. attributes
  41048. else
  41049. raise ArgumentError, "Options does not specify :except or :only (#{options.keys.inspect})"
  41050. end
  41051. end
  41052. end
  41053. # Returns a hash of cloned attributes before typecasting and deserialization.
  41054. def attributes_before_type_cast
  41055. clone_attributes :read_attribute_before_type_cast
  41056. end
  41057. # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
  41058. # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
  41059. def attribute_present?(attribute)
  41060. value = read_attribute(attribute)
  41061. !value.blank? or value == 0
  41062. end
  41063. # Returns true if the given attribute is in the attributes hash
  41064. def has_attribute?(attr_name)
  41065. @attributes.has_key?(attr_name.to_s)
  41066. end
  41067. # Returns an array of names for the attributes available on this object sorted alphabetically.
  41068. def attribute_names
  41069. @attributes.keys.sort
  41070. end
  41071. # Returns the column object for the named attribute.
  41072. def column_for_attribute(name)
  41073. self.class.columns_hash[name.to_s]
  41074. end
  41075. # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
  41076. def ==(comparison_object)
  41077. comparison_object.equal?(self) ||
  41078. (comparison_object.instance_of?(self.class) &&
  41079. comparison_object.id == id &&
  41080. !comparison_object.new_record?)
  41081. end
  41082. # Delegates to ==
  41083. def eql?(comparison_object)
  41084. self == (comparison_object)
  41085. end
  41086. # Delegates to id in order to allow two records of the same type and id to work with something like:
  41087. # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
  41088. def hash
  41089. id.hash
  41090. end
  41091. # For checking respond_to? without searching the attributes (which is faster).
  41092. alias_method :respond_to_without_attributes?, :respond_to?
  41093. # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
  41094. # person.respond_to?("name?") which will all return true.
  41095. def respond_to?(method, include_priv = false)
  41096. if @attributes.nil?
  41097. return super
  41098. elsif attr_name = self.class.column_methods_hash[method.to_sym]
  41099. return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
  41100. return false if self.class.read_methods.include?(attr_name)
  41101. elsif @attributes.include?(method_name = method.to_s)
  41102. return true
  41103. elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
  41104. return true if @attributes.include?(md.pre_match)
  41105. end
  41106. # super must be called at the end of the method, because the inherited respond_to?
  41107. # would return true for generated readers, even if the attribute wasn't present
  41108. super
  41109. end
  41110. # Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
  41111. def freeze
  41112. @attributes.freeze; self
  41113. end
  41114. def frozen?
  41115. @attributes.frozen?
  41116. end
  41117. # Records loaded through joins with piggy-back attributes will be marked as read only as they cannot be saved and return true to this query.
  41118. def readonly?
  41119. @readonly == true
  41120. end
  41121. def readonly! #:nodoc:
  41122. @readonly = true
  41123. end
  41124. # Builds an XML document to represent the model. Some configuration is
  41125. # availble through +options+, however more complicated cases should use
  41126. # Builder.
  41127. #
  41128. # By default the generated XML document will include the processing
  41129. # instruction and all object's attributes. For example:
  41130. #
  41131. # <?xml version="1.0" encoding="UTF-8"?>
  41132. # <topic>
  41133. # <title>The First Topic</title>
  41134. # <author-name>David</author-name>
  41135. # <id type="integer">1</id>
  41136. # <approved type="boolean">false</approved>
  41137. # <replies-count type="integer">0</replies-count>
  41138. # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
  41139. # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
  41140. # <content>Have a nice day</content>
  41141. # <author-email-address>david@loudthinking.com</author-email-address>
  41142. # <parent-id></parent-id>
  41143. # <last-read type="date">2004-04-15</last-read>
  41144. # </topic>
  41145. #
  41146. # This behaviour can be controlled with :only, :except, and :skip_instruct
  41147. # for instance:
  41148. #
  41149. # topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
  41150. #
  41151. # <topic>
  41152. # <title>The First Topic</title>
  41153. # <author-name>David</author-name>
  41154. # <approved type="boolean">false</approved>
  41155. # <content>Have a nice day</content>
  41156. # <author-email-address>david@loudthinking.com</author-email-address>
  41157. # <parent-id></parent-id>
  41158. # <last-read type="date">2004-04-15</last-read>
  41159. # </topic>
  41160. #
  41161. # To include first level associations use :include
  41162. #
  41163. # firm.to_xml :include => [ :account, :clients ]
  41164. #
  41165. # <?xml version="1.0" encoding="UTF-8"?>
  41166. # <firm>
  41167. # <id type="integer">1</id>
  41168. # <rating type="integer">1</rating>
  41169. # <name>37signals</name>
  41170. # <clients>
  41171. # <client>
  41172. # <rating type="integer">1</rating>
  41173. # <name>Summit</name>
  41174. # </client>
  41175. # <client>
  41176. # <rating type="integer">1</rating>
  41177. # <name>Microsoft</name>
  41178. # </client>
  41179. # </clients>
  41180. # <account>
  41181. # <id type="integer">1</id>
  41182. # <credit-limit type="integer">50</credit-limit>
  41183. # </account>
  41184. # </firm>
  41185. def to_xml(options = {})
  41186. options[:root] ||= self.class.to_s.underscore
  41187. options[:except] = Array(options[:except]) << self.class.inheritance_column unless options[:only] # skip type column
  41188. root_only_or_except = { :only => options[:only], :except => options[:except] }
  41189. attributes_for_xml = attributes(root_only_or_except)
  41190. if include_associations = options.delete(:include)
  41191. include_has_options = include_associations.is_a?(Hash)
  41192. for association in include_has_options ? include_associations.keys : Array(include_associations)
  41193. association_options = include_has_options ? include_associations[association] : root_only_or_except
  41194. case self.class.reflect_on_association(association).macro
  41195. when :has_many, :has_and_belongs_to_many
  41196. records = send(association).to_a
  41197. unless records.empty?
  41198. attributes_for_xml[association] = records.collect do |record|
  41199. record.attributes(association_options)
  41200. end
  41201. end
  41202. when :has_one, :belongs_to
  41203. if record = send(association)
  41204. attributes_for_xml[association] = record.attributes(association_options)
  41205. end
  41206. end
  41207. end
  41208. end
  41209. attributes_for_xml.to_xml(options)
  41210. end
  41211. private
  41212. def create_or_update
  41213. if new_record? then create else update end
  41214. end
  41215. # Updates the associated record with values matching those of the instance attributes.
  41216. def update
  41217. connection.update(
  41218. "UPDATE #{self.class.table_name} " +
  41219. "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
  41220. "WHERE #{self.class.primary_key} = #{quote(id)}",
  41221. "#{self.class.name} Update"
  41222. )
  41223. return true
  41224. end
  41225. # Creates a new record with values matching those of the instance attributes.
  41226. def create
  41227. if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
  41228. self.id = connection.next_sequence_value(self.class.sequence_name)
  41229. end
  41230. self.id = connection.insert(
  41231. "INSERT INTO #{self.class.table_name} " +
  41232. "(#{quoted_column_names.join(', ')}) " +
  41233. "VALUES(#{attributes_with_quotes.values.join(', ')})",
  41234. "#{self.class.name} Create",
  41235. self.class.primary_key, self.id, self.class.sequence_name
  41236. )
  41237. @new_record = false
  41238. return true
  41239. end
  41240. # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
  41241. # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
  41242. # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
  41243. # Message class in that example.
  41244. def ensure_proper_type
  41245. unless self.class.descends_from_active_record?
  41246. write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
  41247. end
  41248. end
  41249. # Allows access to the object attributes, which are held in the @attributes hash, as were
  41250. # they first-class methods. So a Person class with a name attribute can use Person#name and
  41251. # Person#name= and never directly use the attributes hash -- except for multiple assigns with
  41252. # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
  41253. # the completed attribute is not nil or 0.
  41254. #
  41255. # It's also possible to instantiate related objects, so a Client class belonging to the clients
  41256. # table with a master_id foreign key can instantiate master through Client#master.
  41257. def method_missing(method_id, *args, &block)
  41258. method_name = method_id.to_s
  41259. if @attributes.include?(method_name) or
  41260. (md = /\?$/.match(method_name) and
  41261. @attributes.include?(method_name = md.pre_match))
  41262. define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
  41263. md ? query_attribute(method_name) : read_attribute(method_name)
  41264. elsif self.class.primary_key.to_s == method_name
  41265. id
  41266. elsif md = /(=|_before_type_cast)$/.match(method_name)
  41267. attribute_name, method_type = md.pre_match, md.to_s
  41268. if @attributes.include?(attribute_name)
  41269. case method_type
  41270. when '='
  41271. write_attribute(attribute_name, args.first)
  41272. when '_before_type_cast'
  41273. read_attribute_before_type_cast(attribute_name)
  41274. end
  41275. else
  41276. super
  41277. end
  41278. else
  41279. super
  41280. end
  41281. end
  41282. # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
  41283. # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
  41284. def read_attribute(attr_name)
  41285. attr_name = attr_name.to_s
  41286. if !(value = @attributes[attr_name]).nil?
  41287. if column = column_for_attribute(attr_name)
  41288. if unserializable_attribute?(attr_name, column)
  41289. unserialize_attribute(attr_name)
  41290. else
  41291. column.type_cast(value)
  41292. end
  41293. else
  41294. value
  41295. end
  41296. else
  41297. nil
  41298. end
  41299. end
  41300. def read_attribute_before_type_cast(attr_name)
  41301. @attributes[attr_name]
  41302. end
  41303. # Called on first read access to any given column and generates reader
  41304. # methods for all columns in the columns_hash if
  41305. # ActiveRecord::Base.generate_read_methods is set to true.
  41306. def define_read_methods
  41307. self.class.columns_hash.each do |name, column|
  41308. unless self.class.serialized_attributes[name]
  41309. define_read_method(name.to_sym, name, column) unless respond_to_without_attributes?(name)
  41310. define_question_method(name) unless respond_to_without_attributes?("#{name}?")
  41311. end
  41312. end
  41313. end
  41314. # Define an attribute reader method. Cope with nil column.
  41315. def define_read_method(symbol, attr_name, column)
  41316. cast_code = column.type_cast_code('v') if column
  41317. access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
  41318. unless attr_name.to_s == self.class.primary_key.to_s
  41319. access_code = access_code.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
  41320. self.class.read_methods << attr_name
  41321. end
  41322. evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
  41323. end
  41324. # Define an attribute ? method.
  41325. def define_question_method(attr_name)
  41326. unless attr_name.to_s == self.class.primary_key.to_s
  41327. self.class.read_methods << "#{attr_name}?"
  41328. end
  41329. evaluate_read_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end"
  41330. end
  41331. # Evaluate the definition for an attribute reader or ? method
  41332. def evaluate_read_method(attr_name, method_definition)
  41333. begin
  41334. self.class.class_eval(method_definition)
  41335. rescue SyntaxError => err
  41336. self.class.read_methods.delete(attr_name)
  41337. if logger
  41338. logger.warn "Exception occured during reader method compilation."
  41339. logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
  41340. logger.warn "#{err.message}"
  41341. end
  41342. end
  41343. end
  41344. # Returns true if the attribute is of a text column and marked for serialization.
  41345. def unserializable_attribute?(attr_name, column)
  41346. column.text? && self.class.serialized_attributes[attr_name]
  41347. end
  41348. # Returns the unserialized object of the attribute.
  41349. def unserialize_attribute(attr_name)
  41350. unserialized_object = object_from_yaml(@attributes[attr_name])
  41351. if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
  41352. @attributes[attr_name] = unserialized_object
  41353. else
  41354. raise SerializationTypeMismatch,
  41355. "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
  41356. end
  41357. end
  41358. # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
  41359. # columns are turned into nil.
  41360. def write_attribute(attr_name, value)
  41361. attr_name = attr_name.to_s
  41362. if (column = column_for_attribute(attr_name)) && column.number?
  41363. @attributes[attr_name] = convert_number_column_value(value)
  41364. else
  41365. @attributes[attr_name] = value
  41366. end
  41367. end
  41368. def convert_number_column_value(value)
  41369. case value
  41370. when FalseClass: 0
  41371. when TrueClass: 1
  41372. when '': nil
  41373. else value
  41374. end
  41375. end
  41376. def query_attribute(attr_name)
  41377. attribute = @attributes[attr_name]
  41378. if attribute.kind_of?(Fixnum) && attribute == 0
  41379. false
  41380. elsif attribute.kind_of?(String) && attribute == "0"
  41381. false
  41382. elsif attribute.kind_of?(String) && attribute.empty?
  41383. false
  41384. elsif attribute.nil?
  41385. false
  41386. elsif attribute == false
  41387. false
  41388. elsif attribute == "f"
  41389. false
  41390. elsif attribute == "false"
  41391. false
  41392. else
  41393. true
  41394. end
  41395. end
  41396. def remove_attributes_protected_from_mass_assignment(attributes)
  41397. if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
  41398. attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
  41399. elsif self.class.protected_attributes.nil?
  41400. attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
  41401. elsif self.class.accessible_attributes.nil?
  41402. attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
  41403. end
  41404. end
  41405. # The primary key and inheritance column can never be set by mass-assignment for security reasons.
  41406. def attributes_protected_by_default
  41407. default = [ self.class.primary_key, self.class.inheritance_column ]
  41408. default << 'id' unless self.class.primary_key.eql? 'id'
  41409. default
  41410. end
  41411. # Returns copy of the attributes hash where all the values have been safely quoted for use in
  41412. # an SQL statement.
  41413. def attributes_with_quotes(include_primary_key = true)
  41414. attributes.inject({}) do |quoted, (name, value)|
  41415. if column = column_for_attribute(name)
  41416. quoted[name] = quote(value, column) unless !include_primary_key && column.primary
  41417. end
  41418. quoted
  41419. end
  41420. end
  41421. # Quote strings appropriately for SQL statements.
  41422. def quote(value, column = nil)
  41423. self.class.connection.quote(value, column)
  41424. end
  41425. # Interpolate custom sql string in instance context.
  41426. # Optional record argument is meant for custom insert_sql.
  41427. def interpolate_sql(sql, record = nil)
  41428. instance_eval("%@#{sql.gsub('@', '\@')}@")
  41429. end
  41430. # Initializes the attributes array with keys matching the columns from the linked table and
  41431. # the values matching the corresponding default value of that column, so
  41432. # that a new instance, or one populated from a passed-in Hash, still has all the attributes
  41433. # that instances loaded from the database would.
  41434. def attributes_from_column_definition
  41435. self.class.columns.inject({}) do |attributes, column|
  41436. attributes[column.name] = column.default unless column.name == self.class.primary_key
  41437. attributes
  41438. end
  41439. end
  41440. # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
  41441. # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
  41442. # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
  41443. # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
  41444. # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
  41445. # s for String, and a for Array. If all the values for a given attribute is empty, the attribute will be set to nil.
  41446. def assign_multiparameter_attributes(pairs)
  41447. execute_callstack_for_multiparameter_attributes(
  41448. extract_callstack_for_multiparameter_attributes(pairs)
  41449. )
  41450. end
  41451. # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
  41452. def execute_callstack_for_multiparameter_attributes(callstack)
  41453. errors = []
  41454. callstack.each do |name, values|
  41455. klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
  41456. if values.empty?
  41457. send(name + "=", nil)
  41458. else
  41459. begin
  41460. send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
  41461. rescue => ex
  41462. errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
  41463. end
  41464. end
  41465. end
  41466. unless errors.empty?
  41467. raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
  41468. end
  41469. end
  41470. def extract_callstack_for_multiparameter_attributes(pairs)
  41471. attributes = { }
  41472. for pair in pairs
  41473. multiparameter_name, value = pair
  41474. attribute_name = multiparameter_name.split("(").first
  41475. attributes[attribute_name] = [] unless attributes.include?(attribute_name)
  41476. unless value.empty?
  41477. attributes[attribute_name] <<
  41478. [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
  41479. end
  41480. end
  41481. attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
  41482. end
  41483. def type_cast_attribute_value(multiparameter_name, value)
  41484. multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
  41485. end
  41486. def find_parameter_position(multiparameter_name)
  41487. multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
  41488. end
  41489. # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
  41490. def comma_pair_list(hash)
  41491. hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
  41492. end
  41493. def quoted_column_names(attributes = attributes_with_quotes)
  41494. attributes.keys.collect do |column_name|
  41495. self.class.connection.quote_column_name(column_name)
  41496. end
  41497. end
  41498. def quote_columns(quoter, hash)
  41499. hash.inject({}) do |quoted, (name, value)|
  41500. quoted[quoter.quote_column_name(name)] = value
  41501. quoted
  41502. end
  41503. end
  41504. def quoted_comma_pair_list(quoter, hash)
  41505. comma_pair_list(quote_columns(quoter, hash))
  41506. end
  41507. def object_from_yaml(string)
  41508. return string unless string.is_a?(String)
  41509. YAML::load(string) rescue string
  41510. end
  41511. def clone_attributes(reader_method = :read_attribute, attributes = {})
  41512. self.attribute_names.inject(attributes) do |attributes, name|
  41513. attributes[name] = clone_attribute_value(reader_method, name)
  41514. attributes
  41515. end
  41516. end
  41517. def clone_attribute_value(reader_method, attribute_name)
  41518. value = send(reader_method, attribute_name)
  41519. value.clone
  41520. rescue TypeError, NoMethodError
  41521. value
  41522. end
  41523. end
  41524. end
  41525. module ActiveRecord
  41526. module Calculations #:nodoc:
  41527. CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset]
  41528. def self.included(base)
  41529. base.extend(ClassMethods)
  41530. end
  41531. module ClassMethods
  41532. # Count operates using three different approaches.
  41533. #
  41534. # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
  41535. # * Count by conditions or joins: For backwards compatibility, you can pass in +conditions+ and +joins+ as individual parameters.
  41536. # * Count using options will find the row count matched by the options used.
  41537. #
  41538. # The last approach, count using options, accepts an option hash as the only parameter. The options are:
  41539. #
  41540. # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
  41541. # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
  41542. # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
  41543. # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
  41544. # to already defined associations. When using named associations count returns the number DISTINCT items for the model you're counting.
  41545. # See eager loading under Associations.
  41546. # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
  41547. # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  41548. # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
  41549. # include the joined columns.
  41550. # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
  41551. #
  41552. # Examples for counting all:
  41553. # Person.count # returns the total count of all people
  41554. #
  41555. # Examples for count by +conditions+ and +joins+ (for backwards compatibility):
  41556. # Person.count("age > 26") # returns the number of people older than 26
  41557. # Person.find("age > 26 AND job.salary > 60000", "LEFT JOIN jobs on jobs.person_id = person.id") # returns the total number of rows matching the conditions and joins fetched by SELECT COUNT(*).
  41558. #
  41559. # Examples for count with options:
  41560. # Person.count(:conditions => "age > 26")
  41561. # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
  41562. # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
  41563. # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
  41564. # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
  41565. #
  41566. # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead.
  41567. def count(*args)
  41568. options = {}
  41569. column_name = :all
  41570. # For backwards compatibility, we need to handle both count(conditions=nil, joins=nil) or count(options={}) or count(column_name=:all, options={}).
  41571. if args.size >= 0 && args.size <= 2
  41572. if args.first.is_a?(Hash)
  41573. options = args.first
  41574. elsif args[1].is_a?(Hash)
  41575. options = args[1]
  41576. column_name = args.first
  41577. else
  41578. # Handle legacy paramter options: def count(conditions=nil, joins=nil)
  41579. options.merge!(:conditions => args[0]) if args.length > 0
  41580. options.merge!(:joins => args[1]) if args.length > 1
  41581. end
  41582. else
  41583. raise(ArgumentError, "Unexpected parameters passed to count(*args): expected either count(conditions=nil, joins=nil) or count(options={})")
  41584. end
  41585. if options[:include] || scope(:find, :include)
  41586. count_with_associations(options)
  41587. else
  41588. calculate(:count, column_name, options)
  41589. end
  41590. end
  41591. # Calculates average value on a given column. The value is returned as a float. See #calculate for examples with options.
  41592. #
  41593. # Person.average('age')
  41594. def average(column_name, options = {})
  41595. calculate(:avg, column_name, options)
  41596. end
  41597. # Calculates the minimum value on a given column. The value is returned with the same data type of the column.. See #calculate for examples with options.
  41598. #
  41599. # Person.minimum('age')
  41600. def minimum(column_name, options = {})
  41601. calculate(:min, column_name, options)
  41602. end
  41603. # Calculates the maximum value on a given column. The value is returned with the same data type of the column.. See #calculate for examples with options.
  41604. #
  41605. # Person.maximum('age')
  41606. def maximum(column_name, options = {})
  41607. calculate(:max, column_name, options)
  41608. end
  41609. # Calculates the sum value on a given column. The value is returned with the same data type of the column.. See #calculate for examples with options.
  41610. #
  41611. # Person.sum('age')
  41612. def sum(column_name, options = {})
  41613. calculate(:sum, column_name, options)
  41614. end
  41615. # This calculates aggregate values in the given column: Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
  41616. # Options such as :conditions, :order, :group, :having, and :joins can be passed to customize the query.
  41617. #
  41618. # There are two basic forms of output:
  41619. # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
  41620. # * Grouped values: This returns an ordered hash of the values and groups them by the :group option. It takes either a column name, or the name
  41621. # of a belongs_to association.
  41622. #
  41623. # values = Person.maximum(:age, :group => 'last_name')
  41624. # puts values["Drake"]
  41625. # => 43
  41626. #
  41627. # drake = Family.find_by_last_name('Drake')
  41628. # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
  41629. # puts values[drake]
  41630. # => 43
  41631. #
  41632. # values.each do |family, max_age|
  41633. # ...
  41634. # end
  41635. #
  41636. # Options:
  41637. # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
  41638. # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
  41639. # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
  41640. # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
  41641. # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  41642. # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
  41643. # include the joined columns.
  41644. # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
  41645. #
  41646. # Examples:
  41647. # Person.calculate(:count, :all) # The same as Person.count
  41648. # Person.average(:age) # SELECT AVG(age) FROM people...
  41649. # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
  41650. # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
  41651. def calculate(operation, column_name, options = {})
  41652. validate_calculation_options(operation, options)
  41653. column_name = options[:select] if options[:select]
  41654. column_name = '*' if column_name == :all
  41655. column = column_for column_name
  41656. aggregate = select_aggregate(operation, column_name, options)
  41657. aggregate_alias = column_alias_for(operation, column_name)
  41658. if options[:group]
  41659. execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
  41660. else
  41661. execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
  41662. end
  41663. end
  41664. protected
  41665. def construct_calculation_sql(aggregate, aggregate_alias, options) #:nodoc:
  41666. scope = scope(:find)
  41667. sql = "SELECT #{aggregate} AS #{aggregate_alias}"
  41668. sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
  41669. sql << " FROM #{table_name} "
  41670. add_joins!(sql, options, scope)
  41671. add_conditions!(sql, options[:conditions], scope)
  41672. sql << " GROUP BY #{options[:group_field]}" if options[:group]
  41673. sql << " HAVING #{options[:having]}" if options[:group] && options[:having]
  41674. sql << " ORDER BY #{options[:order]}" if options[:order]
  41675. add_limit!(sql, options)
  41676. sql
  41677. end
  41678. def execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
  41679. value = connection.select_value(construct_calculation_sql(aggregate, aggregate_alias, options))
  41680. type_cast_calculated_value(value, column, operation)
  41681. end
  41682. def execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
  41683. group_attr = options[:group].to_s
  41684. association = reflect_on_association(group_attr.to_sym)
  41685. associated = association && association.macro == :belongs_to # only count belongs_to associations
  41686. group_field = (associated ? "#{options[:group]}_id" : options[:group]).to_s
  41687. group_alias = column_alias_for(group_field)
  41688. group_column = column_for group_field
  41689. sql = construct_calculation_sql(aggregate, aggregate_alias, options.merge(:group_field => group_field, :group_alias => group_alias))
  41690. calculated_data = connection.select_all(sql)
  41691. if association
  41692. key_ids = calculated_data.collect { |row| row[group_alias] }
  41693. key_records = association.klass.base_class.find(key_ids)
  41694. key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
  41695. end
  41696. calculated_data.inject(OrderedHash.new) do |all, row|
  41697. key = associated ? key_records[row[group_alias].to_i] : type_cast_calculated_value(row[group_alias], group_column)
  41698. value = row[aggregate_alias]
  41699. all << [key, type_cast_calculated_value(value, column, operation)]
  41700. end
  41701. end
  41702. private
  41703. def validate_calculation_options(operation, options = {})
  41704. if operation.to_s == 'count'
  41705. options.assert_valid_keys(CALCULATIONS_OPTIONS + [:include])
  41706. else
  41707. options.assert_valid_keys(CALCULATIONS_OPTIONS)
  41708. end
  41709. end
  41710. def select_aggregate(operation, column_name, options)
  41711. "#{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})"
  41712. end
  41713. # converts a given key to the value that the database adapter returns as
  41714. #
  41715. # users.id #=> users_id
  41716. # sum(id) #=> sum_id
  41717. # count(distinct users.id) #=> count_distinct_users_id
  41718. # count(*) #=> count_all
  41719. def column_alias_for(*keys)
  41720. keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_')
  41721. end
  41722. def column_for(field)
  41723. field_name = field.to_s.split('.').last
  41724. columns.detect { |c| c.name.to_s == field_name }
  41725. end
  41726. def type_cast_calculated_value(value, column, operation = nil)
  41727. operation = operation.to_s.downcase
  41728. case operation
  41729. when 'count' then value.to_i
  41730. when 'avg' then value.to_f
  41731. else column ? column.type_cast(value) : value
  41732. end
  41733. end
  41734. end
  41735. end
  41736. end
  41737. require 'observer'
  41738. module ActiveRecord
  41739. # Callbacks are hooks into the lifecycle of an Active Record object that allows you to trigger logic
  41740. # before or after an alteration of the object state. This can be used to make sure that associated and
  41741. # dependent objects are deleted when destroy is called (by overwriting before_destroy) or to massage attributes
  41742. # before they're validated (by overwriting before_validation). As an example of the callbacks initiated, consider
  41743. # the Base#save call:
  41744. #
  41745. # * (-) save
  41746. # * (-) valid?
  41747. # * (1) before_validation
  41748. # * (2) before_validation_on_create
  41749. # * (-) validate
  41750. # * (-) validate_on_create
  41751. # * (3) after_validation
  41752. # * (4) after_validation_on_create
  41753. # * (5) before_save
  41754. # * (6) before_create
  41755. # * (-) create
  41756. # * (7) after_create
  41757. # * (8) after_save
  41758. #
  41759. # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
  41760. # Active Record lifecycle.
  41761. #
  41762. # Examples:
  41763. # class CreditCard < ActiveRecord::Base
  41764. # # Strip everything but digits, so the user can specify "555 234 34" or
  41765. # # "5552-3434" or both will mean "55523434"
  41766. # def before_validation_on_create
  41767. # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
  41768. # end
  41769. # end
  41770. #
  41771. # class Subscription < ActiveRecord::Base
  41772. # before_create :record_signup
  41773. #
  41774. # private
  41775. # def record_signup
  41776. # self.signed_up_on = Date.today
  41777. # end
  41778. # end
  41779. #
  41780. # class Firm < ActiveRecord::Base
  41781. # # Destroys the associated clients and people when the firm is destroyed
  41782. # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
  41783. # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
  41784. # end
  41785. #
  41786. # == Inheritable callback queues
  41787. #
  41788. # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
  41789. # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
  41790. # hierarchy. Example:
  41791. #
  41792. # class Topic < ActiveRecord::Base
  41793. # before_destroy :destroy_author
  41794. # end
  41795. #
  41796. # class Reply < Topic
  41797. # before_destroy :destroy_readers
  41798. # end
  41799. #
  41800. # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is run both +destroy_author+ and
  41801. # +destroy_readers+ is called. Contrast this to the situation where we've implemented the save behavior through overwriteable
  41802. # methods:
  41803. #
  41804. # class Topic < ActiveRecord::Base
  41805. # def before_destroy() destroy_author end
  41806. # end
  41807. #
  41808. # class Reply < Topic
  41809. # def before_destroy() destroy_readers end
  41810. # end
  41811. #
  41812. # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So use the callback macros when
  41813. # you want to ensure that a certain callback is called for the entire hierarchy and the regular overwriteable methods when you
  41814. # want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
  41815. #
  41816. # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
  41817. # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
  41818. # be inherited.
  41819. #
  41820. # == Types of callbacks
  41821. #
  41822. # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
  41823. # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
  41824. # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
  41825. # eval methods are deprecated.
  41826. #
  41827. # The method reference callbacks work by specifying a protected or private method available in the object, like this:
  41828. #
  41829. # class Topic < ActiveRecord::Base
  41830. # before_destroy :delete_parents
  41831. #
  41832. # private
  41833. # def delete_parents
  41834. # self.class.delete_all "parent_id = #{id}"
  41835. # end
  41836. # end
  41837. #
  41838. # The callback objects have methods named after the callback called with the record as the only parameter, such as:
  41839. #
  41840. # class BankAccount < ActiveRecord::Base
  41841. # before_save EncryptionWrapper.new("credit_card_number")
  41842. # after_save EncryptionWrapper.new("credit_card_number")
  41843. # after_initialize EncryptionWrapper.new("credit_card_number")
  41844. # end
  41845. #
  41846. # class EncryptionWrapper
  41847. # def initialize(attribute)
  41848. # @attribute = attribute
  41849. # end
  41850. #
  41851. # def before_save(record)
  41852. # record.credit_card_number = encrypt(record.credit_card_number)
  41853. # end
  41854. #
  41855. # def after_save(record)
  41856. # record.credit_card_number = decrypt(record.credit_card_number)
  41857. # end
  41858. #
  41859. # alias_method :after_find, :after_save
  41860. #
  41861. # private
  41862. # def encrypt(value)
  41863. # # Secrecy is committed
  41864. # end
  41865. #
  41866. # def decrypt(value)
  41867. # # Secrecy is unveiled
  41868. # end
  41869. # end
  41870. #
  41871. # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
  41872. # a method by the name of the callback messaged.
  41873. #
  41874. # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
  41875. # which will then be evaluated within the binding of the callback. Example:
  41876. #
  41877. # class Topic < ActiveRecord::Base
  41878. # before_destroy 'self.class.delete_all "parent_id = #{id}"'
  41879. # end
  41880. #
  41881. # Notice that single plings (') are used so the #{id} part isn't evaluated until the callback is triggered. Also note that these
  41882. # inline callbacks can be stacked just like the regular ones:
  41883. #
  41884. # class Topic < ActiveRecord::Base
  41885. # before_destroy 'self.class.delete_all "parent_id = #{id}"',
  41886. # 'puts "Evaluated after parents are destroyed"'
  41887. # end
  41888. #
  41889. # == The after_find and after_initialize exceptions
  41890. #
  41891. # Because after_find and after_initialize are called for each object found and instantiated by a finder, such as Base.find(:all), we've had
  41892. # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, after_find and
  41893. # after_initialize will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
  41894. # callback types will be called.
  41895. #
  41896. # == Cancelling callbacks
  41897. #
  41898. # If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns
  41899. # false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
  41900. # defined as methods on the model, which are called last.
  41901. module Callbacks
  41902. CALLBACKS = %w(
  41903. after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
  41904. after_validation before_validation_on_create after_validation_on_create before_validation_on_update
  41905. after_validation_on_update before_destroy after_destroy
  41906. )
  41907. def self.append_features(base) #:nodoc:
  41908. super
  41909. base.extend(ClassMethods)
  41910. base.class_eval do
  41911. class << self
  41912. include Observable
  41913. alias_method :instantiate_without_callbacks, :instantiate
  41914. alias_method :instantiate, :instantiate_with_callbacks
  41915. end
  41916. alias_method :initialize_without_callbacks, :initialize
  41917. alias_method :initialize, :initialize_with_callbacks
  41918. alias_method :create_or_update_without_callbacks, :create_or_update
  41919. alias_method :create_or_update, :create_or_update_with_callbacks
  41920. alias_method :valid_without_callbacks, :valid?
  41921. alias_method :valid?, :valid_with_callbacks
  41922. alias_method :create_without_callbacks, :create
  41923. alias_method :create, :create_with_callbacks
  41924. alias_method :update_without_callbacks, :update
  41925. alias_method :update, :update_with_callbacks
  41926. alias_method :destroy_without_callbacks, :destroy
  41927. alias_method :destroy, :destroy_with_callbacks
  41928. end
  41929. CALLBACKS.each do |method|
  41930. base.class_eval <<-"end_eval"
  41931. def self.#{method}(*callbacks, &block)
  41932. callbacks << block if block_given?
  41933. write_inheritable_array(#{method.to_sym.inspect}, callbacks)
  41934. end
  41935. end_eval
  41936. end
  41937. end
  41938. module ClassMethods #:nodoc:
  41939. def instantiate_with_callbacks(record)
  41940. object = instantiate_without_callbacks(record)
  41941. if object.respond_to_without_attributes?(:after_find)
  41942. object.send(:callback, :after_find)
  41943. end
  41944. if object.respond_to_without_attributes?(:after_initialize)
  41945. object.send(:callback, :after_initialize)
  41946. end
  41947. object
  41948. end
  41949. end
  41950. # Is called when the object was instantiated by one of the finders, like Base.find.
  41951. #def after_find() end
  41952. # Is called after the object has been instantiated by a call to Base.new.
  41953. #def after_initialize() end
  41954. def initialize_with_callbacks(attributes = nil) #:nodoc:
  41955. initialize_without_callbacks(attributes)
  41956. result = yield self if block_given?
  41957. callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
  41958. result
  41959. end
  41960. # Is called _before_ Base.save (regardless of whether it's a create or update save).
  41961. def before_save() end
  41962. # Is called _after_ Base.save (regardless of whether it's a create or update save).
  41963. #
  41964. # class Contact < ActiveRecord::Base
  41965. # after_save { logger.info( 'New contact saved!' ) }
  41966. # end
  41967. def after_save() end
  41968. def create_or_update_with_callbacks #:nodoc:
  41969. return false if callback(:before_save) == false
  41970. result = create_or_update_without_callbacks
  41971. callback(:after_save)
  41972. result
  41973. end
  41974. # Is called _before_ Base.save on new objects that haven't been saved yet (no record exists).
  41975. def before_create() end
  41976. # Is called _after_ Base.save on new objects that haven't been saved yet (no record exists).
  41977. def after_create() end
  41978. def create_with_callbacks #:nodoc:
  41979. return false if callback(:before_create) == false
  41980. result = create_without_callbacks
  41981. callback(:after_create)
  41982. result
  41983. end
  41984. # Is called _before_ Base.save on existing objects that have a record.
  41985. def before_update() end
  41986. # Is called _after_ Base.save on existing objects that have a record.
  41987. def after_update() end
  41988. def update_with_callbacks #:nodoc:
  41989. return false if callback(:before_update) == false
  41990. result = update_without_callbacks
  41991. callback(:after_update)
  41992. result
  41993. end
  41994. # Is called _before_ Validations.validate (which is part of the Base.save call).
  41995. def before_validation() end
  41996. # Is called _after_ Validations.validate (which is part of the Base.save call).
  41997. def after_validation() end
  41998. # Is called _before_ Validations.validate (which is part of the Base.save call) on new objects
  41999. # that haven't been saved yet (no record exists).
  42000. def before_validation_on_create() end
  42001. # Is called _after_ Validations.validate (which is part of the Base.save call) on new objects
  42002. # that haven't been saved yet (no record exists).
  42003. def after_validation_on_create() end
  42004. # Is called _before_ Validations.validate (which is part of the Base.save call) on
  42005. # existing objects that have a record.
  42006. def before_validation_on_update() end
  42007. # Is called _after_ Validations.validate (which is part of the Base.save call) on
  42008. # existing objects that have a record.
  42009. def after_validation_on_update() end
  42010. def valid_with_callbacks #:nodoc:
  42011. return false if callback(:before_validation) == false
  42012. if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
  42013. return false if result == false
  42014. result = valid_without_callbacks
  42015. callback(:after_validation)
  42016. if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
  42017. return result
  42018. end
  42019. # Is called _before_ Base.destroy.
  42020. #
  42021. # Note: If you need to _destroy_ or _nullify_ associated records first,
  42022. # use the _:dependent_ option on your associations.
  42023. def before_destroy() end
  42024. # Is called _after_ Base.destroy (and all the attributes have been frozen).
  42025. #
  42026. # class Contact < ActiveRecord::Base
  42027. # after_destroy { |record| logger.info( "Contact #{record.id} was destroyed." ) }
  42028. # end
  42029. def after_destroy() end
  42030. def destroy_with_callbacks #:nodoc:
  42031. return false if callback(:before_destroy) == false
  42032. result = destroy_without_callbacks
  42033. callback(:after_destroy)
  42034. result
  42035. end
  42036. private
  42037. def callback(method)
  42038. notify(method)
  42039. callbacks_for(method).each do |callback|
  42040. result = case callback
  42041. when Symbol
  42042. self.send(callback)
  42043. when String
  42044. eval(callback, binding)
  42045. when Proc, Method
  42046. callback.call(self)
  42047. else
  42048. if callback.respond_to?(method)
  42049. callback.send(method, self)
  42050. else
  42051. raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
  42052. end
  42053. end
  42054. return false if result == false
  42055. end
  42056. result = send(method) if respond_to_without_attributes?(method)
  42057. return result
  42058. end
  42059. def callbacks_for(method)
  42060. self.class.read_inheritable_attribute(method.to_sym) or []
  42061. end
  42062. def invoke_and_notify(method)
  42063. notify(method)
  42064. send(method) if respond_to_without_attributes?(method)
  42065. end
  42066. def notify(method) #:nodoc:
  42067. self.class.changed
  42068. self.class.notify_observers(method, self)
  42069. end
  42070. end
  42071. end
  42072. require 'set'
  42073. module ActiveRecord
  42074. class Base
  42075. class ConnectionSpecification #:nodoc:
  42076. attr_reader :config, :adapter_method
  42077. def initialize (config, adapter_method)
  42078. @config, @adapter_method = config, adapter_method
  42079. end
  42080. end
  42081. # Check for activity after at least +verification_timeout+ seconds.
  42082. # Defaults to 0 (always check.)
  42083. cattr_accessor :verification_timeout
  42084. @@verification_timeout = 0
  42085. # The class -> [adapter_method, config] map
  42086. @@defined_connections = {}
  42087. # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
  42088. @@active_connections = {}
  42089. class << self
  42090. # Retrieve the connection cache.
  42091. def thread_safe_active_connections #:nodoc:
  42092. @@active_connections[Thread.current.object_id] ||= {}
  42093. end
  42094. def single_threaded_active_connections #:nodoc:
  42095. @@active_connections
  42096. end
  42097. # pick up the right active_connection method from @@allow_concurrency
  42098. if @@allow_concurrency
  42099. alias_method :active_connections, :thread_safe_active_connections
  42100. else
  42101. alias_method :active_connections, :single_threaded_active_connections
  42102. end
  42103. # set concurrency support flag (not thread safe, like most of the methods in this file)
  42104. def allow_concurrency=(threaded) #:nodoc:
  42105. logger.debug "allow_concurrency=#{threaded}" if logger
  42106. return if @@allow_concurrency == threaded
  42107. clear_all_cached_connections!
  42108. @@allow_concurrency = threaded
  42109. method_prefix = threaded ? "thread_safe" : "single_threaded"
  42110. sing = (class << self; self; end)
  42111. [:active_connections, :scoped_methods].each do |method|
  42112. sing.send(:alias_method, method, "#{method_prefix}_#{method}")
  42113. end
  42114. log_connections if logger
  42115. end
  42116. def active_connection_name #:nodoc:
  42117. @active_connection_name ||=
  42118. if active_connections[name] || @@defined_connections[name]
  42119. name
  42120. elsif self == ActiveRecord::Base
  42121. nil
  42122. else
  42123. superclass.active_connection_name
  42124. end
  42125. end
  42126. def clear_active_connection_name #:nodoc:
  42127. @active_connection_name = nil
  42128. subclasses.each { |klass| klass.clear_active_connection_name }
  42129. end
  42130. # Returns the connection currently associated with the class. This can
  42131. # also be used to "borrow" the connection to do database work unrelated
  42132. # to any of the specific Active Records.
  42133. def connection
  42134. if @active_connection_name && (conn = active_connections[@active_connection_name])
  42135. conn
  42136. else
  42137. # retrieve_connection sets the cache key.
  42138. conn = retrieve_connection
  42139. active_connections[@active_connection_name] = conn
  42140. end
  42141. end
  42142. # Clears the cache which maps classes to connections.
  42143. def clear_active_connections!
  42144. clear_cache!(@@active_connections) do |name, conn|
  42145. conn.disconnect!
  42146. end
  42147. end
  42148. # Verify active connections.
  42149. def verify_active_connections! #:nodoc:
  42150. if @@allow_concurrency
  42151. remove_stale_cached_threads!(@@active_connections) do |name, conn|
  42152. conn.disconnect!
  42153. end
  42154. end
  42155. active_connections.each_value do |connection|
  42156. connection.verify!(@@verification_timeout)
  42157. end
  42158. end
  42159. private
  42160. def clear_cache!(cache, thread_id = nil, &block)
  42161. if cache
  42162. if @@allow_concurrency
  42163. thread_id ||= Thread.current.object_id
  42164. thread_cache, cache = cache, cache[thread_id]
  42165. return unless cache
  42166. end
  42167. cache.each(&block) if block_given?
  42168. cache.clear
  42169. end
  42170. ensure
  42171. if thread_cache && @@allow_concurrency
  42172. thread_cache.delete(thread_id)
  42173. end
  42174. end
  42175. # Remove stale threads from the cache.
  42176. def remove_stale_cached_threads!(cache, &block)
  42177. stale = Set.new(cache.keys)
  42178. Thread.list.each do |thread|
  42179. stale.delete(thread.object_id) if thread.alive?
  42180. end
  42181. stale.each do |thread_id|
  42182. clear_cache!(cache, thread_id, &block)
  42183. end
  42184. end
  42185. def clear_all_cached_connections!
  42186. if @@allow_concurrency
  42187. @@active_connections.each_value do |connection_hash_for_thread|
  42188. connection_hash_for_thread.each_value {|conn| conn.disconnect! }
  42189. connection_hash_for_thread.clear
  42190. end
  42191. else
  42192. @@active_connections.each_value {|conn| conn.disconnect! }
  42193. end
  42194. @@active_connections.clear
  42195. end
  42196. end
  42197. # Returns the connection currently associated with the class. This can
  42198. # also be used to "borrow" the connection to do database work that isn't
  42199. # easily done without going straight to SQL.
  42200. def connection
  42201. self.class.connection
  42202. end
  42203. # Establishes the connection to the database. Accepts a hash as input where
  42204. # the :adapter key must be specified with the name of a database adapter (in lower-case)
  42205. # example for regular databases (MySQL, Postgresql, etc):
  42206. #
  42207. # ActiveRecord::Base.establish_connection(
  42208. # :adapter => "mysql",
  42209. # :host => "localhost",
  42210. # :username => "myuser",
  42211. # :password => "mypass",
  42212. # :database => "somedatabase"
  42213. # )
  42214. #
  42215. # Example for SQLite database:
  42216. #
  42217. # ActiveRecord::Base.establish_connection(
  42218. # :adapter => "sqlite",
  42219. # :database => "path/to/dbfile"
  42220. # )
  42221. #
  42222. # Also accepts keys as strings (for parsing from yaml for example):
  42223. # ActiveRecord::Base.establish_connection(
  42224. # "adapter" => "sqlite",
  42225. # "database" => "path/to/dbfile"
  42226. # )
  42227. #
  42228. # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
  42229. # may be returned on an error.
  42230. def self.establish_connection(spec = nil)
  42231. case spec
  42232. when nil
  42233. raise AdapterNotSpecified unless defined? RAILS_ENV
  42234. establish_connection(RAILS_ENV)
  42235. when ConnectionSpecification
  42236. clear_active_connection_name
  42237. @active_connection_name = name
  42238. @@defined_connections[name] = spec
  42239. when Symbol, String
  42240. if configuration = configurations[spec.to_s]
  42241. establish_connection(configuration)
  42242. else
  42243. raise AdapterNotSpecified, "#{spec} database is not configured"
  42244. end
  42245. else
  42246. spec = spec.symbolize_keys
  42247. unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
  42248. adapter_method = "#{spec[:adapter]}_connection"
  42249. unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
  42250. remove_connection
  42251. establish_connection(ConnectionSpecification.new(spec, adapter_method))
  42252. end
  42253. end
  42254. # Locate the connection of the nearest super class. This can be an
  42255. # active or defined connections: if it is the latter, it will be
  42256. # opened and set as the active connection for the class it was defined
  42257. # for (not necessarily the current class).
  42258. def self.retrieve_connection #:nodoc:
  42259. # Name is nil if establish_connection hasn't been called for
  42260. # some class along the inheritance chain up to AR::Base yet.
  42261. if name = active_connection_name
  42262. if conn = active_connections[name]
  42263. # Verify the connection.
  42264. conn.verify!(@@verification_timeout)
  42265. elsif spec = @@defined_connections[name]
  42266. # Activate this connection specification.
  42267. klass = name.constantize
  42268. klass.connection = spec
  42269. conn = active_connections[name]
  42270. end
  42271. end
  42272. conn or raise ConnectionNotEstablished
  42273. end
  42274. # Returns true if a connection that's accessible to this class have already been opened.
  42275. def self.connected?
  42276. active_connections[active_connection_name] ? true : false
  42277. end
  42278. # Remove the connection for this class. This will close the active
  42279. # connection and the defined connection (if they exist). The result
  42280. # can be used as argument for establish_connection, for easy
  42281. # re-establishing of the connection.
  42282. def self.remove_connection(klass=self)
  42283. spec = @@defined_connections[klass.name]
  42284. konn = active_connections[klass.name]
  42285. @@defined_connections.delete_if { |key, value| value == spec }
  42286. active_connections.delete_if { |key, value| value == konn }
  42287. konn.disconnect! if konn
  42288. spec.config if spec
  42289. end
  42290. # Set the connection for the class.
  42291. def self.connection=(spec) #:nodoc:
  42292. if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
  42293. active_connections[name] = spec
  42294. elsif spec.kind_of?(ConnectionSpecification)
  42295. self.connection = self.send(spec.adapter_method, spec.config)
  42296. elsif spec.nil?
  42297. raise ConnectionNotEstablished
  42298. else
  42299. establish_connection spec
  42300. end
  42301. end
  42302. # connection state logging
  42303. def self.log_connections #:nodoc:
  42304. if logger
  42305. logger.info "Defined connections: #{@@defined_connections.inspect}"
  42306. logger.info "Active connections: #{active_connections.inspect}"
  42307. logger.info "Active connection name: #{@active_connection_name}"
  42308. end
  42309. end
  42310. end
  42311. end
  42312. module ActiveRecord
  42313. module ConnectionAdapters # :nodoc:
  42314. module DatabaseStatements
  42315. # Returns an array of record hashes with the column names as keys and
  42316. # column values as values.
  42317. def select_all(sql, name = nil)
  42318. end
  42319. # Returns a record hash with the column names as keys and column values
  42320. # as values.
  42321. def select_one(sql, name = nil)
  42322. end
  42323. # Returns a single value from a record
  42324. def select_value(sql, name = nil)
  42325. result = select_one(sql, name)
  42326. result.nil? ? nil : result.values.first
  42327. end
  42328. # Returns an array of the values of the first column in a select:
  42329. # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
  42330. def select_values(sql, name = nil)
  42331. result = select_all(sql, name)
  42332. result.map{ |v| v.values.first }
  42333. end
  42334. # Executes the SQL statement in the context of this connection.
  42335. # This abstract method raises a NotImplementedError.
  42336. def execute(sql, name = nil)
  42337. raise NotImplementedError, "execute is an abstract method"
  42338. end
  42339. # Returns the last auto-generated ID from the affected table.
  42340. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end
  42341. # Executes the update statement and returns the number of rows affected.
  42342. def update(sql, name = nil) end
  42343. # Executes the delete statement and returns the number of rows affected.
  42344. def delete(sql, name = nil) end
  42345. # Wrap a block in a transaction. Returns result of block.
  42346. def transaction(start_db_transaction = true)
  42347. transaction_open = false
  42348. begin
  42349. if block_given?
  42350. if start_db_transaction
  42351. begin_db_transaction
  42352. transaction_open = true
  42353. end
  42354. yield
  42355. end
  42356. rescue Exception => database_transaction_rollback
  42357. if transaction_open
  42358. transaction_open = false
  42359. rollback_db_transaction
  42360. end
  42361. raise
  42362. end
  42363. ensure
  42364. commit_db_transaction if transaction_open
  42365. end
  42366. # Begins the transaction (and turns off auto-committing).
  42367. def begin_db_transaction() end
  42368. # Commits the transaction (and turns on auto-committing).
  42369. def commit_db_transaction() end
  42370. # Rolls back the transaction (and turns on auto-committing). Must be
  42371. # done if the transaction block raises an exception or returns false.
  42372. def rollback_db_transaction() end
  42373. # Alias for #add_limit_offset!.
  42374. def add_limit!(sql, options)
  42375. add_limit_offset!(sql, options) if options
  42376. end
  42377. # Appends +LIMIT+ and +OFFSET+ options to a SQL statement.
  42378. # This method *modifies* the +sql+ parameter.
  42379. # ===== Examples
  42380. # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
  42381. # generates
  42382. # SELECT * FROM suppliers LIMIT 10 OFFSET 50
  42383. def add_limit_offset!(sql, options)
  42384. if limit = options[:limit]
  42385. sql << " LIMIT #{limit}"
  42386. if offset = options[:offset]
  42387. sql << " OFFSET #{offset}"
  42388. end
  42389. end
  42390. end
  42391. def default_sequence_name(table, column)
  42392. nil
  42393. end
  42394. # Set the sequence to the max value of the table's column.
  42395. def reset_sequence!(table, column, sequence = nil)
  42396. # Do nothing by default. Implement for PostgreSQL, Oracle, ...
  42397. end
  42398. end
  42399. end
  42400. end
  42401. module ActiveRecord
  42402. module ConnectionAdapters # :nodoc:
  42403. module Quoting
  42404. # Quotes the column value to help prevent
  42405. # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
  42406. def quote(value, column = nil)
  42407. case value
  42408. when String
  42409. if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
  42410. "'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
  42411. elsif column && [:integer, :float].include?(column.type)
  42412. value.to_s
  42413. else
  42414. "'#{quote_string(value)}'" # ' (for ruby-mode)
  42415. end
  42416. when NilClass then "NULL"
  42417. when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
  42418. when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
  42419. when Float, Fixnum, Bignum then value.to_s
  42420. when Date then "'#{value.to_s}'"
  42421. when Time, DateTime then "'#{quoted_date(value)}'"
  42422. else "'#{quote_string(value.to_yaml)}'"
  42423. end
  42424. end
  42425. # Quotes a string, escaping any ' (single quote) and \ (backslash)
  42426. # characters.
  42427. def quote_string(s)
  42428. s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
  42429. end
  42430. # Returns a quoted form of the column name. This is highly adapter
  42431. # specific.
  42432. def quote_column_name(name)
  42433. name
  42434. end
  42435. def quoted_true
  42436. "'t'"
  42437. end
  42438. def quoted_false
  42439. "'f'"
  42440. end
  42441. def quoted_date(value)
  42442. value.strftime("%Y-%m-%d %H:%M:%S")
  42443. end
  42444. end
  42445. end
  42446. end
  42447. require 'parsedate'
  42448. module ActiveRecord
  42449. module ConnectionAdapters #:nodoc:
  42450. # An abstract definition of a column in a table.
  42451. class Column
  42452. attr_reader :name, :default, :type, :limit, :null, :sql_type
  42453. attr_accessor :primary
  42454. # Instantiates a new column in the table.
  42455. #
  42456. # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
  42457. # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
  42458. # +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
  42459. # +null+ determines if this column allows +NULL+ values.
  42460. def initialize(name, default, sql_type = nil, null = true)
  42461. @name, @type, @null = name, simplified_type(sql_type), null
  42462. @sql_type = sql_type
  42463. # have to do this one separately because type_cast depends on #type
  42464. @default = type_cast(default)
  42465. @limit = extract_limit(sql_type) unless sql_type.nil?
  42466. @primary = nil
  42467. @text = [:string, :text].include? @type
  42468. @number = [:float, :integer].include? @type
  42469. end
  42470. def text?
  42471. @text
  42472. end
  42473. def number?
  42474. @number
  42475. end
  42476. # Returns the Ruby class that corresponds to the abstract data type.
  42477. def klass
  42478. case type
  42479. when :integer then Fixnum
  42480. when :float then Float
  42481. when :datetime then Time
  42482. when :date then Date
  42483. when :timestamp then Time
  42484. when :time then Time
  42485. when :text, :string then String
  42486. when :binary then String
  42487. when :boolean then Object
  42488. end
  42489. end
  42490. # Casts value (which is a String) to an appropriate instance.
  42491. def type_cast(value)
  42492. return nil if value.nil?
  42493. case type
  42494. when :string then value
  42495. when :text then value
  42496. when :integer then value.to_i rescue value ? 1 : 0
  42497. when :float then value.to_f
  42498. when :datetime then self.class.string_to_time(value)
  42499. when :timestamp then self.class.string_to_time(value)
  42500. when :time then self.class.string_to_dummy_time(value)
  42501. when :date then self.class.string_to_date(value)
  42502. when :binary then self.class.binary_to_string(value)
  42503. when :boolean then self.class.value_to_boolean(value)
  42504. else value
  42505. end
  42506. end
  42507. def type_cast_code(var_name)
  42508. case type
  42509. when :string then nil
  42510. when :text then nil
  42511. when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
  42512. when :float then "#{var_name}.to_f"
  42513. when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
  42514. when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
  42515. when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
  42516. when :date then "#{self.class.name}.string_to_date(#{var_name})"
  42517. when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
  42518. when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
  42519. else nil
  42520. end
  42521. end
  42522. # Returns the human name of the column name.
  42523. #
  42524. # ===== Examples
  42525. # Column.new('sales_stage', ...).human_name #=> 'Sales stage'
  42526. def human_name
  42527. Base.human_attribute_name(@name)
  42528. end
  42529. # Used to convert from Strings to BLOBs
  42530. def self.string_to_binary(value)
  42531. value
  42532. end
  42533. # Used to convert from BLOBs to Strings
  42534. def self.binary_to_string(value)
  42535. value
  42536. end
  42537. def self.string_to_date(string)
  42538. return string unless string.is_a?(String)
  42539. date_array = ParseDate.parsedate(string)
  42540. # treat 0000-00-00 as nil
  42541. Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
  42542. end
  42543. def self.string_to_time(string)
  42544. return string unless string.is_a?(String)
  42545. time_array = ParseDate.parsedate(string)[0..5]
  42546. # treat 0000-00-00 00:00:00 as nil
  42547. Time.send(Base.default_timezone, *time_array) rescue nil
  42548. end
  42549. def self.string_to_dummy_time(string)
  42550. return string unless string.is_a?(String)
  42551. time_array = ParseDate.parsedate(string)
  42552. # pad the resulting array with dummy date information
  42553. time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
  42554. Time.send(Base.default_timezone, *time_array) rescue nil
  42555. end
  42556. # convert something to a boolean
  42557. def self.value_to_boolean(value)
  42558. return value if value==true || value==false
  42559. case value.to_s.downcase
  42560. when "true", "t", "1" then true
  42561. else false
  42562. end
  42563. end
  42564. private
  42565. def extract_limit(sql_type)
  42566. $1.to_i if sql_type =~ /\((.*)\)/
  42567. end
  42568. def simplified_type(field_type)
  42569. case field_type
  42570. when /int/i
  42571. :integer
  42572. when /float|double|decimal|numeric/i
  42573. :float
  42574. when /datetime/i
  42575. :datetime
  42576. when /timestamp/i
  42577. :timestamp
  42578. when /time/i
  42579. :time
  42580. when /date/i
  42581. :date
  42582. when /clob/i, /text/i
  42583. :text
  42584. when /blob/i, /binary/i
  42585. :binary
  42586. when /char/i, /string/i
  42587. :string
  42588. when /boolean/i
  42589. :boolean
  42590. end
  42591. end
  42592. end
  42593. class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
  42594. end
  42595. class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc:
  42596. def to_sql
  42597. column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit)}"
  42598. add_column_options!(column_sql, :null => null, :default => default)
  42599. column_sql
  42600. end
  42601. alias to_s :to_sql
  42602. private
  42603. def type_to_sql(name, limit)
  42604. base.type_to_sql(name, limit) rescue name
  42605. end
  42606. def add_column_options!(sql, options)
  42607. base.add_column_options!(sql, options.merge(:column => self))
  42608. end
  42609. end
  42610. # Represents a SQL table in an abstract way.
  42611. # Columns are stored as ColumnDefinition in the #columns attribute.
  42612. class TableDefinition
  42613. attr_accessor :columns
  42614. def initialize(base)
  42615. @columns = []
  42616. @base = base
  42617. end
  42618. # Appends a primary key definition to the table definition.
  42619. # Can be called multiple times, but this is probably not a good idea.
  42620. def primary_key(name)
  42621. column(name, native[:primary_key])
  42622. end
  42623. # Returns a ColumnDefinition for the column with name +name+.
  42624. def [](name)
  42625. @columns.find {|column| column.name.to_s == name.to_s}
  42626. end
  42627. # Instantiates a new column for the table.
  42628. # The +type+ parameter must be one of the following values:
  42629. # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
  42630. # <tt>:integer</tt>, <tt>:float</tt>, <tt>:datetime</tt>,
  42631. # <tt>:timestamp</tt>, <tt>:time</tt>, <tt>:date</tt>,
  42632. # <tt>:binary</tt>, <tt>:boolean</tt>.
  42633. #
  42634. # Available options are (none of these exists by default):
  42635. # * <tt>:limit</tt>:
  42636. # Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
  42637. # <tt>:binary</tt> or <tt>:integer</tt> columns only)
  42638. # * <tt>:default</tt>:
  42639. # The column's default value. You cannot explicitely set the default
  42640. # value to +NULL+. Simply leave off this option if you want a +NULL+
  42641. # default value.
  42642. # * <tt>:null</tt>:
  42643. # Allows or disallows +NULL+ values in the column. This option could
  42644. # have been named <tt>:null_allowed</tt>.
  42645. #
  42646. # This method returns <tt>self</tt>.
  42647. #
  42648. # ===== Examples
  42649. # # Assuming def is an instance of TableDefinition
  42650. # def.column(:granted, :boolean)
  42651. # #=> granted BOOLEAN
  42652. #
  42653. # def.column(:picture, :binary, :limit => 2.megabytes)
  42654. # #=> picture BLOB(2097152)
  42655. #
  42656. # def.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
  42657. # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
  42658. def column(name, type, options = {})
  42659. column = self[name] || ColumnDefinition.new(@base, name, type)
  42660. column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
  42661. column.default = options[:default]
  42662. column.null = options[:null]
  42663. @columns << column unless @columns.include? column
  42664. self
  42665. end
  42666. # Returns a String whose contents are the column definitions
  42667. # concatenated together. This string can then be pre and appended to
  42668. # to generate the final SQL to create the table.
  42669. def to_sql
  42670. @columns * ', '
  42671. end
  42672. private
  42673. def native
  42674. @base.native_database_types
  42675. end
  42676. end
  42677. end
  42678. end
  42679. module ActiveRecord
  42680. module ConnectionAdapters # :nodoc:
  42681. module SchemaStatements
  42682. # Returns a Hash of mappings from the abstract data types to the native
  42683. # database types. See TableDefinition#column for details on the recognized
  42684. # abstract data types.
  42685. def native_database_types
  42686. {}
  42687. end
  42688. # This is the maximum length a table alias can be
  42689. def table_alias_length
  42690. 255
  42691. end
  42692. # Truncates a table alias according to the limits of the current adapter.
  42693. def table_alias_for(table_name)
  42694. table_name[0..table_alias_length-1].gsub(/\./, '_')
  42695. end
  42696. # def tables(name = nil) end
  42697. # Returns an array of indexes for the given table.
  42698. # def indexes(table_name, name = nil) end
  42699. # Returns an array of Column objects for the table specified by +table_name+.
  42700. # See the concrete implementation for details on the expected parameter values.
  42701. def columns(table_name, name = nil) end
  42702. # Creates a new table
  42703. # There are two ways to work with #create_table. You can use the block
  42704. # form or the regular form, like this:
  42705. #
  42706. # === Block form
  42707. # # create_table() yields a TableDefinition instance
  42708. # create_table(:suppliers) do |t|
  42709. # t.column :name, :string, :limit => 60
  42710. # # Other fields here
  42711. # end
  42712. #
  42713. # === Regular form
  42714. # create_table(:suppliers)
  42715. # add_column(:suppliers, :name, :string, {:limit => 60})
  42716. #
  42717. # The +options+ hash can include the following keys:
  42718. # [<tt>:id</tt>]
  42719. # Set to true or false to add/not add a primary key column
  42720. # automatically. Defaults to true.
  42721. # [<tt>:primary_key</tt>]
  42722. # The name of the primary key, if one is to be added automatically.
  42723. # Defaults to +id+.
  42724. # [<tt>:options</tt>]
  42725. # Any extra options you want appended to the table definition.
  42726. # [<tt>:temporary</tt>]
  42727. # Make a temporary table.
  42728. # [<tt>:force</tt>]
  42729. # Set to true or false to drop the table before creating it.
  42730. # Defaults to false.
  42731. #
  42732. # ===== Examples
  42733. # ====== Add a backend specific option to the generated SQL (MySQL)
  42734. # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
  42735. # generates:
  42736. # CREATE TABLE suppliers (
  42737. # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
  42738. # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  42739. #
  42740. # ====== Rename the primary key column
  42741. # create_table(:objects, :primary_key => 'guid') do |t|
  42742. # t.column :name, :string, :limit => 80
  42743. # end
  42744. # generates:
  42745. # CREATE TABLE objects (
  42746. # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
  42747. # name varchar(80)
  42748. # )
  42749. #
  42750. # ====== Do not add a primary key column
  42751. # create_table(:categories_suppliers, :id => false) do |t|
  42752. # t.column :category_id, :integer
  42753. # t.column :supplier_id, :integer
  42754. # end
  42755. # generates:
  42756. # CREATE TABLE categories_suppliers_join (
  42757. # category_id int,
  42758. # supplier_id int
  42759. # )
  42760. #
  42761. # See also TableDefinition#column for details on how to create columns.
  42762. def create_table(name, options = {})
  42763. table_definition = TableDefinition.new(self)
  42764. table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
  42765. yield table_definition
  42766. if options[:force]
  42767. drop_table(name) rescue nil
  42768. end
  42769. create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
  42770. create_sql << "#{name} ("
  42771. create_sql << table_definition.to_sql
  42772. create_sql << ") #{options[:options]}"
  42773. execute create_sql
  42774. end
  42775. # Renames a table.
  42776. # ===== Example
  42777. # rename_table('octopuses', 'octopi')
  42778. def rename_table(name, new_name)
  42779. raise NotImplementedError, "rename_table is not implemented"
  42780. end
  42781. # Drops a table from the database.
  42782. def drop_table(name)
  42783. execute "DROP TABLE #{name}"
  42784. end
  42785. # Adds a new column to the named table.
  42786. # See TableDefinition#column for details of the options you can use.
  42787. def add_column(table_name, column_name, type, options = {})
  42788. add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}"
  42789. add_column_options!(add_column_sql, options)
  42790. execute(add_column_sql)
  42791. end
  42792. # Removes the column from the table definition.
  42793. # ===== Examples
  42794. # remove_column(:suppliers, :qualification)
  42795. def remove_column(table_name, column_name)
  42796. execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
  42797. end
  42798. # Changes the column's definition according to the new options.
  42799. # See TableDefinition#column for details of the options you can use.
  42800. # ===== Examples
  42801. # change_column(:suppliers, :name, :string, :limit => 80)
  42802. # change_column(:accounts, :description, :text)
  42803. def change_column(table_name, column_name, type, options = {})
  42804. raise NotImplementedError, "change_column is not implemented"
  42805. end
  42806. # Sets a new default value for a column. If you want to set the default
  42807. # value to +NULL+, you are out of luck. You need to
  42808. # DatabaseStatements#execute the apppropriate SQL statement yourself.
  42809. # ===== Examples
  42810. # change_column_default(:suppliers, :qualification, 'new')
  42811. # change_column_default(:accounts, :authorized, 1)
  42812. def change_column_default(table_name, column_name, default)
  42813. raise NotImplementedError, "change_column_default is not implemented"
  42814. end
  42815. # Renames a column.
  42816. # ===== Example
  42817. # rename_column(:suppliers, :description, :name)
  42818. def rename_column(table_name, column_name, new_column_name)
  42819. raise NotImplementedError, "rename_column is not implemented"
  42820. end
  42821. # Adds a new index to the table. +column_name+ can be a single Symbol, or
  42822. # an Array of Symbols.
  42823. #
  42824. # The index will be named after the table and the first column names,
  42825. # unless you pass +:name+ as an option.
  42826. #
  42827. # When creating an index on multiple columns, the first column is used as a name
  42828. # for the index. For example, when you specify an index on two columns
  42829. # [+:first+, +:last+], the DBMS creates an index for both columns as well as an
  42830. # index for the first colum +:first+. Using just the first name for this index
  42831. # makes sense, because you will never have to create a singular index with this
  42832. # name.
  42833. #
  42834. # ===== Examples
  42835. # ====== Creating a simple index
  42836. # add_index(:suppliers, :name)
  42837. # generates
  42838. # CREATE INDEX suppliers_name_index ON suppliers(name)
  42839. # ====== Creating a unique index
  42840. # add_index(:accounts, [:branch_id, :party_id], :unique => true)
  42841. # generates
  42842. # CREATE UNIQUE INDEX accounts_branch_id_index ON accounts(branch_id, party_id)
  42843. # ====== Creating a named index
  42844. # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
  42845. # generates
  42846. # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
  42847. def add_index(table_name, column_name, options = {})
  42848. column_names = Array(column_name)
  42849. index_name = index_name(table_name, :column => column_names.first)
  42850. if Hash === options # legacy support, since this param was a string
  42851. index_type = options[:unique] ? "UNIQUE" : ""
  42852. index_name = options[:name] || index_name
  42853. else
  42854. index_type = options
  42855. end
  42856. quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
  42857. execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names})"
  42858. end
  42859. # Remove the given index from the table.
  42860. #
  42861. # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
  42862. # remove_index :suppliers, :name
  42863. # Remove the index named accounts_branch_id in the accounts table.
  42864. # remove_index :accounts, :column => :branch_id
  42865. # Remove the index named by_branch_party in the accounts table.
  42866. # remove_index :accounts, :name => :by_branch_party
  42867. #
  42868. # You can remove an index on multiple columns by specifying the first column.
  42869. # add_index :accounts, [:username, :password]
  42870. # remove_index :accounts, :username
  42871. def remove_index(table_name, options = {})
  42872. execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
  42873. end
  42874. def index_name(table_name, options) #:nodoc:
  42875. if Hash === options # legacy support
  42876. if options[:column]
  42877. "#{table_name}_#{options[:column]}_index"
  42878. elsif options[:name]
  42879. options[:name]
  42880. else
  42881. raise ArgumentError, "You must specify the index name"
  42882. end
  42883. else
  42884. "#{table_name}_#{options}_index"
  42885. end
  42886. end
  42887. # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
  42888. # entire structure of the database.
  42889. def structure_dump
  42890. end
  42891. # Should not be called normally, but this operation is non-destructive.
  42892. # The migrations module handles this automatically.
  42893. def initialize_schema_information
  42894. begin
  42895. execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
  42896. execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
  42897. rescue ActiveRecord::StatementInvalid
  42898. # Schema has been intialized
  42899. end
  42900. end
  42901. def dump_schema_information #:nodoc:
  42902. begin
  42903. if (current_schema = ActiveRecord::Migrator.current_version) > 0
  42904. return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})"
  42905. end
  42906. rescue ActiveRecord::StatementInvalid
  42907. # No Schema Info
  42908. end
  42909. end
  42910. def type_to_sql(type, limit = nil) #:nodoc:
  42911. native = native_database_types[type]
  42912. limit ||= native[:limit]
  42913. column_type_sql = native[:name]
  42914. column_type_sql << "(#{limit})" if limit
  42915. column_type_sql
  42916. end
  42917. def add_column_options!(sql, options) #:nodoc:
  42918. sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
  42919. sql << " NOT NULL" if options[:null] == false
  42920. end
  42921. end
  42922. end
  42923. end
  42924. require 'benchmark'
  42925. require 'date'
  42926. require 'active_record/connection_adapters/abstract/schema_definitions'
  42927. require 'active_record/connection_adapters/abstract/schema_statements'
  42928. require 'active_record/connection_adapters/abstract/database_statements'
  42929. require 'active_record/connection_adapters/abstract/quoting'
  42930. require 'active_record/connection_adapters/abstract/connection_specification'
  42931. module ActiveRecord
  42932. module ConnectionAdapters # :nodoc:
  42933. # All the concrete database adapters follow the interface laid down in this class.
  42934. # You can use this interface directly by borrowing the database connection from the Base with
  42935. # Base.connection.
  42936. #
  42937. # Most of the methods in the adapter are useful during migrations. Most
  42938. # notably, SchemaStatements#create_table, SchemaStatements#drop_table,
  42939. # SchemaStatements#add_index, SchemaStatements#remove_index,
  42940. # SchemaStatements#add_column, SchemaStatements#change_column and
  42941. # SchemaStatements#remove_column are very useful.
  42942. class AbstractAdapter
  42943. include Quoting, DatabaseStatements, SchemaStatements
  42944. @@row_even = true
  42945. def initialize(connection, logger = nil) #:nodoc:
  42946. @connection, @logger = connection, logger
  42947. @runtime = 0
  42948. @last_verification = 0
  42949. end
  42950. # Returns the human-readable name of the adapter. Use mixed case - one
  42951. # can always use downcase if needed.
  42952. def adapter_name
  42953. 'Abstract'
  42954. end
  42955. # Does this adapter support migrations? Backend specific, as the
  42956. # abstract adapter always returns +false+.
  42957. def supports_migrations?
  42958. false
  42959. end
  42960. # Does this adapter support using DISTINCT within COUNT? This is +true+
  42961. # for all adapters except sqlite.
  42962. def supports_count_distinct?
  42963. true
  42964. end
  42965. # Should primary key values be selected from their corresponding
  42966. # sequence before the insert statement? If true, next_sequence_value
  42967. # is called before each insert to set the record's primary key.
  42968. # This is false for all adapters but Firebird.
  42969. def prefetch_primary_key?(table_name = nil)
  42970. false
  42971. end
  42972. def reset_runtime #:nodoc:
  42973. rt, @runtime = @runtime, 0
  42974. rt
  42975. end
  42976. # CONNECTION MANAGEMENT ====================================
  42977. # Is this connection active and ready to perform queries?
  42978. def active?
  42979. @active != false
  42980. end
  42981. # Close this connection and open a new one in its place.
  42982. def reconnect!
  42983. @active = true
  42984. end
  42985. # Close this connection
  42986. def disconnect!
  42987. @active = false
  42988. end
  42989. # Lazily verify this connection, calling +active?+ only if it hasn't
  42990. # been called for +timeout+ seconds.
  42991. def verify!(timeout)
  42992. now = Time.now.to_i
  42993. if (now - @last_verification) > timeout
  42994. reconnect! unless active?
  42995. @last_verification = now
  42996. end
  42997. end
  42998. # Provides access to the underlying database connection. Useful for
  42999. # when you need to call a proprietary method such as postgresql's lo_*
  43000. # methods
  43001. def raw_connection
  43002. @connection
  43003. end
  43004. protected
  43005. def log(sql, name)
  43006. if block_given?
  43007. if @logger and @logger.level <= Logger::INFO
  43008. result = nil
  43009. seconds = Benchmark.realtime { result = yield }
  43010. @runtime += seconds
  43011. log_info(sql, name, seconds)
  43012. result
  43013. else
  43014. yield
  43015. end
  43016. else
  43017. log_info(sql, name, 0)
  43018. nil
  43019. end
  43020. rescue Exception => e
  43021. # Log message and raise exception.
  43022. # Set last_verfication to 0, so that connection gets verified
  43023. # upon reentering the request loop
  43024. @last_verification = 0
  43025. message = "#{e.class.name}: #{e.message}: #{sql}"
  43026. log_info(message, name, 0)
  43027. raise ActiveRecord::StatementInvalid, message
  43028. end
  43029. def log_info(sql, name, runtime)
  43030. return unless @logger
  43031. @logger.debug(
  43032. format_log_entry(
  43033. "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
  43034. sql.gsub(/ +/, " ")
  43035. )
  43036. )
  43037. end
  43038. def format_log_entry(message, dump = nil)
  43039. if ActiveRecord::Base.colorize_logging
  43040. if @@row_even
  43041. @@row_even = false
  43042. message_color, dump_color = "4;36;1", "0;1"
  43043. else
  43044. @@row_even = true
  43045. message_color, dump_color = "4;35;1", "0"
  43046. end
  43047. log_entry = " \e[#{message_color}m#{message}\e[0m "
  43048. log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
  43049. log_entry
  43050. else
  43051. "%s %s" % [message, dump]
  43052. end
  43053. end
  43054. end
  43055. end
  43056. end
  43057. # Author/Maintainer: Maik Schmidt <contact@maik-schmidt.de>
  43058. require 'active_record/connection_adapters/abstract_adapter'
  43059. begin
  43060. require 'db2/db2cli' unless self.class.const_defined?(:DB2CLI)
  43061. require 'active_record/vendor/db2'
  43062. module ActiveRecord
  43063. class Base
  43064. # Establishes a connection to the database that's used by
  43065. # all Active Record objects
  43066. def self.db2_connection(config) # :nodoc:
  43067. config = config.symbolize_keys
  43068. usr = config[:username]
  43069. pwd = config[:password]
  43070. schema = config[:schema]
  43071. if config.has_key?(:database)
  43072. database = config[:database]
  43073. else
  43074. raise ArgumentError, 'No database specified. Missing argument: database.'
  43075. end
  43076. connection = DB2::Connection.new(DB2::Environment.new)
  43077. connection.connect(database, usr, pwd)
  43078. ConnectionAdapters::DB2Adapter.new(connection, logger, :schema => schema)
  43079. end
  43080. end
  43081. module ConnectionAdapters
  43082. # The DB2 adapter works with the C-based CLI driver (http://rubyforge.org/projects/ruby-dbi/)
  43083. #
  43084. # Options:
  43085. #
  43086. # * <tt>:username</tt> -- Defaults to nothing
  43087. # * <tt>:password</tt> -- Defaults to nothing
  43088. # * <tt>:database</tt> -- The name of the database. No default, must be provided.
  43089. # * <tt>:schema</tt> -- Database schema to be set initially.
  43090. class DB2Adapter < AbstractAdapter
  43091. def initialize(connection, logger, connection_options)
  43092. super(connection, logger)
  43093. @connection_options = connection_options
  43094. if schema = @connection_options[:schema]
  43095. with_statement do |stmt|
  43096. stmt.exec_direct("SET SCHEMA=#{schema}")
  43097. end
  43098. end
  43099. end
  43100. def select_all(sql, name = nil)
  43101. select(sql, name)
  43102. end
  43103. def select_one(sql, name = nil)
  43104. select(sql, name).first
  43105. end
  43106. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  43107. execute(sql, name = nil)
  43108. id_value || last_insert_id
  43109. end
  43110. def execute(sql, name = nil)
  43111. rows_affected = 0
  43112. with_statement do |stmt|
  43113. log(sql, name) do
  43114. stmt.exec_direct(sql)
  43115. rows_affected = stmt.row_count
  43116. end
  43117. end
  43118. rows_affected
  43119. end
  43120. alias_method :update, :execute
  43121. alias_method :delete, :execute
  43122. def begin_db_transaction
  43123. @connection.set_auto_commit_off
  43124. end
  43125. def commit_db_transaction
  43126. @connection.commit
  43127. @connection.set_auto_commit_on
  43128. end
  43129. def rollback_db_transaction
  43130. @connection.rollback
  43131. @connection.set_auto_commit_on
  43132. end
  43133. def quote_column_name(column_name)
  43134. column_name
  43135. end
  43136. def adapter_name()
  43137. 'DB2'
  43138. end
  43139. def quote_string(string)
  43140. string.gsub(/'/, "''") # ' (for ruby-mode)
  43141. end
  43142. def add_limit_offset!(sql, options)
  43143. if limit = options[:limit]
  43144. offset = options[:offset] || 0
  43145. # The following trick was added by andrea+rails@webcom.it.
  43146. sql.gsub!(/SELECT/i, 'SELECT B.* FROM (SELECT A.*, row_number() over () AS internal$rownum FROM (SELECT')
  43147. sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
  43148. end
  43149. end
  43150. def tables(name = nil)
  43151. result = []
  43152. schema = @connection_options[:schema] || '%'
  43153. with_statement do |stmt|
  43154. stmt.tables(schema).each { |t| result << t[2].downcase }
  43155. end
  43156. result
  43157. end
  43158. def indexes(table_name, name = nil)
  43159. tmp = {}
  43160. schema = @connection_options[:schema] || ''
  43161. with_statement do |stmt|
  43162. stmt.indexes(table_name, schema).each do |t|
  43163. next unless t[5]
  43164. next if t[4] == 'SYSIBM' # Skip system indexes.
  43165. idx_name = t[5].downcase
  43166. col_name = t[8].downcase
  43167. if tmp.has_key?(idx_name)
  43168. tmp[idx_name].columns << col_name
  43169. else
  43170. is_unique = t[3] == 0
  43171. tmp[idx_name] = IndexDefinition.new(table_name, idx_name, is_unique, [col_name])
  43172. end
  43173. end
  43174. end
  43175. tmp.values
  43176. end
  43177. def columns(table_name, name = nil)
  43178. result = []
  43179. schema = @connection_options[:schema] || '%'
  43180. with_statement do |stmt|
  43181. stmt.columns(table_name, schema).each do |c|
  43182. c_name = c[3].downcase
  43183. c_default = c[12] == 'NULL' ? nil : c[12]
  43184. c_default.gsub!(/^'(.*)'$/, '\1') if !c_default.nil?
  43185. c_type = c[5].downcase
  43186. c_type += "(#{c[6]})" if !c[6].nil? && c[6] != ''
  43187. result << Column.new(c_name, c_default, c_type, c[17] == 'YES')
  43188. end
  43189. end
  43190. result
  43191. end
  43192. def native_database_types
  43193. {
  43194. :primary_key => 'int generated by default as identity (start with 42) primary key',
  43195. :string => { :name => 'varchar', :limit => 255 },
  43196. :text => { :name => 'clob', :limit => 32768 },
  43197. :integer => { :name => 'int' },
  43198. :float => { :name => 'float' },
  43199. :datetime => { :name => 'timestamp' },
  43200. :timestamp => { :name => 'timestamp' },
  43201. :time => { :name => 'time' },
  43202. :date => { :name => 'date' },
  43203. :binary => { :name => 'blob', :limit => 32768 },
  43204. :boolean => { :name => 'decimal', :limit => 1 }
  43205. }
  43206. end
  43207. def quoted_true
  43208. '1'
  43209. end
  43210. def quoted_false
  43211. '0'
  43212. end
  43213. def active?
  43214. @connection.select_one 'select 1 from ibm.sysdummy1'
  43215. true
  43216. rescue Exception
  43217. false
  43218. end
  43219. def reconnect!
  43220. end
  43221. def table_alias_length
  43222. 128
  43223. end
  43224. private
  43225. def with_statement
  43226. stmt = DB2::Statement.new(@connection)
  43227. yield stmt
  43228. stmt.free
  43229. end
  43230. def last_insert_id
  43231. row = select_one(<<-GETID.strip)
  43232. with temp(id) as (values (identity_val_local())) select * from temp
  43233. GETID
  43234. row['id'].to_i
  43235. end
  43236. def select(sql, name = nil)
  43237. rows = []
  43238. with_statement do |stmt|
  43239. log(sql, name) do
  43240. stmt.exec_direct("#{sql.gsub(/=\s*null/i, 'IS NULL')} with ur")
  43241. end
  43242. while row = stmt.fetch_as_hash
  43243. row.delete('internal$rownum')
  43244. rows << row
  43245. end
  43246. end
  43247. rows
  43248. end
  43249. end
  43250. end
  43251. end
  43252. rescue LoadError
  43253. # DB2 driver is unavailable.
  43254. module ActiveRecord # :nodoc:
  43255. class Base
  43256. def self.db2_connection(config) # :nodoc:
  43257. # Set up a reasonable error message
  43258. raise LoadError, "DB2 Libraries could not be loaded."
  43259. end
  43260. end
  43261. end
  43262. end
  43263. # Author: Ken Kunz <kennethkunz@gmail.com>
  43264. require 'active_record/connection_adapters/abstract_adapter'
  43265. module FireRuby # :nodoc: all
  43266. class Database
  43267. def self.new_from_params(database, host, port, service)
  43268. db_string = ""
  43269. if host
  43270. db_string << host
  43271. db_string << "/#{service || port}" if service || port
  43272. db_string << ":"
  43273. end
  43274. db_string << database
  43275. new(db_string)
  43276. end
  43277. end
  43278. end
  43279. module ActiveRecord
  43280. class << Base
  43281. def firebird_connection(config) # :nodoc:
  43282. require_library_or_gem 'fireruby'
  43283. unless defined? FireRuby::SQLType
  43284. raise AdapterNotFound,
  43285. 'The Firebird adapter requires FireRuby version 0.4.0 or greater; you appear ' <<
  43286. 'to be running an older version -- please update FireRuby (gem install fireruby).'
  43287. end
  43288. config = config.symbolize_keys
  43289. unless config.has_key?(:database)
  43290. raise ArgumentError, "No database specified. Missing argument: database."
  43291. end
  43292. options = config[:charset] ? { CHARACTER_SET => config[:charset] } : {}
  43293. connection_params = [config[:username], config[:password], options]
  43294. db = FireRuby::Database.new_from_params(*config.values_at(:database, :host, :port, :service))
  43295. connection = db.connect(*connection_params)
  43296. ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params)
  43297. end
  43298. end
  43299. module ConnectionAdapters
  43300. class FirebirdColumn < Column # :nodoc:
  43301. VARCHAR_MAX_LENGTH = 32_765
  43302. BLOB_MAX_LENGTH = 32_767
  43303. def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
  43304. @firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
  43305. super(name.downcase, nil, @firebird_type, !null_flag)
  43306. @default = parse_default(default_source) if default_source
  43307. @limit = type == 'BLOB' ? BLOB_MAX_LENGTH : length
  43308. @domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale
  43309. end
  43310. def type
  43311. if @domain =~ /BOOLEAN/
  43312. :boolean
  43313. elsif @type == :binary and @sub_type == 1
  43314. :text
  43315. else
  43316. @type
  43317. end
  43318. end
  43319. # Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
  43320. # This enables Firebird to provide an actual value when context variables are used as column
  43321. # defaults (such as CURRENT_TIMESTAMP).
  43322. def default
  43323. if @default
  43324. sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
  43325. connection = ActiveRecord::Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
  43326. if connection
  43327. type_cast connection.execute(sql).to_a.first['CAST']
  43328. else
  43329. raise ConnectionNotEstablished, "No Firebird connections established."
  43330. end
  43331. end
  43332. end
  43333. def type_cast(value)
  43334. if type == :boolean
  43335. value == true or value == ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain[:true]
  43336. else
  43337. super
  43338. end
  43339. end
  43340. private
  43341. def parse_default(default_source)
  43342. default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
  43343. return $1 unless $1.upcase == "NULL"
  43344. end
  43345. def column_def
  43346. case @firebird_type
  43347. when 'BLOB' then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
  43348. when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
  43349. when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
  43350. when 'DOUBLE' then "DOUBLE PRECISION"
  43351. else @firebird_type
  43352. end
  43353. end
  43354. def simplified_type(field_type)
  43355. if field_type == 'TIMESTAMP'
  43356. :datetime
  43357. else
  43358. super
  43359. end
  43360. end
  43361. end
  43362. # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
  43363. # extension, version 0.4.0 or later (available as a gem or from
  43364. # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with
  43365. # Firebird 1.5.x on Linux, OS X and Win32 platforms.
  43366. #
  43367. # == Usage Notes
  43368. #
  43369. # === Sequence (Generator) Names
  43370. # The Firebird adapter supports the same approach adopted for the Oracle
  43371. # adapter. See ActiveRecord::Base#set_sequence_name for more details.
  43372. #
  43373. # Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
  43374. # trigger corresponding to a Firebird sequence generator when using
  43375. # ActiveRecord. In other words, you don't have to try to make Firebird
  43376. # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
  43377. # new record, ActiveRecord pre-fetches the next sequence value for the table
  43378. # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
  43379. # next primary key value is the only reliable method for the Firebird
  43380. # adapter to report back the +id+ after a successful insert.)
  43381. #
  43382. # === BOOLEAN Domain
  43383. # Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
  43384. # define a +BOOLEAN+ _domain_ for this purpose, e.g.:
  43385. #
  43386. # CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
  43387. #
  43388. # When the Firebird adapter encounters a column that is based on a domain
  43389. # that includes "BOOLEAN" in the domain name, it will attempt to treat
  43390. # the column as a +BOOLEAN+.
  43391. #
  43392. # By default, the Firebird adapter will assume that the BOOLEAN domain is
  43393. # defined as above. This can be modified if needed. For example, if you
  43394. # have a legacy schema with the following +BOOLEAN+ domain defined:
  43395. #
  43396. # CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
  43397. #
  43398. # ...you can add the following line to your <tt>environment.rb</tt> file:
  43399. #
  43400. # ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain = { :true => 'T', :false => 'F' }
  43401. #
  43402. # === BLOB Elements
  43403. # The Firebird adapter currently provides only limited support for +BLOB+
  43404. # columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream.
  43405. # When selecting a +BLOB+, the entire element is converted into a String.
  43406. # When inserting or updating a +BLOB+, the entire value is included in-line
  43407. # in the SQL statement, limiting you to values <= 32KB in size.
  43408. #
  43409. # === Column Name Case Semantics
  43410. # Firebird and ActiveRecord have somewhat conflicting case semantics for
  43411. # column names.
  43412. #
  43413. # [*Firebird*]
  43414. # The standard practice is to use unquoted column names, which can be
  43415. # thought of as case-insensitive. (In fact, Firebird converts them to
  43416. # uppercase.) Quoted column names (not typically used) are case-sensitive.
  43417. # [*ActiveRecord*]
  43418. # Attribute accessors corresponding to column names are case-sensitive.
  43419. # The defaults for primary key and inheritance columns are lowercase, and
  43420. # in general, people use lowercase attribute names.
  43421. #
  43422. # In order to map between the differing semantics in a way that conforms
  43423. # to common usage for both Firebird and ActiveRecord, uppercase column names
  43424. # in Firebird are converted to lowercase attribute names in ActiveRecord,
  43425. # and vice-versa. Mixed-case column names retain their case in both
  43426. # directions. Lowercase (quoted) Firebird column names are not supported.
  43427. # This is similar to the solutions adopted by other adapters.
  43428. #
  43429. # In general, the best approach is to use unqouted (case-insensitive) column
  43430. # names in your Firebird DDL (or if you must quote, use uppercase column
  43431. # names). These will correspond to lowercase attributes in ActiveRecord.
  43432. #
  43433. # For example, a Firebird table based on the following DDL:
  43434. #
  43435. # CREATE TABLE products (
  43436. # id BIGINT NOT NULL PRIMARY KEY,
  43437. # "TYPE" VARCHAR(50),
  43438. # name VARCHAR(255) );
  43439. #
  43440. # ...will correspond to an ActiveRecord model class called +Product+ with
  43441. # the following attributes: +id+, +type+, +name+.
  43442. #
  43443. # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
  43444. # In ActiveRecord, the default inheritance column name is +type+. The word
  43445. # _type_ is a Firebird reserved word, so it must be quoted in any Firebird
  43446. # SQL statements. Because of the case mapping described above, you should
  43447. # always reference this column using quoted-uppercase syntax
  43448. # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
  43449. # example above). This holds true for any other Firebird reserved words used
  43450. # as column names as well.
  43451. #
  43452. # === Migrations
  43453. # The Firebird adapter does not currently support Migrations. I hope to
  43454. # add this feature in the near future.
  43455. #
  43456. # == Connection Options
  43457. # The following options are supported by the Firebird adapter. None of the
  43458. # options have default values.
  43459. #
  43460. # <tt>:database</tt>::
  43461. # <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
  43462. # (ii) the full path of a database file; _or_ (iii) a full Firebird
  43463. # connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
  43464. # or <tt>:port</tt> as separate options when using a full connection
  43465. # string.</i>
  43466. # <tt>:host</tt>::
  43467. # Set to <tt>"remote.host.name"</tt> for remote database connections.
  43468. # May be omitted for local connections if a full database path is
  43469. # specified for <tt>:database</tt>. Some platforms require a value of
  43470. # <tt>"localhost"</tt> for local connections when using a Firebird
  43471. # database _alias_.
  43472. # <tt>:service</tt>::
  43473. # Specifies a service name for the connection. Only used if <tt>:host</tt>
  43474. # is provided. Required when connecting to a non-standard service.
  43475. # <tt>:port</tt>::
  43476. # Specifies the connection port. Only used if <tt>:host</tt> is provided
  43477. # and <tt>:service</tt> is not. Required when connecting to a non-standard
  43478. # port and <tt>:service</tt> is not defined.
  43479. # <tt>:username</tt>::
  43480. # Specifies the database user. May be omitted or set to +nil+ (together
  43481. # with <tt>:password</tt>) to use the underlying operating system user
  43482. # credentials on supported platforms.
  43483. # <tt>:password</tt>::
  43484. # Specifies the database password. Must be provided if <tt>:username</tt>
  43485. # is explicitly specified; should be omitted if OS user credentials are
  43486. # are being used.
  43487. # <tt>:charset</tt>::
  43488. # Specifies the character set to be used by the connection. Refer to
  43489. # Firebird documentation for valid options.
  43490. class FirebirdAdapter < AbstractAdapter
  43491. @@boolean_domain = { :true => 1, :false => 0 }
  43492. cattr_accessor :boolean_domain
  43493. def initialize(connection, logger, connection_params=nil)
  43494. super(connection, logger)
  43495. @connection_params = connection_params
  43496. end
  43497. def adapter_name # :nodoc:
  43498. 'Firebird'
  43499. end
  43500. # Returns true for Firebird adapter (since Firebird requires primary key
  43501. # values to be pre-fetched before insert). See also #next_sequence_value.
  43502. def prefetch_primary_key?(table_name = nil)
  43503. true
  43504. end
  43505. def default_sequence_name(table_name, primary_key) # :nodoc:
  43506. "#{table_name}_seq"
  43507. end
  43508. # QUOTING ==================================================
  43509. def quote(value, column = nil) # :nodoc:
  43510. if [Time, DateTime].include?(value.class)
  43511. "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
  43512. else
  43513. super
  43514. end
  43515. end
  43516. def quote_string(string) # :nodoc:
  43517. string.gsub(/'/, "''")
  43518. end
  43519. def quote_column_name(column_name) # :nodoc:
  43520. %Q("#{ar_to_fb_case(column_name)}")
  43521. end
  43522. def quoted_true # :nodoc:
  43523. quote(boolean_domain[:true])
  43524. end
  43525. def quoted_false # :nodoc:
  43526. quote(boolean_domain[:false])
  43527. end
  43528. # CONNECTION MANAGEMENT ====================================
  43529. def active?
  43530. not @connection.closed?
  43531. end
  43532. def reconnect!
  43533. @connection.close
  43534. @connection = @connection.database.connect(*@connection_params)
  43535. end
  43536. # DATABASE STATEMENTS ======================================
  43537. def select_all(sql, name = nil) # :nodoc:
  43538. select(sql, name)
  43539. end
  43540. def select_one(sql, name = nil) # :nodoc:
  43541. result = select(sql, name)
  43542. result.nil? ? nil : result.first
  43543. end
  43544. def execute(sql, name = nil, &block) # :nodoc:
  43545. log(sql, name) do
  43546. if @transaction
  43547. @connection.execute(sql, @transaction, &block)
  43548. else
  43549. @connection.execute_immediate(sql, &block)
  43550. end
  43551. end
  43552. end
  43553. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
  43554. execute(sql, name)
  43555. id_value
  43556. end
  43557. alias_method :update, :execute
  43558. alias_method :delete, :execute
  43559. def begin_db_transaction() # :nodoc:
  43560. @transaction = @connection.start_transaction
  43561. end
  43562. def commit_db_transaction() # :nodoc:
  43563. @transaction.commit
  43564. ensure
  43565. @transaction = nil
  43566. end
  43567. def rollback_db_transaction() # :nodoc:
  43568. @transaction.rollback
  43569. ensure
  43570. @transaction = nil
  43571. end
  43572. def add_limit_offset!(sql, options) # :nodoc:
  43573. if options[:limit]
  43574. limit_string = "FIRST #{options[:limit]}"
  43575. limit_string << " SKIP #{options[:offset]}" if options[:offset]
  43576. sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
  43577. end
  43578. end
  43579. # Returns the next sequence value from a sequence generator. Not generally
  43580. # called directly; used by ActiveRecord to get the next primary key value
  43581. # when inserting a new database record (see #prefetch_primary_key?).
  43582. def next_sequence_value(sequence_name)
  43583. FireRuby::Generator.new(sequence_name, @connection).next(1)
  43584. end
  43585. # SCHEMA STATEMENTS ========================================
  43586. def columns(table_name, name = nil) # :nodoc:
  43587. sql = <<-END_SQL
  43588. SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
  43589. f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
  43590. COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
  43591. COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
  43592. FROM rdb$relation_fields r
  43593. JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
  43594. WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
  43595. ORDER BY r.rdb$field_position
  43596. END_SQL
  43597. execute(sql, name).collect do |field|
  43598. field_values = field.values.collect do |value|
  43599. case value
  43600. when String then value.rstrip
  43601. when FireRuby::Blob then value.to_s
  43602. else value
  43603. end
  43604. end
  43605. FirebirdColumn.new(*field_values)
  43606. end
  43607. end
  43608. private
  43609. def select(sql, name = nil)
  43610. execute(sql, name).collect do |row|
  43611. hashed_row = {}
  43612. row.each do |column, value|
  43613. value = value.to_s if FireRuby::Blob === value
  43614. hashed_row[fb_to_ar_case(column)] = value
  43615. end
  43616. hashed_row
  43617. end
  43618. end
  43619. # Maps uppercase Firebird column names to lowercase for ActiveRecord;
  43620. # mixed-case columns retain their original case.
  43621. def fb_to_ar_case(column_name)
  43622. column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
  43623. end
  43624. # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
  43625. # mixed-case columns retain their original case.
  43626. def ar_to_fb_case(column_name)
  43627. column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
  43628. end
  43629. end
  43630. end
  43631. end
  43632. require 'active_record/connection_adapters/abstract_adapter'
  43633. module ActiveRecord
  43634. class Base
  43635. # Establishes a connection to the database that's used by all Active Record objects.
  43636. def self.mysql_connection(config) # :nodoc:
  43637. # Only include the MySQL driver if one hasn't already been loaded
  43638. unless defined? Mysql
  43639. begin
  43640. require_library_or_gem 'mysql'
  43641. rescue LoadError => cannot_require_mysql
  43642. # Only use the supplied backup Ruby/MySQL driver if no driver is already in place
  43643. begin
  43644. require 'active_record/vendor/mysql'
  43645. rescue LoadError
  43646. raise cannot_require_mysql
  43647. end
  43648. end
  43649. end
  43650. config = config.symbolize_keys
  43651. host = config[:host]
  43652. port = config[:port]
  43653. socket = config[:socket]
  43654. username = config[:username] ? config[:username].to_s : 'root'
  43655. password = config[:password].to_s
  43656. if config.has_key?(:database)
  43657. database = config[:database]
  43658. else
  43659. raise ArgumentError, "No database specified. Missing argument: database."
  43660. end
  43661. mysql = Mysql.init
  43662. mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
  43663. ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
  43664. end
  43665. end
  43666. module ConnectionAdapters
  43667. class MysqlColumn < Column #:nodoc:
  43668. private
  43669. def simplified_type(field_type)
  43670. return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
  43671. return :string if field_type =~ /enum/i
  43672. super
  43673. end
  43674. end
  43675. # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
  43676. # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
  43677. #
  43678. # Options:
  43679. #
  43680. # * <tt>:host</tt> -- Defaults to localhost
  43681. # * <tt>:port</tt> -- Defaults to 3306
  43682. # * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
  43683. # * <tt>:username</tt> -- Defaults to root
  43684. # * <tt>:password</tt> -- Defaults to nothing
  43685. # * <tt>:database</tt> -- The name of the database. No default, must be provided.
  43686. # * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
  43687. # * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
  43688. # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
  43689. # * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
  43690. #
  43691. # By default, the MysqlAdapter will consider all columns of type tinyint(1)
  43692. # as boolean. If you wish to disable this emulation (which was the default
  43693. # behavior in versions 0.13.1 and earlier) you can add the following line
  43694. # to your environment.rb file:
  43695. #
  43696. # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
  43697. class MysqlAdapter < AbstractAdapter
  43698. @@emulate_booleans = true
  43699. cattr_accessor :emulate_booleans
  43700. LOST_CONNECTION_ERROR_MESSAGES = [
  43701. "Server shutdown in progress",
  43702. "Broken pipe",
  43703. "Lost connection to MySQL server during query",
  43704. "MySQL server has gone away"
  43705. ]
  43706. def initialize(connection, logger, connection_options, config)
  43707. super(connection, logger)
  43708. @connection_options, @config = connection_options, config
  43709. @null_values_in_each_hash = Mysql.const_defined?(:VERSION)
  43710. connect
  43711. end
  43712. def adapter_name #:nodoc:
  43713. 'MySQL'
  43714. end
  43715. def supports_migrations? #:nodoc:
  43716. true
  43717. end
  43718. def native_database_types #:nodoc
  43719. {
  43720. :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
  43721. :string => { :name => "varchar", :limit => 255 },
  43722. :text => { :name => "text" },
  43723. :integer => { :name => "int", :limit => 11 },
  43724. :float => { :name => "float" },
  43725. :datetime => { :name => "datetime" },
  43726. :timestamp => { :name => "datetime" },
  43727. :time => { :name => "time" },
  43728. :date => { :name => "date" },
  43729. :binary => { :name => "blob" },
  43730. :boolean => { :name => "tinyint", :limit => 1 }
  43731. }
  43732. end
  43733. # QUOTING ==================================================
  43734. def quote(value, column = nil)
  43735. if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
  43736. s = column.class.string_to_binary(value).unpack("H*")[0]
  43737. "x'#{s}'"
  43738. else
  43739. super
  43740. end
  43741. end
  43742. def quote_column_name(name) #:nodoc:
  43743. "`#{name}`"
  43744. end
  43745. def quote_string(string) #:nodoc:
  43746. @connection.quote(string)
  43747. end
  43748. def quoted_true
  43749. "1"
  43750. end
  43751. def quoted_false
  43752. "0"
  43753. end
  43754. # CONNECTION MANAGEMENT ====================================
  43755. def active?
  43756. if @connection.respond_to?(:stat)
  43757. @connection.stat
  43758. else
  43759. @connection.query 'select 1'
  43760. end
  43761. # mysql-ruby doesn't raise an exception when stat fails.
  43762. if @connection.respond_to?(:errno)
  43763. @connection.errno.zero?
  43764. else
  43765. true
  43766. end
  43767. rescue Mysql::Error
  43768. false
  43769. end
  43770. def reconnect!
  43771. disconnect!
  43772. connect
  43773. end
  43774. def disconnect!
  43775. @connection.close rescue nil
  43776. end
  43777. # DATABASE STATEMENTS ======================================
  43778. def select_all(sql, name = nil) #:nodoc:
  43779. select(sql, name)
  43780. end
  43781. def select_one(sql, name = nil) #:nodoc:
  43782. result = select(sql, name)
  43783. result.nil? ? nil : result.first
  43784. end
  43785. def execute(sql, name = nil, retries = 2) #:nodoc:
  43786. log(sql, name) { @connection.query(sql) }
  43787. rescue ActiveRecord::StatementInvalid => exception
  43788. if exception.message.split(":").first =~ /Packets out of order/
  43789. raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
  43790. else
  43791. raise
  43792. end
  43793. end
  43794. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
  43795. execute(sql, name = nil)
  43796. id_value || @connection.insert_id
  43797. end
  43798. def update(sql, name = nil) #:nodoc:
  43799. execute(sql, name)
  43800. @connection.affected_rows
  43801. end
  43802. alias_method :delete, :update #:nodoc:
  43803. def begin_db_transaction #:nodoc:
  43804. execute "BEGIN"
  43805. rescue Exception
  43806. # Transactions aren't supported
  43807. end
  43808. def commit_db_transaction #:nodoc:
  43809. execute "COMMIT"
  43810. rescue Exception
  43811. # Transactions aren't supported
  43812. end
  43813. def rollback_db_transaction #:nodoc:
  43814. execute "ROLLBACK"
  43815. rescue Exception
  43816. # Transactions aren't supported
  43817. end
  43818. def add_limit_offset!(sql, options) #:nodoc
  43819. if limit = options[:limit]
  43820. unless offset = options[:offset]
  43821. sql << " LIMIT #{limit}"
  43822. else
  43823. sql << " LIMIT #{offset}, #{limit}"
  43824. end
  43825. end
  43826. end
  43827. # SCHEMA STATEMENTS ========================================
  43828. def structure_dump #:nodoc:
  43829. if supports_views?
  43830. sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
  43831. else
  43832. sql = "SHOW TABLES"
  43833. end
  43834. select_all(sql).inject("") do |structure, table|
  43835. table.delete('Table_type')
  43836. structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
  43837. end
  43838. end
  43839. def recreate_database(name) #:nodoc:
  43840. drop_database(name)
  43841. create_database(name)
  43842. end
  43843. def create_database(name) #:nodoc:
  43844. execute "CREATE DATABASE `#{name}`"
  43845. end
  43846. def drop_database(name) #:nodoc:
  43847. execute "DROP DATABASE IF EXISTS `#{name}`"
  43848. end
  43849. def current_database
  43850. select_one("SELECT DATABASE() as db")["db"]
  43851. end
  43852. def tables(name = nil) #:nodoc:
  43853. tables = []
  43854. execute("SHOW TABLES", name).each { |field| tables << field[0] }
  43855. tables
  43856. end
  43857. def indexes(table_name, name = nil)#:nodoc:
  43858. indexes = []
  43859. current_index = nil
  43860. execute("SHOW KEYS FROM #{table_name}", name).each do |row|
  43861. if current_index != row[2]
  43862. next if row[2] == "PRIMARY" # skip the primary key
  43863. current_index = row[2]
  43864. indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
  43865. end
  43866. indexes.last.columns << row[4]
  43867. end
  43868. indexes
  43869. end
  43870. def columns(table_name, name = nil)#:nodoc:
  43871. sql = "SHOW FIELDS FROM #{table_name}"
  43872. columns = []
  43873. execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
  43874. columns
  43875. end
  43876. def create_table(name, options = {}) #:nodoc:
  43877. super(name, {:options => "ENGINE=InnoDB"}.merge(options))
  43878. end
  43879. def rename_table(name, new_name)
  43880. execute "RENAME TABLE #{name} TO #{new_name}"
  43881. end
  43882. def change_column_default(table_name, column_name, default) #:nodoc:
  43883. current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
  43884. change_column(table_name, column_name, current_type, { :default => default })
  43885. end
  43886. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  43887. options[:default] ||= select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
  43888. change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
  43889. add_column_options!(change_column_sql, options)
  43890. execute(change_column_sql)
  43891. end
  43892. def rename_column(table_name, column_name, new_column_name) #:nodoc:
  43893. current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
  43894. execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
  43895. end
  43896. private
  43897. def connect
  43898. encoding = @config[:encoding]
  43899. if encoding
  43900. @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
  43901. end
  43902. @connection.real_connect(*@connection_options)
  43903. execute("SET NAMES '#{encoding}'") if encoding
  43904. end
  43905. def select(sql, name = nil)
  43906. @connection.query_with_result = true
  43907. result = execute(sql, name)
  43908. rows = []
  43909. if @null_values_in_each_hash
  43910. result.each_hash { |row| rows << row }
  43911. else
  43912. all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
  43913. result.each_hash { |row| rows << all_fields.dup.update(row) }
  43914. end
  43915. result.free
  43916. rows
  43917. end
  43918. def supports_views?
  43919. version[0] >= 5
  43920. end
  43921. def version
  43922. @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
  43923. end
  43924. end
  43925. end
  43926. end
  43927. require 'active_record/connection_adapters/abstract_adapter'
  43928. module ActiveRecord
  43929. class Base
  43930. # Establishes a connection to the database that's used by all Active Record objects
  43931. def self.openbase_connection(config) # :nodoc:
  43932. require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)
  43933. config = config.symbolize_keys
  43934. host = config[:host]
  43935. username = config[:username].to_s
  43936. password = config[:password].to_s
  43937. if config.has_key?(:database)
  43938. database = config[:database]
  43939. else
  43940. raise ArgumentError, "No database specified. Missing argument: database."
  43941. end
  43942. oba = ConnectionAdapters::OpenBaseAdapter.new(
  43943. OpenBase.new(database, host, username, password), logger
  43944. )
  43945. oba
  43946. end
  43947. end
  43948. module ConnectionAdapters
  43949. class OpenBaseColumn < Column #:nodoc:
  43950. private
  43951. def simplified_type(field_type)
  43952. return :integer if field_type.downcase =~ /long/
  43953. return :float if field_type.downcase == "money"
  43954. return :binary if field_type.downcase == "object"
  43955. super
  43956. end
  43957. end
  43958. # The OpenBase adapter works with the Ruby/Openbase driver by Tetsuya Suzuki.
  43959. # http://www.spice-of-life.net/ruby-openbase/ (needs version 0.7.3+)
  43960. #
  43961. # Options:
  43962. #
  43963. # * <tt>:host</tt> -- Defaults to localhost
  43964. # * <tt>:username</tt> -- Defaults to nothing
  43965. # * <tt>:password</tt> -- Defaults to nothing
  43966. # * <tt>:database</tt> -- The name of the database. No default, must be provided.
  43967. #
  43968. # The OpenBase adapter will make use of OpenBase's ability to generate unique ids
  43969. # for any column with an unique index applied. Thus, if the value of a primary
  43970. # key is not specified at the time an INSERT is performed, the adapter will prefetch
  43971. # a unique id for the primary key. This prefetching is also necessary in order
  43972. # to return the id after an insert.
  43973. #
  43974. # Caveat: Operations involving LIMIT and OFFSET do not yet work!
  43975. #
  43976. # Maintainer: derrickspell@cdmplus.com
  43977. class OpenBaseAdapter < AbstractAdapter
  43978. def adapter_name
  43979. 'OpenBase'
  43980. end
  43981. def native_database_types
  43982. {
  43983. :primary_key => "integer UNIQUE INDEX DEFAULT _rowid",
  43984. :string => { :name => "char", :limit => 4096 },
  43985. :text => { :name => "text" },
  43986. :integer => { :name => "integer" },
  43987. :float => { :name => "float" },
  43988. :datetime => { :name => "datetime" },
  43989. :timestamp => { :name => "timestamp" },
  43990. :time => { :name => "time" },
  43991. :date => { :name => "date" },
  43992. :binary => { :name => "object" },
  43993. :boolean => { :name => "boolean" }
  43994. }
  43995. end
  43996. def supports_migrations?
  43997. false
  43998. end
  43999. def prefetch_primary_key?(table_name = nil)
  44000. true
  44001. end
  44002. def default_sequence_name(table_name, primary_key) # :nodoc:
  44003. "#{table_name} #{primary_key}"
  44004. end
  44005. def next_sequence_value(sequence_name)
  44006. ary = sequence_name.split(' ')
  44007. if (!ary[1]) then
  44008. ary[0] =~ /(\w+)_nonstd_seq/
  44009. ary[0] = $1
  44010. end
  44011. @connection.unique_row_id(ary[0], ary[1])
  44012. end
  44013. # QUOTING ==================================================
  44014. def quote(value, column = nil)
  44015. if value.kind_of?(String) && column && column.type == :binary
  44016. "'#{@connection.insert_binary(value)}'"
  44017. else
  44018. super
  44019. end
  44020. end
  44021. def quoted_true
  44022. "1"
  44023. end
  44024. def quoted_false
  44025. "0"
  44026. end
  44027. # DATABASE STATEMENTS ======================================
  44028. def add_limit_offset!(sql, options) #:nodoc
  44029. if limit = options[:limit]
  44030. unless offset = options[:offset]
  44031. sql << " RETURN RESULTS #{limit}"
  44032. else
  44033. limit = limit + offset
  44034. sql << " RETURN RESULTS #{offset} TO #{limit}"
  44035. end
  44036. end
  44037. end
  44038. def select_all(sql, name = nil) #:nodoc:
  44039. select(sql, name)
  44040. end
  44041. def select_one(sql, name = nil) #:nodoc:
  44042. add_limit_offset!(sql,{:limit => 1})
  44043. results = select(sql, name)
  44044. results.first if results
  44045. end
  44046. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
  44047. execute(sql, name)
  44048. update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
  44049. id_value
  44050. end
  44051. def execute(sql, name = nil) #:nodoc:
  44052. log(sql, name) { @connection.execute(sql) }
  44053. end
  44054. def update(sql, name = nil) #:nodoc:
  44055. execute(sql, name).rows_affected
  44056. end
  44057. alias_method :delete, :update #:nodoc:
  44058. #=begin
  44059. def begin_db_transaction #:nodoc:
  44060. execute "START TRANSACTION"
  44061. rescue Exception
  44062. # Transactions aren't supported
  44063. end
  44064. def commit_db_transaction #:nodoc:
  44065. execute "COMMIT"
  44066. rescue Exception
  44067. # Transactions aren't supported
  44068. end
  44069. def rollback_db_transaction #:nodoc:
  44070. execute "ROLLBACK"
  44071. rescue Exception
  44072. # Transactions aren't supported
  44073. end
  44074. #=end
  44075. # SCHEMA STATEMENTS ========================================
  44076. # Return the list of all tables in the schema search path.
  44077. def tables(name = nil) #:nodoc:
  44078. tables = @connection.tables
  44079. tables.reject! { |t| /\A_SYS_/ === t }
  44080. tables
  44081. end
  44082. def columns(table_name, name = nil) #:nodoc:
  44083. sql = "SELECT * FROM _sys_tables "
  44084. sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
  44085. sql << "ORDER BY columnNumber"
  44086. columns = []
  44087. select_all(sql, name).each do |row|
  44088. columns << OpenBaseColumn.new(row["fieldname"],
  44089. default_value(row["defaultvalue"]),
  44090. sql_type_name(row["typename"],row["length"]),
  44091. row["notnull"]
  44092. )
  44093. # breakpoint() if row["fieldname"] == "content"
  44094. end
  44095. columns
  44096. end
  44097. def indexes(table_name, name = nil)#:nodoc:
  44098. sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
  44099. sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
  44100. sql << "AND primarykey=0 "
  44101. sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
  44102. sql << "ORDER BY columnNumber"
  44103. indexes = []
  44104. execute(sql, name).each do |row|
  44105. indexes << IndexDefinition.new(table_name,index_name(row),row[3]==1,[row[0]])
  44106. end
  44107. indexes
  44108. end
  44109. private
  44110. def select(sql, name = nil)
  44111. sql = translate_sql(sql)
  44112. results = execute(sql, name)
  44113. date_cols = []
  44114. col_names = []
  44115. results.column_infos.each do |info|
  44116. col_names << info.name
  44117. date_cols << info.name if info.type == "date"
  44118. end
  44119. rows = []
  44120. if ( results.rows_affected )
  44121. results.each do |row| # loop through result rows
  44122. hashed_row = {}
  44123. row.each_index do |index|
  44124. hashed_row["#{col_names[index]}"] = row[index] unless col_names[index] == "_rowid"
  44125. end
  44126. date_cols.each do |name|
  44127. unless hashed_row["#{name}"].nil? or hashed_row["#{name}"].empty?
  44128. hashed_row["#{name}"] = Date.parse(hashed_row["#{name}"],false).to_s
  44129. end
  44130. end
  44131. rows << hashed_row
  44132. end
  44133. end
  44134. rows
  44135. end
  44136. def default_value(value)
  44137. # Boolean type values
  44138. return true if value =~ /true/
  44139. return false if value =~ /false/
  44140. # Date / Time magic values
  44141. return Time.now.to_s if value =~ /^now\(\)/i
  44142. # Empty strings should be set to null
  44143. return nil if value.empty?
  44144. # Otherwise return what we got from OpenBase
  44145. # and hope for the best...
  44146. return value
  44147. end
  44148. def sql_type_name(type_name, length)
  44149. return "#{type_name}(#{length})" if ( type_name =~ /char/ )
  44150. type_name
  44151. end
  44152. def index_name(row = [])
  44153. name = ""
  44154. name << "UNIQUE " if row[3]
  44155. name << "CLUSTERED " if row[4]
  44156. name << "INDEX"
  44157. name
  44158. end
  44159. def translate_sql(sql)
  44160. # Change table.* to list of columns in table
  44161. while (sql =~ /SELECT.*\s(\w+)\.\*/)
  44162. table = $1
  44163. cols = columns(table)
  44164. if ( cols.size == 0 ) then
  44165. # Maybe this is a table alias
  44166. sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
  44167. $1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
  44168. cols = columns($1)
  44169. end
  44170. select_columns = []
  44171. cols.each do |col|
  44172. select_columns << table + '.' + col.name
  44173. end
  44174. sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
  44175. end
  44176. # Change JOIN clause to table list and WHERE condition
  44177. while (sql =~ /JOIN/)
  44178. sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
  44179. join_clause = $1 + $5
  44180. is_outer_join = $3
  44181. join_table = $4
  44182. join_condition = $5
  44183. join_condition.gsub!(/=/,"*") if is_outer_join
  44184. if (sql =~ /WHERE/)
  44185. sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
  44186. else
  44187. sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
  44188. end
  44189. sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
  44190. from_clause = $1
  44191. sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
  44192. sql.gsub!(join_clause,"")
  44193. end
  44194. # ORDER BY _rowid if no explicit ORDER BY
  44195. # This will ensure that find(:first) returns the first inserted row
  44196. if (sql !~ /(ORDER BY)|(GROUP BY)/)
  44197. if (sql =~ /RETURN RESULTS/)
  44198. sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
  44199. else
  44200. sql << " ORDER BY _rowid"
  44201. end
  44202. end
  44203. sql
  44204. end
  44205. def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  44206. sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
  44207. table = $1
  44208. cols = $2
  44209. values = $3
  44210. cols = cols.split(',')
  44211. values.gsub!(/'[^']*'/,"''")
  44212. values.gsub!(/"[^"]*"/,"\"\"")
  44213. values = values.split(',')
  44214. update_cols = []
  44215. values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
  44216. update_sql = "UPDATE #{table} SET"
  44217. update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
  44218. update_sql.chop!()
  44219. update_sql << " WHERE #{pk}=#{quote(id_value)}"
  44220. execute(update_sql, name + " NULL Correction") if update_cols.size > 0
  44221. end
  44222. end
  44223. end
  44224. end
  44225. # oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
  44226. #
  44227. # Original author: Graham Jenkins
  44228. #
  44229. # Current maintainer: Michael Schoen <schoenm@earthlink.net>
  44230. #
  44231. #########################################################################
  44232. #
  44233. # Implementation notes:
  44234. # 1. Redefines (safely) a method in ActiveRecord to make it possible to
  44235. # implement an autonumbering solution for Oracle.
  44236. # 2. The OCI8 driver is patched to properly handle values for LONG and
  44237. # TIMESTAMP columns. The driver-author has indicated that a future
  44238. # release of the driver will obviate this patch.
  44239. # 3. LOB support is implemented through an after_save callback.
  44240. # 4. Oracle does not offer native LIMIT and OFFSET options; this
  44241. # functionality is mimiced through the use of nested selects.
  44242. # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
  44243. #
  44244. # Do what you want with this code, at your own peril, but if any
  44245. # significant portion of my code remains then please acknowledge my
  44246. # contribution.
  44247. # portions Copyright 2005 Graham Jenkins
  44248. require 'active_record/connection_adapters/abstract_adapter'
  44249. require 'delegate'
  44250. begin
  44251. require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8
  44252. module ActiveRecord
  44253. class Base
  44254. def self.oracle_connection(config) #:nodoc:
  44255. # Use OCI8AutoRecover instead of normal OCI8 driver.
  44256. ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger
  44257. end
  44258. # for backwards-compatibility
  44259. def self.oci_connection(config) #:nodoc:
  44260. config[:database] = config[:host]
  44261. self.oracle_connection(config)
  44262. end
  44263. # Enable the id column to be bound into the sql later, by the adapter's insert method.
  44264. # This is preferable to inserting the hard-coded value here, because the insert method
  44265. # needs to know the id value explicitly.
  44266. alias :attributes_with_quotes_pre_oracle :attributes_with_quotes
  44267. def attributes_with_quotes(include_primary_key = true) #:nodoc:
  44268. aq = attributes_with_quotes_pre_oracle(include_primary_key)
  44269. if connection.class == ConnectionAdapters::OracleAdapter
  44270. aq[self.class.primary_key] = ":id" if include_primary_key && aq[self.class.primary_key].nil?
  44271. end
  44272. aq
  44273. end
  44274. # After setting large objects to empty, select the OCI8::LOB
  44275. # and write back the data.
  44276. after_save :write_lobs
  44277. def write_lobs() #:nodoc:
  44278. if connection.is_a?(ConnectionAdapters::OracleAdapter)
  44279. self.class.columns.select { |c| c.type == :binary }.each { |c|
  44280. value = self[c.name]
  44281. next if value.nil? || (value == '')
  44282. lob = connection.select_one(
  44283. "SELECT #{ c.name} FROM #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
  44284. 'Writable Large Object')[c.name]
  44285. lob.write value
  44286. }
  44287. end
  44288. end
  44289. private :write_lobs
  44290. end
  44291. module ConnectionAdapters #:nodoc:
  44292. class OracleColumn < Column #:nodoc:
  44293. attr_reader :sql_type
  44294. # overridden to add the concept of scale, required to differentiate
  44295. # between integer and float fields
  44296. def initialize(name, default, sql_type, limit, scale, null)
  44297. @name, @limit, @sql_type, @scale, @null = name, limit, sql_type, scale, null
  44298. @type = simplified_type(sql_type)
  44299. @default = type_cast(default)
  44300. @primary = nil
  44301. @text = [:string, :text].include? @type
  44302. @number = [:float, :integer].include? @type
  44303. end
  44304. def type_cast(value)
  44305. return nil if value.nil? || value =~ /^\s*null\s*$/i
  44306. case type
  44307. when :string then value
  44308. when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
  44309. when :float then value.to_f
  44310. when :datetime then cast_to_date_or_time(value)
  44311. when :time then cast_to_time(value)
  44312. else value
  44313. end
  44314. end
  44315. private
  44316. def simplified_type(field_type)
  44317. case field_type
  44318. when /char/i : :string
  44319. when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float
  44320. when /date|time/i : @name =~ /_at$/ ? :time : :datetime
  44321. when /clob/i : :text
  44322. when /blob/i : :binary
  44323. end
  44324. end
  44325. def cast_to_date_or_time(value)
  44326. return value if value.is_a? Date
  44327. return nil if value.blank?
  44328. guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value)
  44329. end
  44330. def cast_to_time(value)
  44331. return value if value.is_a? Time
  44332. time_array = ParseDate.parsedate value
  44333. time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
  44334. Time.send(Base.default_timezone, *time_array) rescue nil
  44335. end
  44336. def guess_date_or_time(value)
  44337. (value.hour == 0 and value.min == 0 and value.sec == 0) ?
  44338. Date.new(value.year, value.month, value.day) : value
  44339. end
  44340. end
  44341. # This is an Oracle/OCI adapter for the ActiveRecord persistence
  44342. # framework. It relies upon the OCI8 driver, which works with Oracle 8i
  44343. # and above. Most recent development has been on Debian Linux against
  44344. # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
  44345. # See: http://rubyforge.org/projects/ruby-oci8/
  44346. #
  44347. # Usage notes:
  44348. # * Key generation assumes a "${table_name}_seq" sequence is available
  44349. # for all tables; the sequence name can be changed using
  44350. # ActiveRecord::Base.set_sequence_name. When using Migrations, these
  44351. # sequences are created automatically.
  44352. # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
  44353. # Consequently some hacks are employed to map data back to Date or Time
  44354. # in Ruby. If the column_name ends in _time it's created as a Ruby Time.
  44355. # Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
  44356. # it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
  44357. # you'll probably not care very much. In 9i and up it's tempting to
  44358. # map DATE to Date and TIMESTAMP to Time, but too many databases use
  44359. # DATE for both. Timezones and sub-second precision on timestamps are
  44360. # not supported.
  44361. # * Default values that are functions (such as "SYSDATE") are not
  44362. # supported. This is a restriction of the way ActiveRecord supports
  44363. # default values.
  44364. # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
  44365. # is supported in Oracle9i and later. You will need to use #finder_sql for
  44366. # has_and_belongs_to_many associations to run against Oracle8.
  44367. #
  44368. # Required parameters:
  44369. #
  44370. # * <tt>:username</tt>
  44371. # * <tt>:password</tt>
  44372. # * <tt>:database</tt>
  44373. class OracleAdapter < AbstractAdapter
  44374. def adapter_name #:nodoc:
  44375. 'Oracle'
  44376. end
  44377. def supports_migrations? #:nodoc:
  44378. true
  44379. end
  44380. def native_database_types #:nodoc
  44381. {
  44382. :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
  44383. :string => { :name => "VARCHAR2", :limit => 255 },
  44384. :text => { :name => "CLOB" },
  44385. :integer => { :name => "NUMBER", :limit => 38 },
  44386. :float => { :name => "NUMBER" },
  44387. :datetime => { :name => "DATE" },
  44388. :timestamp => { :name => "DATE" },
  44389. :time => { :name => "DATE" },
  44390. :date => { :name => "DATE" },
  44391. :binary => { :name => "BLOB" },
  44392. :boolean => { :name => "NUMBER", :limit => 1 }
  44393. }
  44394. end
  44395. def table_alias_length
  44396. 30
  44397. end
  44398. # QUOTING ==================================================
  44399. #
  44400. # see: abstract/quoting.rb
  44401. # camelCase column names need to be quoted; not that anyone using Oracle
  44402. # would really do this, but handling this case means we pass the test...
  44403. def quote_column_name(name) #:nodoc:
  44404. name =~ /[A-Z]/ ? "\"#{name}\"" : name
  44405. end
  44406. def quote_string(string) #:nodoc:
  44407. string.gsub(/'/, "''")
  44408. end
  44409. def quote(value, column = nil) #:nodoc:
  44410. if column && column.type == :binary
  44411. %Q{empty_#{ column.sql_type rescue 'blob' }()}
  44412. else
  44413. case value
  44414. when String : %Q{'#{quote_string(value)}'}
  44415. when NilClass : 'null'
  44416. when TrueClass : '1'
  44417. when FalseClass : '0'
  44418. when Numeric : value.to_s
  44419. when Date, Time : %Q{'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
  44420. else %Q{'#{quote_string(value.to_yaml)}'}
  44421. end
  44422. end
  44423. end
  44424. # CONNECTION MANAGEMENT ====================================
  44425. #
  44426. # Returns true if the connection is active.
  44427. def active?
  44428. # Pings the connection to check if it's still good. Note that an
  44429. # #active? method is also available, but that simply returns the
  44430. # last known state, which isn't good enough if the connection has
  44431. # gone stale since the last use.
  44432. @connection.ping
  44433. rescue OCIException
  44434. false
  44435. end
  44436. # Reconnects to the database.
  44437. def reconnect!
  44438. @connection.reset!
  44439. rescue OCIException => e
  44440. @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
  44441. end
  44442. # Disconnects from the database.
  44443. def disconnect!
  44444. @connection.logoff rescue nil
  44445. @connection.active = false
  44446. end
  44447. # DATABASE STATEMENTS ======================================
  44448. #
  44449. # see: abstract/database_statements.rb
  44450. def select_all(sql, name = nil) #:nodoc:
  44451. select(sql, name)
  44452. end
  44453. def select_one(sql, name = nil) #:nodoc:
  44454. result = select_all(sql, name)
  44455. result.size > 0 ? result.first : nil
  44456. end
  44457. def execute(sql, name = nil) #:nodoc:
  44458. log(sql, name) { @connection.exec sql }
  44459. end
  44460. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
  44461. if pk.nil? # Who called us? What does the sql look like? No idea!
  44462. execute sql, name
  44463. elsif id_value # Pre-assigned id
  44464. log(sql, name) { @connection.exec sql }
  44465. else # Assume the sql contains a bind-variable for the id
  44466. id_value = select_one("select #{sequence_name}.nextval id from dual")['id']
  44467. log(sql, name) { @connection.exec sql, id_value }
  44468. end
  44469. id_value
  44470. end
  44471. alias :update :execute #:nodoc:
  44472. alias :delete :execute #:nodoc:
  44473. def begin_db_transaction #:nodoc:
  44474. @connection.autocommit = false
  44475. end
  44476. def commit_db_transaction #:nodoc:
  44477. @connection.commit
  44478. ensure
  44479. @connection.autocommit = true
  44480. end
  44481. def rollback_db_transaction #:nodoc:
  44482. @connection.rollback
  44483. ensure
  44484. @connection.autocommit = true
  44485. end
  44486. def add_limit_offset!(sql, options) #:nodoc:
  44487. offset = options[:offset] || 0
  44488. if limit = options[:limit]
  44489. sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
  44490. elsif offset > 0
  44491. sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
  44492. end
  44493. end
  44494. def default_sequence_name(table, column) #:nodoc:
  44495. "#{table}_seq"
  44496. end
  44497. # SCHEMA STATEMENTS ========================================
  44498. #
  44499. # see: abstract/schema_statements.rb
  44500. def current_database #:nodoc:
  44501. select_one("select sys_context('userenv','db_name') db from dual")["db"]
  44502. end
  44503. def tables(name = nil) #:nodoc:
  44504. select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
  44505. tabs << t.to_a.first.last
  44506. end
  44507. end
  44508. def indexes(table_name, name = nil) #:nodoc:
  44509. result = select_all(<<-SQL, name)
  44510. SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
  44511. FROM user_indexes i, user_ind_columns c
  44512. WHERE i.table_name = '#{table_name.to_s.upcase}'
  44513. AND c.index_name = i.index_name
  44514. AND i.index_name NOT IN (SELECT index_name FROM user_constraints WHERE constraint_type = 'P')
  44515. ORDER BY i.index_name, c.column_position
  44516. SQL
  44517. current_index = nil
  44518. indexes = []
  44519. result.each do |row|
  44520. if current_index != row['index_name']
  44521. indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
  44522. current_index = row['index_name']
  44523. end
  44524. indexes.last.columns << row['column_name']
  44525. end
  44526. indexes
  44527. end
  44528. def columns(table_name, name = nil) #:nodoc:
  44529. (owner, table_name) = @connection.describe(table_name)
  44530. table_cols = %Q{
  44531. select column_name, data_type, data_default, nullable,
  44532. decode(data_type, 'NUMBER', data_precision,
  44533. 'VARCHAR2', data_length,
  44534. null) as length,
  44535. decode(data_type, 'NUMBER', data_scale, null) as scale
  44536. from all_tab_columns
  44537. where owner = '#{owner}'
  44538. and table_name = '#{table_name}'
  44539. order by column_id
  44540. }
  44541. select_all(table_cols, name).map do |row|
  44542. if row['data_default']
  44543. row['data_default'].sub!(/^(.*?)\s*$/, '\1')
  44544. row['data_default'].sub!(/^'(.*)'$/, '\1')
  44545. end
  44546. OracleColumn.new(
  44547. oracle_downcase(row['column_name']),
  44548. row['data_default'],
  44549. row['data_type'],
  44550. (l = row['length']).nil? ? nil : l.to_i,
  44551. (s = row['scale']).nil? ? nil : s.to_i,
  44552. row['nullable'] == 'Y'
  44553. )
  44554. end
  44555. end
  44556. def create_table(name, options = {}) #:nodoc:
  44557. super(name, options)
  44558. execute "CREATE SEQUENCE #{name}_seq START WITH 10000" unless options[:id] == false
  44559. end
  44560. def rename_table(name, new_name) #:nodoc:
  44561. execute "RENAME #{name} TO #{new_name}"
  44562. execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
  44563. end
  44564. def drop_table(name) #:nodoc:
  44565. super(name)
  44566. execute "DROP SEQUENCE #{name}_seq" rescue nil
  44567. end
  44568. def remove_index(table_name, options = {}) #:nodoc:
  44569. execute "DROP INDEX #{index_name(table_name, options)}"
  44570. end
  44571. def change_column_default(table_name, column_name, default) #:nodoc:
  44572. execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
  44573. end
  44574. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  44575. change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
  44576. add_column_options!(change_column_sql, options)
  44577. execute(change_column_sql)
  44578. end
  44579. def rename_column(table_name, column_name, new_column_name) #:nodoc:
  44580. execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
  44581. end
  44582. def remove_column(table_name, column_name) #:nodoc:
  44583. execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
  44584. end
  44585. def structure_dump #:nodoc:
  44586. s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
  44587. structure << "create sequence #{seq.to_a.first.last};\n\n"
  44588. end
  44589. select_all("select table_name from user_tables").inject(s) do |structure, table|
  44590. ddl = "create table #{table.to_a.first.last} (\n "
  44591. cols = select_all(%Q{
  44592. select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
  44593. from user_tab_columns
  44594. where table_name = '#{table.to_a.first.last}'
  44595. order by column_id
  44596. }).map do |row|
  44597. col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
  44598. if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
  44599. col << "(#{row['data_precision'].to_i}"
  44600. col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
  44601. col << ')'
  44602. elsif row['data_type'].include?('CHAR')
  44603. col << "(#{row['data_length'].to_i})"
  44604. end
  44605. col << " default #{row['data_default']}" if !row['data_default'].nil?
  44606. col << ' not null' if row['nullable'] == 'N'
  44607. col
  44608. end
  44609. ddl << cols.join(",\n ")
  44610. ddl << ");\n\n"
  44611. structure << ddl
  44612. end
  44613. end
  44614. def structure_drop #:nodoc:
  44615. s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
  44616. drop << "drop sequence #{seq.to_a.first.last};\n\n"
  44617. end
  44618. select_all("select table_name from user_tables").inject(s) do |drop, table|
  44619. drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
  44620. end
  44621. end
  44622. private
  44623. def select(sql, name = nil)
  44624. cursor = execute(sql, name)
  44625. cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
  44626. rows = []
  44627. while row = cursor.fetch
  44628. hash = Hash.new
  44629. cols.each_with_index do |col, i|
  44630. hash[col] =
  44631. case row[i]
  44632. when OCI8::LOB
  44633. name == 'Writable Large Object' ? row[i]: row[i].read
  44634. when OraDate
  44635. (row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ?
  44636. row[i].to_date : row[i].to_time
  44637. else row[i]
  44638. end unless col == 'raw_rnum_'
  44639. end
  44640. rows << hash
  44641. end
  44642. rows
  44643. ensure
  44644. cursor.close if cursor
  44645. end
  44646. # Oracle column names by default are case-insensitive, but treated as upcase;
  44647. # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
  44648. # their column names when creating Oracle tables, which makes then case-sensitive.
  44649. # I don't know anybody who does this, but we'll handle the theoretical case of a
  44650. # camelCase column name. I imagine other dbs handle this different, since there's a
  44651. # unit test that's currently failing test_oci.
  44652. def oracle_downcase(column_name)
  44653. column_name =~ /[a-z]/ ? column_name : column_name.downcase
  44654. end
  44655. end
  44656. end
  44657. end
  44658. class OCI8 #:nodoc:
  44659. # This OCI8 patch may not longer be required with the upcoming
  44660. # release of version 0.2.
  44661. class Cursor #:nodoc:
  44662. alias :define_a_column_pre_ar :define_a_column
  44663. def define_a_column(i)
  44664. case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
  44665. when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values
  44666. when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
  44667. when 108
  44668. if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
  44669. @stmt.defineByPos(i, String, 65535)
  44670. else
  44671. raise 'unsupported datatype'
  44672. end
  44673. else define_a_column_pre_ar i
  44674. end
  44675. end
  44676. end
  44677. # missing constant from oci8 < 0.1.14
  44678. OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
  44679. # Uses the describeAny OCI call to find the target owner and table_name
  44680. # indicated by +name+, parsing through synonynms as necessary. Returns
  44681. # an array of [owner, table_name].
  44682. def describe(name)
  44683. @desc ||= @@env.alloc(OCIDescribe)
  44684. @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
  44685. @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK)
  44686. info = @desc.attrGet(OCI_ATTR_PARAM)
  44687. case info.attrGet(OCI_ATTR_PTYPE)
  44688. when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
  44689. owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
  44690. table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
  44691. [owner, table_name]
  44692. when OCI_PTYPE_SYN
  44693. schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
  44694. name = info.attrGet(OCI_ATTR_NAME)
  44695. describe(schema + '.' + name)
  44696. end
  44697. end
  44698. end
  44699. # The OracleConnectionFactory factors out the code necessary to connect and
  44700. # configure an Oracle/OCI connection.
  44701. class OracleConnectionFactory #:nodoc:
  44702. def new_connection(username, password, database)
  44703. conn = OCI8.new username, password, database
  44704. conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
  44705. conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
  44706. conn.autocommit = true
  44707. conn
  44708. end
  44709. end
  44710. # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
  44711. # reset functionality. If a call to #exec fails, and autocommit is turned on
  44712. # (ie., we're not in the middle of a longer transaction), it will
  44713. # automatically reconnect and try again. If autocommit is turned off,
  44714. # this would be dangerous (as the earlier part of the implied transaction
  44715. # may have failed silently if the connection died) -- so instead the
  44716. # connection is marked as dead, to be reconnected on it's next use.
  44717. class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
  44718. attr_accessor :active
  44719. alias :active? :active
  44720. cattr_accessor :auto_retry
  44721. class << self
  44722. alias :auto_retry? :auto_retry
  44723. end
  44724. @@auto_retry = false
  44725. def initialize(config, factory = OracleConnectionFactory.new)
  44726. @active = true
  44727. @username, @password, @database = config[:username], config[:password], config[:database]
  44728. @factory = factory
  44729. @connection = @factory.new_connection @username, @password, @database
  44730. super @connection
  44731. end
  44732. # Checks connection, returns true if active. Note that ping actively
  44733. # checks the connection, while #active? simply returns the last
  44734. # known state.
  44735. def ping
  44736. @connection.exec("select 1 from dual") { |r| nil }
  44737. @active = true
  44738. rescue
  44739. @active = false
  44740. raise
  44741. end
  44742. # Resets connection, by logging off and creating a new connection.
  44743. def reset!
  44744. logoff rescue nil
  44745. begin
  44746. @connection = @factory.new_connection @username, @password, @database
  44747. __setobj__ @connection
  44748. @active = true
  44749. rescue
  44750. @active = false
  44751. raise
  44752. end
  44753. end
  44754. # ORA-00028: your session has been killed
  44755. # ORA-01012: not logged on
  44756. # ORA-03113: end-of-file on communication channel
  44757. # ORA-03114: not connected to ORACLE
  44758. LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
  44759. # Adds auto-recovery functionality.
  44760. #
  44761. # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
  44762. def exec(sql, *bindvars)
  44763. should_retry = self.class.auto_retry? && autocommit?
  44764. begin
  44765. @connection.exec(sql, *bindvars)
  44766. rescue OCIException => e
  44767. raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
  44768. @active = false
  44769. raise unless should_retry
  44770. should_retry = false
  44771. reset! rescue nil
  44772. retry
  44773. end
  44774. end
  44775. end
  44776. rescue LoadError
  44777. # OCI8 driver is unavailable.
  44778. module ActiveRecord # :nodoc:
  44779. class Base
  44780. def self.oracle_connection(config) # :nodoc:
  44781. # Set up a reasonable error message
  44782. raise LoadError, "Oracle/OCI libraries could not be loaded."
  44783. end
  44784. def self.oci_connection(config) # :nodoc:
  44785. # Set up a reasonable error message
  44786. raise LoadError, "Oracle/OCI libraries could not be loaded."
  44787. end
  44788. end
  44789. end
  44790. end
  44791. require 'active_record/connection_adapters/abstract_adapter'
  44792. module ActiveRecord
  44793. class Base
  44794. # Establishes a connection to the database that's used by all Active Record objects
  44795. def self.postgresql_connection(config) # :nodoc:
  44796. require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn)
  44797. config = config.symbolize_keys
  44798. host = config[:host]
  44799. port = config[:port] || 5432 unless host.nil?
  44800. username = config[:username].to_s
  44801. password = config[:password].to_s
  44802. min_messages = config[:min_messages]
  44803. if config.has_key?(:database)
  44804. database = config[:database]
  44805. else
  44806. raise ArgumentError, "No database specified. Missing argument: database."
  44807. end
  44808. pga = ConnectionAdapters::PostgreSQLAdapter.new(
  44809. PGconn.connect(host, port, "", "", database, username, password), logger, config
  44810. )
  44811. PGconn.translate_results = false if PGconn.respond_to? :translate_results=
  44812. pga.schema_search_path = config[:schema_search_path] || config[:schema_order]
  44813. pga
  44814. end
  44815. end
  44816. module ConnectionAdapters
  44817. # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base
  44818. # (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers.
  44819. #
  44820. # Options:
  44821. #
  44822. # * <tt>:host</tt> -- Defaults to localhost
  44823. # * <tt>:port</tt> -- Defaults to 5432
  44824. # * <tt>:username</tt> -- Defaults to nothing
  44825. # * <tt>:password</tt> -- Defaults to nothing
  44826. # * <tt>:database</tt> -- The name of the database. No default, must be provided.
  44827. # * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
  44828. # * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
  44829. # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
  44830. class PostgreSQLAdapter < AbstractAdapter
  44831. def adapter_name
  44832. 'PostgreSQL'
  44833. end
  44834. def initialize(connection, logger, config = {})
  44835. super(connection, logger)
  44836. @config = config
  44837. configure_connection
  44838. end
  44839. # Is this connection alive and ready for queries?
  44840. def active?
  44841. if @connection.respond_to?(:status)
  44842. @connection.status == PGconn::CONNECTION_OK
  44843. else
  44844. @connection.query 'SELECT 1'
  44845. true
  44846. end
  44847. # postgres-pr raises a NoMethodError when querying if no conn is available
  44848. rescue PGError, NoMethodError
  44849. false
  44850. end
  44851. # Close then reopen the connection.
  44852. def reconnect!
  44853. # TODO: postgres-pr doesn't have PGconn#reset.
  44854. if @connection.respond_to?(:reset)
  44855. @connection.reset
  44856. configure_connection
  44857. end
  44858. end
  44859. def disconnect!
  44860. # Both postgres and postgres-pr respond to :close
  44861. @connection.close rescue nil
  44862. end
  44863. def native_database_types
  44864. {
  44865. :primary_key => "serial primary key",
  44866. :string => { :name => "character varying", :limit => 255 },
  44867. :text => { :name => "text" },
  44868. :integer => { :name => "integer" },
  44869. :float => { :name => "float" },
  44870. :datetime => { :name => "timestamp" },
  44871. :timestamp => { :name => "timestamp" },
  44872. :time => { :name => "time" },
  44873. :date => { :name => "date" },
  44874. :binary => { :name => "bytea" },
  44875. :boolean => { :name => "boolean" }
  44876. }
  44877. end
  44878. def supports_migrations?
  44879. true
  44880. end
  44881. def table_alias_length
  44882. 63
  44883. end
  44884. # QUOTING ==================================================
  44885. def quote(value, column = nil)
  44886. if value.kind_of?(String) && column && column.type == :binary
  44887. "'#{escape_bytea(value)}'"
  44888. else
  44889. super
  44890. end
  44891. end
  44892. def quote_column_name(name)
  44893. %("#{name}")
  44894. end
  44895. # DATABASE STATEMENTS ======================================
  44896. def select_all(sql, name = nil) #:nodoc:
  44897. select(sql, name)
  44898. end
  44899. def select_one(sql, name = nil) #:nodoc:
  44900. result = select(sql, name)
  44901. result.first if result
  44902. end
  44903. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
  44904. execute(sql, name)
  44905. table = sql.split(" ", 4)[2]
  44906. id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
  44907. end
  44908. def query(sql, name = nil) #:nodoc:
  44909. log(sql, name) { @connection.query(sql) }
  44910. end
  44911. def execute(sql, name = nil) #:nodoc:
  44912. log(sql, name) { @connection.exec(sql) }
  44913. end
  44914. def update(sql, name = nil) #:nodoc:
  44915. execute(sql, name).cmdtuples
  44916. end
  44917. alias_method :delete, :update #:nodoc:
  44918. def begin_db_transaction #:nodoc:
  44919. execute "BEGIN"
  44920. end
  44921. def commit_db_transaction #:nodoc:
  44922. execute "COMMIT"
  44923. end
  44924. def rollback_db_transaction #:nodoc:
  44925. execute "ROLLBACK"
  44926. end
  44927. # SCHEMA STATEMENTS ========================================
  44928. # Return the list of all tables in the schema search path.
  44929. def tables(name = nil) #:nodoc:
  44930. schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
  44931. query(<<-SQL, name).map { |row| row[0] }
  44932. SELECT tablename
  44933. FROM pg_tables
  44934. WHERE schemaname IN (#{schemas})
  44935. SQL
  44936. end
  44937. def indexes(table_name, name = nil) #:nodoc:
  44938. result = query(<<-SQL, name)
  44939. SELECT i.relname, d.indisunique, a.attname
  44940. FROM pg_class t, pg_class i, pg_index d, pg_attribute a
  44941. WHERE i.relkind = 'i'
  44942. AND d.indexrelid = i.oid
  44943. AND d.indisprimary = 'f'
  44944. AND t.oid = d.indrelid
  44945. AND t.relname = '#{table_name}'
  44946. AND a.attrelid = t.oid
  44947. AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
  44948. OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
  44949. OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
  44950. OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
  44951. OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
  44952. ORDER BY i.relname
  44953. SQL
  44954. current_index = nil
  44955. indexes = []
  44956. result.each do |row|
  44957. if current_index != row[0]
  44958. indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
  44959. current_index = row[0]
  44960. end
  44961. indexes.last.columns << row[2]
  44962. end
  44963. indexes
  44964. end
  44965. def columns(table_name, name = nil) #:nodoc:
  44966. column_definitions(table_name).collect do |name, type, default, notnull|
  44967. Column.new(name, default_value(default), translate_field_type(type),
  44968. notnull == "f")
  44969. end
  44970. end
  44971. # Set the schema search path to a string of comma-separated schema names.
  44972. # Names beginning with $ are quoted (e.g. $user => '$user')
  44973. # See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
  44974. def schema_search_path=(schema_csv) #:nodoc:
  44975. if schema_csv
  44976. execute "SET search_path TO #{schema_csv}"
  44977. @schema_search_path = nil
  44978. end
  44979. end
  44980. def schema_search_path #:nodoc:
  44981. @schema_search_path ||= query('SHOW search_path')[0][0]
  44982. end
  44983. def default_sequence_name(table_name, pk = nil)
  44984. default_pk, default_seq = pk_and_sequence_for(table_name)
  44985. default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
  44986. end
  44987. # Resets sequence to the max value of the table's pk if present.
  44988. def reset_pk_sequence!(table, pk = nil, sequence = nil)
  44989. unless pk and sequence
  44990. default_pk, default_sequence = pk_and_sequence_for(table)
  44991. pk ||= default_pk
  44992. sequence ||= default_sequence
  44993. end
  44994. if pk
  44995. if sequence
  44996. select_value <<-end_sql, 'Reset sequence'
  44997. SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
  44998. end_sql
  44999. else
  45000. @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
  45001. end
  45002. end
  45003. end
  45004. # Find a table's primary key and sequence.
  45005. def pk_and_sequence_for(table)
  45006. # First try looking for a sequence with a dependency on the
  45007. # given table's primary key.
  45008. result = execute(<<-end_sql, 'PK and serial sequence')[0]
  45009. SELECT attr.attname, name.nspname, seq.relname
  45010. FROM pg_class seq,
  45011. pg_attribute attr,
  45012. pg_depend dep,
  45013. pg_namespace name,
  45014. pg_constraint cons
  45015. WHERE seq.oid = dep.objid
  45016. AND seq.relnamespace = name.oid
  45017. AND seq.relkind = 'S'
  45018. AND attr.attrelid = dep.refobjid
  45019. AND attr.attnum = dep.refobjsubid
  45020. AND attr.attrelid = cons.conrelid
  45021. AND attr.attnum = cons.conkey[1]
  45022. AND cons.contype = 'p'
  45023. AND dep.refobjid = '#{table}'::regclass
  45024. end_sql
  45025. if result.nil? or result.empty?
  45026. # If that fails, try parsing the primary key's default value.
  45027. # Support the 7.x and 8.0 nextval('foo'::text) as well as
  45028. # the 8.1+ nextval('foo'::regclass).
  45029. # TODO: assumes sequence is in same schema as table.
  45030. result = execute(<<-end_sql, 'PK and custom sequence')[0]
  45031. SELECT attr.attname, name.nspname, split_part(def.adsrc, '\\\'', 2)
  45032. FROM pg_class t
  45033. JOIN pg_namespace name ON (t.relnamespace = name.oid)
  45034. JOIN pg_attribute attr ON (t.oid = attrelid)
  45035. JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
  45036. JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
  45037. WHERE t.oid = '#{table}'::regclass
  45038. AND cons.contype = 'p'
  45039. AND def.adsrc ~* 'nextval'
  45040. end_sql
  45041. end
  45042. # check for existence of . in sequence name as in public.foo_sequence. if it does not exist, join the current namespace
  45043. result.last['.'] ? [result.first, result.last] : [result.first, "#{result[1]}.#{result[2]}"]
  45044. rescue
  45045. nil
  45046. end
  45047. def rename_table(name, new_name)
  45048. execute "ALTER TABLE #{name} RENAME TO #{new_name}"
  45049. end
  45050. def add_column(table_name, column_name, type, options = {})
  45051. execute("ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}")
  45052. execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL") if options[:null] == false
  45053. change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
  45054. end
  45055. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  45056. begin
  45057. execute "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
  45058. rescue ActiveRecord::StatementInvalid
  45059. # This is PG7, so we use a more arcane way of doing it.
  45060. begin_db_transaction
  45061. add_column(table_name, "#{column_name}_ar_tmp", type, options)
  45062. execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
  45063. remove_column(table_name, column_name)
  45064. rename_column(table_name, "#{column_name}_ar_tmp", column_name)
  45065. commit_db_transaction
  45066. end
  45067. change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
  45068. end
  45069. def change_column_default(table_name, column_name, default) #:nodoc:
  45070. execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
  45071. end
  45072. def rename_column(table_name, column_name, new_column_name) #:nodoc:
  45073. execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
  45074. end
  45075. def remove_index(table_name, options) #:nodoc:
  45076. execute "DROP INDEX #{index_name(table_name, options)}"
  45077. end
  45078. private
  45079. BYTEA_COLUMN_TYPE_OID = 17
  45080. TIMESTAMPOID = 1114
  45081. TIMESTAMPTZOID = 1184
  45082. def configure_connection
  45083. if @config[:encoding]
  45084. execute("SET client_encoding TO '#{@config[:encoding]}'")
  45085. end
  45086. if @config[:min_messages]
  45087. execute("SET client_min_messages TO '#{@config[:min_messages]}'")
  45088. end
  45089. end
  45090. def last_insert_id(table, sequence_name)
  45091. Integer(select_value("SELECT currval('#{sequence_name}')"))
  45092. end
  45093. def select(sql, name = nil)
  45094. res = execute(sql, name)
  45095. results = res.result
  45096. rows = []
  45097. if results.length > 0
  45098. fields = res.fields
  45099. results.each do |row|
  45100. hashed_row = {}
  45101. row.each_index do |cel_index|
  45102. column = row[cel_index]
  45103. case res.type(cel_index)
  45104. when BYTEA_COLUMN_TYPE_OID
  45105. column = unescape_bytea(column)
  45106. when TIMESTAMPTZOID, TIMESTAMPOID
  45107. column = cast_to_time(column)
  45108. end
  45109. hashed_row[fields[cel_index]] = column
  45110. end
  45111. rows << hashed_row
  45112. end
  45113. end
  45114. return rows
  45115. end
  45116. def escape_bytea(s)
  45117. if PGconn.respond_to? :escape_bytea
  45118. self.class.send(:define_method, :escape_bytea) do |s|
  45119. PGconn.escape_bytea(s) if s
  45120. end
  45121. else
  45122. self.class.send(:define_method, :escape_bytea) do |s|
  45123. if s
  45124. result = ''
  45125. s.each_byte { |c| result << sprintf('\\\\%03o', c) }
  45126. result
  45127. end
  45128. end
  45129. end
  45130. escape_bytea(s)
  45131. end
  45132. def unescape_bytea(s)
  45133. if PGconn.respond_to? :unescape_bytea
  45134. self.class.send(:define_method, :unescape_bytea) do |s|
  45135. PGconn.unescape_bytea(s) if s
  45136. end
  45137. else
  45138. self.class.send(:define_method, :unescape_bytea) do |s|
  45139. if s
  45140. result = ''
  45141. i, max = 0, s.size
  45142. while i < max
  45143. char = s[i]
  45144. if char == ?\\
  45145. if s[i+1] == ?\\
  45146. char = ?\\
  45147. i += 1
  45148. else
  45149. char = s[i+1..i+3].oct
  45150. i += 3
  45151. end
  45152. end
  45153. result << char
  45154. i += 1
  45155. end
  45156. result
  45157. end
  45158. end
  45159. end
  45160. unescape_bytea(s)
  45161. end
  45162. # Query a table's column names, default values, and types.
  45163. #
  45164. # The underlying query is roughly:
  45165. # SELECT column.name, column.type, default.value
  45166. # FROM column LEFT JOIN default
  45167. # ON column.table_id = default.table_id
  45168. # AND column.num = default.column_num
  45169. # WHERE column.table_id = get_table_id('table_name')
  45170. # AND column.num > 0
  45171. # AND NOT column.is_dropped
  45172. # ORDER BY column.num
  45173. #
  45174. # If the table name is not prefixed with a schema, the database will
  45175. # take the first match from the schema search path.
  45176. #
  45177. # Query implementation notes:
  45178. # - format_type includes the column size constraint, e.g. varchar(50)
  45179. # - ::regclass is a function that gives the id for a table name
  45180. def column_definitions(table_name)
  45181. query <<-end_sql
  45182. SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
  45183. FROM pg_attribute a LEFT JOIN pg_attrdef d
  45184. ON a.attrelid = d.adrelid AND a.attnum = d.adnum
  45185. WHERE a.attrelid = '#{table_name}'::regclass
  45186. AND a.attnum > 0 AND NOT a.attisdropped
  45187. ORDER BY a.attnum
  45188. end_sql
  45189. end
  45190. # Translate PostgreSQL-specific types into simplified SQL types.
  45191. # These are special cases; standard types are handled by
  45192. # ConnectionAdapters::Column#simplified_type.
  45193. def translate_field_type(field_type)
  45194. # Match the beginning of field_type since it may have a size constraint on the end.
  45195. case field_type
  45196. when /^timestamp/i then 'datetime'
  45197. when /^real|^money/i then 'float'
  45198. when /^interval/i then 'string'
  45199. # geometric types (the line type is currently not implemented in postgresql)
  45200. when /^(?:point|lseg|box|"?path"?|polygon|circle)/i then 'string'
  45201. when /^bytea/i then 'binary'
  45202. else field_type # Pass through standard types.
  45203. end
  45204. end
  45205. def default_value(value)
  45206. # Boolean types
  45207. return "t" if value =~ /true/i
  45208. return "f" if value =~ /false/i
  45209. # Char/String/Bytea type values
  45210. return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
  45211. # Numeric values
  45212. return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
  45213. # Fixed dates / times
  45214. return $1 if value =~ /^'(.+)'::(date|timestamp)/
  45215. # Anything else is blank, some user type, or some function
  45216. # and we can't know the value of that, so return nil.
  45217. return nil
  45218. end
  45219. # Only needed for DateTime instances
  45220. def cast_to_time(value)
  45221. return value unless value.class == DateTime
  45222. v = value
  45223. time_array = [v.year, v.month, v.day, v.hour, v.min, v.sec]
  45224. Time.send(Base.default_timezone, *time_array) rescue nil
  45225. end
  45226. end
  45227. end
  45228. end
  45229. # Author: Luke Holden <lholden@cablelan.net>
  45230. # Updated for SQLite3: Jamis Buck <jamis@37signals.com>
  45231. require 'active_record/connection_adapters/abstract_adapter'
  45232. module ActiveRecord
  45233. class Base
  45234. class << self
  45235. # sqlite3 adapter reuses sqlite_connection.
  45236. def sqlite3_connection(config) # :nodoc:
  45237. parse_config!(config)
  45238. unless self.class.const_defined?(:SQLite3)
  45239. require_library_or_gem(config[:adapter])
  45240. end
  45241. db = SQLite3::Database.new(
  45242. config[:database],
  45243. :results_as_hash => true,
  45244. :type_translation => false
  45245. )
  45246. ConnectionAdapters::SQLiteAdapter.new(db, logger)
  45247. end
  45248. # Establishes a connection to the database that's used by all Active Record objects
  45249. def sqlite_connection(config) # :nodoc:
  45250. parse_config!(config)
  45251. unless self.class.const_defined?(:SQLite)
  45252. require_library_or_gem(config[:adapter])
  45253. db = SQLite::Database.new(config[:database], 0)
  45254. db.show_datatypes = "ON" if !defined? SQLite::Version
  45255. db.results_as_hash = true if defined? SQLite::Version
  45256. db.type_translation = false
  45257. # "Downgrade" deprecated sqlite API
  45258. if SQLite.const_defined?(:Version)
  45259. ConnectionAdapters::SQLite2Adapter.new(db, logger)
  45260. else
  45261. ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
  45262. end
  45263. end
  45264. end
  45265. private
  45266. def parse_config!(config)
  45267. config[:database] ||= config[:dbfile]
  45268. # Require database.
  45269. unless config[:database]
  45270. raise ArgumentError, "No database file specified. Missing argument: database"
  45271. end
  45272. # Allow database path relative to RAILS_ROOT, but only if
  45273. # the database path is not the special path that tells
  45274. # Sqlite build a database only in memory.
  45275. if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
  45276. config[:database] = File.expand_path(config[:database], RAILS_ROOT)
  45277. end
  45278. end
  45279. end
  45280. end
  45281. module ConnectionAdapters #:nodoc:
  45282. class SQLiteColumn < Column #:nodoc:
  45283. class << self
  45284. def string_to_binary(value)
  45285. value.gsub(/\0|\%/) do |b|
  45286. case b
  45287. when "\0" then "%00"
  45288. when "%" then "%25"
  45289. end
  45290. end
  45291. end
  45292. def binary_to_string(value)
  45293. value.gsub(/%00|%25/) do |b|
  45294. case b
  45295. when "%00" then "\0"
  45296. when "%25" then "%"
  45297. end
  45298. end
  45299. end
  45300. end
  45301. end
  45302. # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
  45303. # from http://rubyforge.org/projects/sqlite-ruby/).
  45304. #
  45305. # Options:
  45306. #
  45307. # * <tt>:database</tt> -- Path to the database file.
  45308. class SQLiteAdapter < AbstractAdapter
  45309. def adapter_name #:nodoc:
  45310. 'SQLite'
  45311. end
  45312. def supports_migrations? #:nodoc:
  45313. true
  45314. end
  45315. def supports_count_distinct? #:nodoc:
  45316. false
  45317. end
  45318. def native_database_types #:nodoc:
  45319. {
  45320. :primary_key => "INTEGER PRIMARY KEY NOT NULL",
  45321. :string => { :name => "varchar", :limit => 255 },
  45322. :text => { :name => "text" },
  45323. :integer => { :name => "integer" },
  45324. :float => { :name => "float" },
  45325. :datetime => { :name => "datetime" },
  45326. :timestamp => { :name => "datetime" },
  45327. :time => { :name => "datetime" },
  45328. :date => { :name => "date" },
  45329. :binary => { :name => "blob" },
  45330. :boolean => { :name => "boolean" }
  45331. }
  45332. end
  45333. # QUOTING ==================================================
  45334. def quote_string(s) #:nodoc:
  45335. @connection.class.quote(s)
  45336. end
  45337. def quote_column_name(name) #:nodoc:
  45338. %Q("#{name}")
  45339. end
  45340. # DATABASE STATEMENTS ======================================
  45341. def execute(sql, name = nil) #:nodoc:
  45342. catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
  45343. end
  45344. def update(sql, name = nil) #:nodoc:
  45345. execute(sql, name)
  45346. @connection.changes
  45347. end
  45348. def delete(sql, name = nil) #:nodoc:
  45349. sql += " WHERE 1=1" unless sql =~ /WHERE/i
  45350. execute(sql, name)
  45351. @connection.changes
  45352. end
  45353. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
  45354. execute(sql, name = nil)
  45355. id_value || @connection.last_insert_row_id
  45356. end
  45357. def select_all(sql, name = nil) #:nodoc:
  45358. execute(sql, name).map do |row|
  45359. record = {}
  45360. row.each_key do |key|
  45361. if key.is_a?(String)
  45362. record[key.sub(/^\w+\./, '')] = row[key]
  45363. end
  45364. end
  45365. record
  45366. end
  45367. end
  45368. def select_one(sql, name = nil) #:nodoc:
  45369. result = select_all(sql, name)
  45370. result.nil? ? nil : result.first
  45371. end
  45372. def begin_db_transaction #:nodoc:
  45373. catch_schema_changes { @connection.transaction }
  45374. end
  45375. def commit_db_transaction #:nodoc:
  45376. catch_schema_changes { @connection.commit }
  45377. end
  45378. def rollback_db_transaction #:nodoc:
  45379. catch_schema_changes { @connection.rollback }
  45380. end
  45381. # SCHEMA STATEMENTS ========================================
  45382. def tables(name = nil) #:nodoc:
  45383. execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
  45384. row[0]
  45385. end
  45386. end
  45387. def columns(table_name, name = nil) #:nodoc:
  45388. table_structure(table_name).map do |field|
  45389. SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
  45390. end
  45391. end
  45392. def indexes(table_name, name = nil) #:nodoc:
  45393. execute("PRAGMA index_list(#{table_name})", name).map do |row|
  45394. index = IndexDefinition.new(table_name, row['name'])
  45395. index.unique = row['unique'] != '0'
  45396. index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
  45397. index
  45398. end
  45399. end
  45400. def primary_key(table_name) #:nodoc:
  45401. column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
  45402. column ? column['name'] : nil
  45403. end
  45404. def remove_index(table_name, options={}) #:nodoc:
  45405. execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
  45406. end
  45407. def rename_table(name, new_name)
  45408. move_table(name, new_name)
  45409. end
  45410. def add_column(table_name, column_name, type, options = {}) #:nodoc:
  45411. alter_table(table_name) do |definition|
  45412. definition.column(column_name, type, options)
  45413. end
  45414. end
  45415. def remove_column(table_name, column_name) #:nodoc:
  45416. alter_table(table_name) do |definition|
  45417. definition.columns.delete(definition[column_name])
  45418. end
  45419. end
  45420. def change_column_default(table_name, column_name, default) #:nodoc:
  45421. alter_table(table_name) do |definition|
  45422. definition[column_name].default = default
  45423. end
  45424. end
  45425. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  45426. alter_table(table_name) do |definition|
  45427. definition[column_name].instance_eval do
  45428. self.type = type
  45429. self.limit = options[:limit] if options[:limit]
  45430. self.default = options[:default] if options[:default]
  45431. end
  45432. end
  45433. end
  45434. def rename_column(table_name, column_name, new_column_name) #:nodoc:
  45435. alter_table(table_name, :rename => {column_name => new_column_name})
  45436. end
  45437. protected
  45438. def table_structure(table_name)
  45439. returning structure = execute("PRAGMA table_info(#{table_name})") do
  45440. raise ActiveRecord::StatementInvalid if structure.empty?
  45441. end
  45442. end
  45443. def alter_table(table_name, options = {}) #:nodoc:
  45444. altered_table_name = "altered_#{table_name}"
  45445. caller = lambda {|definition| yield definition if block_given?}
  45446. transaction do
  45447. move_table(table_name, altered_table_name,
  45448. options.merge(:temporary => true))
  45449. move_table(altered_table_name, table_name, &caller)
  45450. end
  45451. end
  45452. def move_table(from, to, options = {}, &block) #:nodoc:
  45453. copy_table(from, to, options, &block)
  45454. drop_table(from)
  45455. end
  45456. def copy_table(from, to, options = {}) #:nodoc:
  45457. create_table(to, options) do |@definition|
  45458. columns(from).each do |column|
  45459. column_name = options[:rename] ?
  45460. (options[:rename][column.name] ||
  45461. options[:rename][column.name.to_sym] ||
  45462. column.name) : column.name
  45463. @definition.column(column_name, column.type,
  45464. :limit => column.limit, :default => column.default,
  45465. :null => column.null)
  45466. end
  45467. @definition.primary_key(primary_key(from))
  45468. yield @definition if block_given?
  45469. end
  45470. copy_table_indexes(from, to)
  45471. copy_table_contents(from, to,
  45472. @definition.columns.map {|column| column.name},
  45473. options[:rename] || {})
  45474. end
  45475. def copy_table_indexes(from, to) #:nodoc:
  45476. indexes(from).each do |index|
  45477. name = index.name
  45478. if to == "altered_#{from}"
  45479. name = "temp_#{name}"
  45480. elsif from == "altered_#{to}"
  45481. name = name[5..-1]
  45482. end
  45483. opts = { :name => name }
  45484. opts[:unique] = true if index.unique
  45485. add_index(to, index.columns, opts)
  45486. end
  45487. end
  45488. def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
  45489. column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
  45490. rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
  45491. @connection.execute "SELECT * FROM #{from}" do |row|
  45492. sql = "INSERT INTO #{to} VALUES ("
  45493. sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
  45494. sql << ')'
  45495. @connection.execute sql
  45496. end
  45497. end
  45498. def catch_schema_changes
  45499. return yield
  45500. rescue ActiveRecord::StatementInvalid => exception
  45501. if exception.message =~ /database schema has changed/
  45502. reconnect!
  45503. retry
  45504. else
  45505. raise
  45506. end
  45507. end
  45508. end
  45509. class SQLite2Adapter < SQLiteAdapter # :nodoc:
  45510. # SQLite 2 does not support COUNT(DISTINCT) queries:
  45511. #
  45512. # select COUNT(DISTINCT ArtistID) from CDs;
  45513. #
  45514. # In order to get the number of artists we execute the following statement
  45515. #
  45516. # SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
  45517. def execute(sql, name = nil) #:nodoc:
  45518. super(rewrite_count_distinct_queries(sql), name)
  45519. end
  45520. def rewrite_count_distinct_queries(sql)
  45521. if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
  45522. distinct_column = $1
  45523. distinct_query = $3
  45524. column_name = distinct_column.split('.').last
  45525. "SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
  45526. else
  45527. sql
  45528. end
  45529. end
  45530. end
  45531. class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
  45532. def insert(sql, name = nil, pk = nil, id_value = nil)
  45533. execute(sql, name = nil)
  45534. id_value || @connection.last_insert_rowid
  45535. end
  45536. end
  45537. end
  45538. end
  45539. require 'active_record/connection_adapters/abstract_adapter'
  45540. # sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
  45541. #
  45542. # Author: Joey Gibson <joey@joeygibson.com>
  45543. # Date: 10/14/2004
  45544. #
  45545. # Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
  45546. # Date: 3/22/2005
  45547. #
  45548. # Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
  45549. # Date: 6/26/2005
  45550. #
  45551. # Current maintainer: Ryan Tomayko <rtomayko@gmail.com>
  45552. #
  45553. # Modifications (Migrations): Tom Ward <tom@popdog.net>
  45554. # Date: 27/10/2005
  45555. #
  45556. module ActiveRecord
  45557. class Base
  45558. def self.sqlserver_connection(config) #:nodoc:
  45559. require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
  45560. config = config.symbolize_keys
  45561. mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
  45562. username = config[:username] ? config[:username].to_s : 'sa'
  45563. password = config[:password] ? config[:password].to_s : ''
  45564. autocommit = config.key?(:autocommit) ? config[:autocommit] : true
  45565. if mode == "ODBC"
  45566. raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
  45567. dsn = config[:dsn]
  45568. driver_url = "DBI:ODBC:#{dsn}"
  45569. else
  45570. raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
  45571. database = config[:database]
  45572. host = config[:host] ? config[:host].to_s : 'localhost'
  45573. driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
  45574. end
  45575. conn = DBI.connect(driver_url, username, password)
  45576. conn["AutoCommit"] = autocommit
  45577. ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
  45578. end
  45579. end # class Base
  45580. module ConnectionAdapters
  45581. class ColumnWithIdentity < Column# :nodoc:
  45582. attr_reader :identity, :is_special, :scale
  45583. def initialize(name, default, sql_type = nil, is_identity = false, null = true, scale_value = 0)
  45584. super(name, default, sql_type, null)
  45585. @identity = is_identity
  45586. @is_special = sql_type =~ /text|ntext|image/i ? true : false
  45587. @scale = scale_value
  45588. # SQL Server only supports limits on *char and float types
  45589. @limit = nil unless @type == :float or @type == :string
  45590. end
  45591. def simplified_type(field_type)
  45592. case field_type
  45593. when /int|bigint|smallint|tinyint/i then :integer
  45594. when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :float
  45595. when /datetime|smalldatetime/i then :datetime
  45596. when /timestamp/i then :timestamp
  45597. when /time/i then :time
  45598. when /text|ntext/i then :text
  45599. when /binary|image|varbinary/i then :binary
  45600. when /char|nchar|nvarchar|string|varchar/i then :string
  45601. when /bit/i then :boolean
  45602. when /uniqueidentifier/i then :string
  45603. end
  45604. end
  45605. def type_cast(value)
  45606. return nil if value.nil? || value =~ /^\s*null\s*$/i
  45607. case type
  45608. when :string then value
  45609. when :integer then value == true || value == false ? value == true ? 1 : 0 : value.to_i
  45610. when :float then value.to_f
  45611. when :datetime then cast_to_datetime(value)
  45612. when :timestamp then cast_to_time(value)
  45613. when :time then cast_to_time(value)
  45614. when :date then cast_to_datetime(value)
  45615. when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
  45616. else value
  45617. end
  45618. end
  45619. def cast_to_time(value)
  45620. return value if value.is_a?(Time)
  45621. time_array = ParseDate.parsedate(value)
  45622. time_array[0] ||= 2000
  45623. time_array[1] ||= 1
  45624. time_array[2] ||= 1
  45625. Time.send(Base.default_timezone, *time_array) rescue nil
  45626. end
  45627. def cast_to_datetime(value)
  45628. if value.is_a?(Time)
  45629. if value.year != 0 and value.month != 0 and value.day != 0
  45630. return value
  45631. else
  45632. return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
  45633. end
  45634. end
  45635. return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
  45636. value
  45637. end
  45638. # These methods will only allow the adapter to insert binary data with a length of 7K or less
  45639. # because of a SQL Server statement length policy.
  45640. def self.string_to_binary(value)
  45641. value.gsub(/(\r|\n|\0|\x1a)/) do
  45642. case $1
  45643. when "\r" then "%00"
  45644. when "\n" then "%01"
  45645. when "\0" then "%02"
  45646. when "\x1a" then "%03"
  45647. end
  45648. end
  45649. end
  45650. def self.binary_to_string(value)
  45651. value.gsub(/(%00|%01|%02|%03)/) do
  45652. case $1
  45653. when "%00" then "\r"
  45654. when "%01" then "\n"
  45655. when "%02\0" then "\0"
  45656. when "%03" then "\x1a"
  45657. end
  45658. end
  45659. end
  45660. end
  45661. # In ADO mode, this adapter will ONLY work on Windows systems,
  45662. # since it relies on Win32OLE, which, to my knowledge, is only
  45663. # available on Windows.
  45664. #
  45665. # This mode also relies on the ADO support in the DBI module. If you are using the
  45666. # one-click installer of Ruby, then you already have DBI installed, but
  45667. # the ADO module is *NOT* installed. You will need to get the latest
  45668. # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
  45669. # unzip it, and copy the file
  45670. # <tt>src/lib/dbd_ado/ADO.rb</tt>
  45671. # to
  45672. # <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
  45673. # (you will more than likely need to create the ADO directory).
  45674. # Once you've installed that file, you are ready to go.
  45675. #
  45676. # In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
  45677. # the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
  45678. # and it is available at http://www.ch-werner.de/rubyodbc/
  45679. #
  45680. # Options:
  45681. #
  45682. # * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
  45683. # * <tt>:username</tt> -- Defaults to sa.
  45684. # * <tt>:password</tt> -- Defaults to empty string.
  45685. #
  45686. # ADO specific options:
  45687. #
  45688. # * <tt>:host</tt> -- Defaults to localhost.
  45689. # * <tt>:database</tt> -- The name of the database. No default, must be provided.
  45690. #
  45691. # ODBC specific options:
  45692. #
  45693. # * <tt>:dsn</tt> -- Defaults to nothing.
  45694. #
  45695. # ADO code tested on Windows 2000 and higher systems,
  45696. # running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
  45697. #
  45698. # ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
  45699. # unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
  45700. # [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
  45701. class SQLServerAdapter < AbstractAdapter
  45702. def initialize(connection, logger, connection_options=nil)
  45703. super(connection, logger)
  45704. @connection_options = connection_options
  45705. end
  45706. def native_database_types
  45707. {
  45708. :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
  45709. :string => { :name => "varchar", :limit => 255 },
  45710. :text => { :name => "text" },
  45711. :integer => { :name => "int" },
  45712. :float => { :name => "float", :limit => 8 },
  45713. :datetime => { :name => "datetime" },
  45714. :timestamp => { :name => "datetime" },
  45715. :time => { :name => "datetime" },
  45716. :date => { :name => "datetime" },
  45717. :binary => { :name => "image"},
  45718. :boolean => { :name => "bit"}
  45719. }
  45720. end
  45721. def adapter_name
  45722. 'SQLServer'
  45723. end
  45724. def supports_migrations? #:nodoc:
  45725. true
  45726. end
  45727. # CONNECTION MANAGEMENT ====================================#
  45728. # Returns true if the connection is active.
  45729. def active?
  45730. @connection.execute("SELECT 1") { }
  45731. true
  45732. rescue DBI::DatabaseError, DBI::InterfaceError
  45733. false
  45734. end
  45735. # Reconnects to the database, returns false if no connection could be made.
  45736. def reconnect!
  45737. disconnect!
  45738. @connection = DBI.connect(*@connection_options)
  45739. rescue DBI::DatabaseError => e
  45740. @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
  45741. false
  45742. end
  45743. # Disconnects from the database
  45744. def disconnect!
  45745. @connection.disconnect rescue nil
  45746. end
  45747. def select_all(sql, name = nil)
  45748. select(sql, name)
  45749. end
  45750. def select_one(sql, name = nil)
  45751. add_limit!(sql, :limit => 1)
  45752. result = select(sql, name)
  45753. result.nil? ? nil : result.first
  45754. end
  45755. def columns(table_name, name = nil)
  45756. return [] if table_name.blank?
  45757. table_name = table_name.to_s if table_name.is_a?(Symbol)
  45758. table_name = table_name.split('.')[-1] unless table_name.nil?
  45759. sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, IS_NULLABLE As IsNullable, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
  45760. # Comment out if you want to have the Columns select statment logged.
  45761. # Personally, I think it adds unnecessary bloat to the log.
  45762. # If you do comment it out, make sure to un-comment the "result" line that follows
  45763. result = log(sql, name) { @connection.select_all(sql) }
  45764. #result = @connection.select_all(sql)
  45765. columns = []
  45766. result.each do |field|
  45767. default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
  45768. type = "#{field[:ColType]}(#{field[:Length]})"
  45769. is_identity = field[:IsIdentity] == 1
  45770. is_nullable = field[:IsNullable] == 'YES'
  45771. columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
  45772. end
  45773. columns
  45774. end
  45775. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  45776. begin
  45777. table_name = get_table_name(sql)
  45778. col = get_identity_column(table_name)
  45779. ii_enabled = false
  45780. if col != nil
  45781. if query_contains_identity_column(sql, col)
  45782. begin
  45783. execute enable_identity_insert(table_name, true)
  45784. ii_enabled = true
  45785. rescue Exception => e
  45786. raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
  45787. end
  45788. end
  45789. end
  45790. log(sql, name) do
  45791. @connection.execute(sql)
  45792. id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
  45793. end
  45794. ensure
  45795. if ii_enabled
  45796. begin
  45797. execute enable_identity_insert(table_name, false)
  45798. rescue Exception => e
  45799. raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
  45800. end
  45801. end
  45802. end
  45803. end
  45804. def execute(sql, name = nil)
  45805. if sql =~ /^\s*INSERT/i
  45806. insert(sql, name)
  45807. elsif sql =~ /^\s*UPDATE|^\s*DELETE/i
  45808. log(sql, name) do
  45809. @connection.execute(sql)
  45810. retVal = select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
  45811. end
  45812. else
  45813. log(sql, name) { @connection.execute(sql) }
  45814. end
  45815. end
  45816. def update(sql, name = nil)
  45817. execute(sql, name)
  45818. end
  45819. alias_method :delete, :update
  45820. def begin_db_transaction
  45821. @connection["AutoCommit"] = false
  45822. rescue Exception => e
  45823. @connection["AutoCommit"] = true
  45824. end
  45825. def commit_db_transaction
  45826. @connection.commit
  45827. ensure
  45828. @connection["AutoCommit"] = true
  45829. end
  45830. def rollback_db_transaction
  45831. @connection.rollback
  45832. ensure
  45833. @connection["AutoCommit"] = true
  45834. end
  45835. def quote(value, column = nil)
  45836. case value
  45837. when String
  45838. if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
  45839. "'#{quote_string(column.class.string_to_binary(value))}'"
  45840. else
  45841. "'#{quote_string(value)}'"
  45842. end
  45843. when NilClass then "NULL"
  45844. when TrueClass then '1'
  45845. when FalseClass then '0'
  45846. when Float, Fixnum, Bignum then value.to_s
  45847. when Date then "'#{value.to_s}'"
  45848. when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
  45849. else "'#{quote_string(value.to_yaml)}'"
  45850. end
  45851. end
  45852. def quote_string(string)
  45853. string.gsub(/\'/, "''")
  45854. end
  45855. def quoted_true
  45856. "1"
  45857. end
  45858. def quoted_false
  45859. "0"
  45860. end
  45861. def quote_column_name(name)
  45862. "[#{name}]"
  45863. end
  45864. def add_limit_offset!(sql, options)
  45865. if options[:limit] and options[:offset]
  45866. total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT\b/i, "SELECT TOP 1000000000")}) tally")[0][:TotalRows].to_i
  45867. if (options[:limit] + options[:offset]) >= total_rows
  45868. options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
  45869. end
  45870. sql.sub!(/^\s*SELECT/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT TOP #{options[:limit] + options[:offset]} ")
  45871. sql << ") AS tmp1"
  45872. if options[:order]
  45873. options[:order] = options[:order].split(',').map do |field|
  45874. parts = field.split(" ")
  45875. tc = parts[0]
  45876. if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
  45877. tc.gsub!(/\./, '\\.\\[')
  45878. tc << '\\]'
  45879. end
  45880. if sql =~ /#{tc} AS (t\d_r\d\d?)/
  45881. parts[0] = $1
  45882. end
  45883. parts.join(' ')
  45884. end.join(', ')
  45885. sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
  45886. else
  45887. sql << " ) AS tmp2"
  45888. end
  45889. elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
  45890. sql.sub!(/^\s*SELECT([\s]*distinct)?/i) do
  45891. "SELECT#{$1} TOP #{options[:limit]}"
  45892. end unless options[:limit].nil?
  45893. end
  45894. end
  45895. def recreate_database(name)
  45896. drop_database(name)
  45897. create_database(name)
  45898. end
  45899. def drop_database(name)
  45900. execute "DROP DATABASE #{name}"
  45901. end
  45902. def create_database(name)
  45903. execute "CREATE DATABASE #{name}"
  45904. end
  45905. def current_database
  45906. @connection.select_one("select DB_NAME()")[0]
  45907. end
  45908. def tables(name = nil)
  45909. execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
  45910. table_name = field[0]
  45911. tables << table_name unless table_name == 'dtproperties'
  45912. tables
  45913. end
  45914. end
  45915. def indexes(table_name, name = nil)
  45916. indexes = []
  45917. execute("EXEC sp_helpindex #{table_name}", name).each do |index|
  45918. unique = index[1] =~ /unique/
  45919. primary = index[1] =~ /primary key/
  45920. if !primary
  45921. indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
  45922. end
  45923. end
  45924. indexes
  45925. end
  45926. def rename_table(name, new_name)
  45927. execute "EXEC sp_rename '#{name}', '#{new_name}'"
  45928. end
  45929. def remove_column(table_name, column_name)
  45930. execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
  45931. end
  45932. def rename_column(table, column, new_column_name)
  45933. execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
  45934. end
  45935. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  45936. sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"]
  45937. if options[:default]
  45938. remove_default_constraint(table_name, column_name)
  45939. sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
  45940. end
  45941. sql_commands.each {|c|
  45942. execute(c)
  45943. }
  45944. end
  45945. def remove_column(table_name, column_name)
  45946. remove_default_constraint(table_name, column_name)
  45947. execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
  45948. end
  45949. def remove_default_constraint(table_name, column_name)
  45950. defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
  45951. defaults.each {|constraint|
  45952. execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
  45953. }
  45954. end
  45955. def remove_index(table_name, options = {})
  45956. execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
  45957. end
  45958. def type_to_sql(type, limit = nil) #:nodoc:
  45959. native = native_database_types[type]
  45960. # if there's no :limit in the default type definition, assume that type doesn't support limits
  45961. limit = limit || native[:limit]
  45962. column_type_sql = native[:name]
  45963. column_type_sql << "(#{limit})" if limit
  45964. column_type_sql
  45965. end
  45966. private
  45967. def select(sql, name = nil)
  45968. rows = []
  45969. repair_special_columns(sql)
  45970. log(sql, name) do
  45971. @connection.select_all(sql) do |row|
  45972. record = {}
  45973. row.column_names.each do |col|
  45974. record[col] = row[col]
  45975. record[col] = record[col].to_time if record[col].is_a? DBI::Timestamp
  45976. end
  45977. rows << record
  45978. end
  45979. end
  45980. rows
  45981. end
  45982. def enable_identity_insert(table_name, enable = true)
  45983. if has_identity_column(table_name)
  45984. "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
  45985. end
  45986. end
  45987. def get_table_name(sql)
  45988. if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
  45989. $1
  45990. elsif sql =~ /from\s+([^\(\s]+)\s*/i
  45991. $1
  45992. else
  45993. nil
  45994. end
  45995. end
  45996. def has_identity_column(table_name)
  45997. !get_identity_column(table_name).nil?
  45998. end
  45999. def get_identity_column(table_name)
  46000. @table_columns = {} unless @table_columns
  46001. @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
  46002. @table_columns[table_name].each do |col|
  46003. return col.name if col.identity
  46004. end
  46005. return nil
  46006. end
  46007. def query_contains_identity_column(sql, col)
  46008. sql =~ /\[#{col}\]/
  46009. end
  46010. def change_order_direction(order)
  46011. order.split(",").collect {|fragment|
  46012. case fragment
  46013. when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
  46014. when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
  46015. else String.new(fragment).split(',').join(' DESC,') + ' DESC'
  46016. end
  46017. }.join(",")
  46018. end
  46019. def get_special_columns(table_name)
  46020. special = []
  46021. @table_columns ||= {}
  46022. @table_columns[table_name] ||= columns(table_name)
  46023. @table_columns[table_name].each do |col|
  46024. special << col.name if col.is_special
  46025. end
  46026. special
  46027. end
  46028. def repair_special_columns(sql)
  46029. special_cols = get_special_columns(get_table_name(sql))
  46030. for col in special_cols.to_a
  46031. sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
  46032. sql.gsub!(/ORDER BY #{col.to_s}/i, '')
  46033. end
  46034. sql
  46035. end
  46036. end #class SQLServerAdapter < AbstractAdapter
  46037. end #module ConnectionAdapters
  46038. end #module ActiveRecord
  46039. # sybase_adaptor.rb
  46040. # Author: John Sheets <dev@metacasa.net>
  46041. # Date: 01 Mar 2006
  46042. #
  46043. # Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030)
  46044. #
  46045. # 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
  46046. #
  46047. require 'active_record/connection_adapters/abstract_adapter'
  46048. begin
  46049. require 'sybsql'
  46050. module ActiveRecord
  46051. class Base
  46052. # Establishes a connection to the database that's used by all Active Record objects
  46053. def self.sybase_connection(config) # :nodoc:
  46054. config = config.symbolize_keys
  46055. username = config[:username] ? config[:username].to_s : 'sa'
  46056. password = config[:password] ? config[:password].to_s : ''
  46057. if config.has_key?(:host)
  46058. host = config[:host]
  46059. else
  46060. raise ArgumentError, "No database server name specified. Missing argument: host."
  46061. end
  46062. if config.has_key?(:database)
  46063. database = config[:database]
  46064. else
  46065. raise ArgumentError, "No database specified. Missing argument: database."
  46066. end
  46067. ConnectionAdapters::SybaseAdapter.new(
  46068. SybSQL.new({'S' => host, 'U' => username, 'P' => password},
  46069. ConnectionAdapters::SybaseAdapterContext), database, logger)
  46070. end
  46071. end # class Base
  46072. module ConnectionAdapters
  46073. # ActiveRecord connection adapter for Sybase Open Client bindings
  46074. # (see http://raa.ruby-lang.org/project/sybase-ctlib).
  46075. #
  46076. # Options:
  46077. #
  46078. # * <tt>:host</tt> -- The name of the database server. No default, must be provided.
  46079. # * <tt>:database</tt> -- The name of the database. No default, must be provided.
  46080. # * <tt>:username</tt> -- Defaults to sa.
  46081. # * <tt>:password</tt> -- Defaults to empty string.
  46082. #
  46083. # Usage Notes:
  46084. #
  46085. # * The sybase-ctlib bindings do not support the DATE SQL column type; use DATETIME instead.
  46086. # * Table and column names are limited to 30 chars in Sybase 12.5
  46087. # * :binary columns not yet supported
  46088. # * :boolean columns use the BIT SQL type, which does not allow nulls or
  46089. # indexes. If a DEFAULT is not specified for ALTER TABLE commands, the
  46090. # column will be declared with DEFAULT 0 (false).
  46091. #
  46092. # Migrations:
  46093. #
  46094. # The Sybase adapter supports migrations, but for ALTER TABLE commands to
  46095. # work, the database must have the database option 'select into' set to
  46096. # 'true' with sp_dboption (see below). The sp_helpdb command lists the current
  46097. # options for all databases.
  46098. #
  46099. # 1> use mydb
  46100. # 2> go
  46101. # 1> master..sp_dboption mydb, "select into", true
  46102. # 2> go
  46103. # 1> checkpoint
  46104. # 2> go
  46105. class SybaseAdapter < AbstractAdapter # :nodoc:
  46106. class ColumnWithIdentity < Column
  46107. attr_reader :identity, :primary
  46108. def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
  46109. super(name, default, sql_type, nullable)
  46110. @default, @identity, @primary = type_cast(default), identity, primary
  46111. end
  46112. def simplified_type(field_type)
  46113. case field_type
  46114. when /int|bigint|smallint|tinyint/i then :integer
  46115. when /float|double|decimal|money|numeric|real|smallmoney/i then :float
  46116. when /text|ntext/i then :text
  46117. when /binary|image|varbinary/i then :binary
  46118. when /char|nchar|nvarchar|string|varchar/i then :string
  46119. when /bit/i then :boolean
  46120. when /datetime|smalldatetime/i then :datetime
  46121. else super
  46122. end
  46123. end
  46124. def self.string_to_binary(value)
  46125. "0x#{value.unpack("H*")[0]}"
  46126. end
  46127. def self.binary_to_string(value)
  46128. # FIXME: sybase-ctlib uses separate sql method for binary columns.
  46129. value
  46130. end
  46131. end # class ColumnWithIdentity
  46132. # Sybase adapter
  46133. def initialize(connection, database, logger = nil)
  46134. super(connection, logger)
  46135. context = connection.context
  46136. context.init(logger)
  46137. @limit = @offset = 0
  46138. unless connection.sql_norow("USE #{database}")
  46139. raise "Cannot USE #{database}"
  46140. end
  46141. end
  46142. def native_database_types
  46143. {
  46144. :primary_key => "numeric(9,0) IDENTITY PRIMARY KEY",
  46145. :string => { :name => "varchar", :limit => 255 },
  46146. :text => { :name => "text" },
  46147. :integer => { :name => "int" },
  46148. :float => { :name => "float", :limit => 8 },
  46149. :datetime => { :name => "datetime" },
  46150. :timestamp => { :name => "timestamp" },
  46151. :time => { :name => "time" },
  46152. :date => { :name => "datetime" },
  46153. :binary => { :name => "image"},
  46154. :boolean => { :name => "bit" }
  46155. }
  46156. end
  46157. def adapter_name
  46158. 'Sybase'
  46159. end
  46160. def active?
  46161. !(@connection.connection.nil? || @connection.connection_dead?)
  46162. end
  46163. def disconnect!
  46164. @connection.close rescue nil
  46165. end
  46166. def reconnect!
  46167. raise "Sybase Connection Adapter does not yet support reconnect!"
  46168. # disconnect!
  46169. # connect! # Not yet implemented
  46170. end
  46171. def table_alias_length
  46172. 30
  46173. end
  46174. # Check for a limit statement and parse out the limit and
  46175. # offset if specified. Remove the limit from the sql statement
  46176. # and call select.
  46177. def select_all(sql, name = nil)
  46178. select(sql, name)
  46179. end
  46180. # Remove limit clause from statement. This will almost always
  46181. # contain LIMIT 1 from the caller. set the rowcount to 1 before
  46182. # calling select.
  46183. def select_one(sql, name = nil)
  46184. result = select(sql, name)
  46185. result.nil? ? nil : result.first
  46186. end
  46187. def columns(table_name, name = nil)
  46188. table_structure(table_name).inject([]) do |columns, column|
  46189. name, default, type, nullable, identity, primary = column
  46190. columns << ColumnWithIdentity.new(name, default, type, nullable, identity, primary)
  46191. columns
  46192. end
  46193. end
  46194. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  46195. begin
  46196. table_name = get_table_name(sql)
  46197. col = get_identity_column(table_name)
  46198. ii_enabled = false
  46199. if col != nil
  46200. if query_contains_identity_column(sql, col)
  46201. begin
  46202. execute enable_identity_insert(table_name, true)
  46203. ii_enabled = true
  46204. rescue Exception => e
  46205. raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
  46206. end
  46207. end
  46208. end
  46209. log(sql, name) do
  46210. execute(sql, name)
  46211. ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"]
  46212. id_value || ident
  46213. end
  46214. ensure
  46215. if ii_enabled
  46216. begin
  46217. execute enable_identity_insert(table_name, false)
  46218. rescue Exception => e
  46219. raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
  46220. end
  46221. end
  46222. end
  46223. end
  46224. def execute(sql, name = nil)
  46225. log(sql, name) do
  46226. @connection.context.reset
  46227. @connection.set_rowcount(@limit || 0)
  46228. @limit = @offset = nil
  46229. @connection.sql_norow(sql)
  46230. if @connection.cmd_fail? or @connection.context.failed?
  46231. raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
  46232. end
  46233. end
  46234. # Return rows affected
  46235. @connection.results[0].row_count
  46236. end
  46237. alias_method :update, :execute
  46238. alias_method :delete, :execute
  46239. def begin_db_transaction() execute "BEGIN TRAN" end
  46240. def commit_db_transaction() execute "COMMIT TRAN" end
  46241. def rollback_db_transaction() execute "ROLLBACK TRAN" end
  46242. def tables(name = nil)
  46243. tables = []
  46244. select("select name from sysobjects where type='U'", name).each do |row|
  46245. tables << row['name']
  46246. end
  46247. tables
  46248. end
  46249. def indexes(table_name, name = nil)
  46250. indexes = []
  46251. select("exec sp_helpindex #{table_name}", name).each do |index|
  46252. unique = index["index_description"] =~ /unique/
  46253. primary = index["index_description"] =~ /^clustered/
  46254. if !primary
  46255. cols = index["index_keys"].split(", ").each { |col| col.strip! }
  46256. indexes << IndexDefinition.new(table_name, index["index_name"], unique, cols)
  46257. end
  46258. end
  46259. indexes
  46260. end
  46261. def quoted_true
  46262. "1"
  46263. end
  46264. def quoted_false
  46265. "0"
  46266. end
  46267. def quote(value, column = nil)
  46268. case value
  46269. when String
  46270. if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
  46271. "#{quote_string(column.class.string_to_binary(value))}"
  46272. elsif value =~ /^[+-]?[0-9]+$/o
  46273. value
  46274. else
  46275. "'#{quote_string(value)}'"
  46276. end
  46277. when NilClass then (column && column.type == :boolean) ? '0' : "NULL"
  46278. when TrueClass then '1'
  46279. when FalseClass then '0'
  46280. when Float, Fixnum, Bignum then value.to_s
  46281. when Date then "'#{value.to_s}'"
  46282. when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
  46283. else "'#{quote_string(value.to_yaml)}'"
  46284. end
  46285. end
  46286. def quote_column(type, value)
  46287. case type
  46288. when :boolean
  46289. case value
  46290. when String then value =~ /^[ty]/o ? 1 : 0
  46291. when true then 1
  46292. when false then 0
  46293. else value.to_i
  46294. end
  46295. when :integer then value.to_i
  46296. when :float then value.to_f
  46297. when :text, :string, :enum
  46298. case value
  46299. when String, Symbol, Fixnum, Float, Bignum, TrueClass, FalseClass
  46300. "'#{quote_string(value.to_s)}'"
  46301. else
  46302. "'#{quote_string(value.to_yaml)}'"
  46303. end
  46304. when :date, :datetime, :time
  46305. case value
  46306. when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
  46307. when Date then "'#{value.to_s}'"
  46308. else "'#{quote_string(value)}'"
  46309. end
  46310. else "'#{quote_string(value.to_yaml)}'"
  46311. end
  46312. end
  46313. def quote_string(s)
  46314. s.gsub(/'/, "''") # ' (for ruby-mode)
  46315. end
  46316. def quote_column_name(name)
  46317. "[#{name}]"
  46318. end
  46319. def add_limit_offset!(sql, options) # :nodoc:
  46320. @limit = options[:limit]
  46321. @offset = options[:offset]
  46322. if !normal_select?
  46323. # Use temp table to hack offset with Sybase
  46324. sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
  46325. elsif zero_limit?
  46326. # "SET ROWCOUNT 0" turns off limits, so we have
  46327. # to use a cheap trick.
  46328. if sql =~ /WHERE/i
  46329. sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
  46330. elsif sql =~ /ORDER\s+BY/i
  46331. sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
  46332. else
  46333. sql << 'WHERE 1 = 2'
  46334. end
  46335. end
  46336. end
  46337. def supports_migrations? #:nodoc:
  46338. true
  46339. end
  46340. def rename_table(name, new_name)
  46341. execute "EXEC sp_rename '#{name}', '#{new_name}'"
  46342. end
  46343. def rename_column(table, column, new_column_name)
  46344. execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
  46345. end
  46346. def change_column(table_name, column_name, type, options = {}) #:nodoc:
  46347. sql_commands = ["ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"]
  46348. if options[:default]
  46349. remove_default_constraint(table_name, column_name)
  46350. sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
  46351. end
  46352. sql_commands.each { |c| execute(c) }
  46353. end
  46354. def remove_column(table_name, column_name)
  46355. remove_default_constraint(table_name, column_name)
  46356. execute "ALTER TABLE #{table_name} DROP #{column_name}"
  46357. end
  46358. def remove_default_constraint(table_name, column_name)
  46359. defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
  46360. defaults.each {|constraint|
  46361. execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
  46362. }
  46363. end
  46364. def remove_index(table_name, options = {})
  46365. execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
  46366. end
  46367. def add_column_options!(sql, options) #:nodoc:
  46368. sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
  46369. if check_null_for_column?(options[:column], sql)
  46370. sql << (options[:null] == false ? " NOT NULL" : " NULL")
  46371. end
  46372. sql
  46373. end
  46374. private
  46375. def check_null_for_column?(col, sql)
  46376. # Sybase columns are NOT NULL by default, so explicitly set NULL
  46377. # if :null option is omitted. Disallow NULLs for boolean.
  46378. type = col.nil? ? "" : col[:type]
  46379. # Ignore :null if a primary key
  46380. return false if type =~ /PRIMARY KEY/i
  46381. # Ignore :null if a :boolean or BIT column
  46382. if (sql =~ /\s+bit(\s+DEFAULT)?/i) || type == :boolean
  46383. # If no default clause found on a boolean column, add one.
  46384. sql << " DEFAULT 0" if $1.nil?
  46385. return false
  46386. end
  46387. true
  46388. end
  46389. # Return the last value of the identity global value.
  46390. def last_insert_id
  46391. @connection.sql("SELECT @@IDENTITY")
  46392. unless @connection.cmd_fail?
  46393. id = @connection.top_row_result.rows.first.first
  46394. if id
  46395. id = id.to_i
  46396. id = nil if id == 0
  46397. end
  46398. else
  46399. id = nil
  46400. end
  46401. id
  46402. end
  46403. def affected_rows(name = nil)
  46404. @connection.sql("SELECT @@ROWCOUNT")
  46405. unless @connection.cmd_fail?
  46406. count = @connection.top_row_result.rows.first.first
  46407. count = count.to_i if count
  46408. else
  46409. 0
  46410. end
  46411. end
  46412. def normal_select?
  46413. # If limit is not set at all, we can ignore offset;
  46414. # If limit *is* set but offset is zero, use normal select
  46415. # with simple SET ROWCOUNT. Thus, only use the temp table
  46416. # if limit is set and offset > 0.
  46417. has_limit = !@limit.nil?
  46418. has_offset = !@offset.nil? && @offset > 0
  46419. !has_limit || !has_offset
  46420. end
  46421. def zero_limit?
  46422. !@limit.nil? && @limit == 0
  46423. end
  46424. # Select limit number of rows starting at optional offset.
  46425. def select(sql, name = nil)
  46426. @connection.context.reset
  46427. log(sql, name) do
  46428. if normal_select?
  46429. # If limit is not explicitly set, return all results.
  46430. @logger.debug "Setting row count to (#{@limit || 'off'})" if @logger
  46431. # Run a normal select
  46432. @connection.set_rowcount(@limit || 0)
  46433. @connection.sql(sql)
  46434. else
  46435. # Select into a temp table and prune results
  46436. @logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
  46437. @connection.set_rowcount(@limit + (@offset || 0))
  46438. @connection.sql_norow(sql) # Select into temp table
  46439. @logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
  46440. @connection.set_rowcount(@offset || 0)
  46441. @connection.sql_norow("delete from #artemp") # Delete leading rows
  46442. @connection.set_rowcount(0)
  46443. @connection.sql("select * from #artemp") # Return the rest
  46444. end
  46445. end
  46446. rows = []
  46447. if @connection.context.failed? or @connection.cmd_fail?
  46448. raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
  46449. else
  46450. results = @connection.top_row_result
  46451. if results && results.rows.length > 0
  46452. fields = fixup_column_names(results.columns)
  46453. results.rows.each do |row|
  46454. hashed_row = {}
  46455. row.zip(fields) { |cell, column| hashed_row[column] = cell }
  46456. rows << hashed_row
  46457. end
  46458. end
  46459. end
  46460. @connection.sql_norow("drop table #artemp") if !normal_select?
  46461. @limit = @offset = nil
  46462. return rows
  46463. end
  46464. def enable_identity_insert(table_name, enable = true)
  46465. if has_identity_column(table_name)
  46466. "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
  46467. end
  46468. end
  46469. def get_table_name(sql)
  46470. if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
  46471. $1
  46472. elsif sql =~ /from\s+([^\(\s]+)\s*/i
  46473. $1
  46474. else
  46475. nil
  46476. end
  46477. end
  46478. def has_identity_column(table_name)
  46479. !get_identity_column(table_name).nil?
  46480. end
  46481. def get_identity_column(table_name)
  46482. @table_columns = {} unless @table_columns
  46483. @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
  46484. @table_columns[table_name].each do |col|
  46485. return col.name if col.identity
  46486. end
  46487. return nil
  46488. end
  46489. def query_contains_identity_column(sql, col)
  46490. sql =~ /\[#{col}\]/
  46491. end
  46492. # Remove trailing _ from names.
  46493. def fixup_column_names(columns)
  46494. columns.map { |column| column.sub(/_$/, '') }
  46495. end
  46496. def table_structure(table_name)
  46497. sql = <<SQLTEXT
  46498. SELECT col.name AS name, type.name AS type, col.prec, col.scale, col.length,
  46499. col.status, obj.sysstat2, def.text
  46500. FROM sysobjects obj, syscolumns col, systypes type, syscomments def
  46501. WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
  46502. AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
  46503. SQLTEXT
  46504. log(sql, "Get Column Info ") do
  46505. @connection.set_rowcount(0)
  46506. @connection.sql(sql)
  46507. end
  46508. if @connection.context.failed?
  46509. raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}"
  46510. elsif !@connection.cmd_fail?
  46511. columns = []
  46512. results = @connection.top_row_result
  46513. results.rows.each do |row|
  46514. name, type, prec, scale, length, status, sysstat2, default = row
  46515. type = normalize_type(type, prec, scale, length)
  46516. default_value = nil
  46517. name.sub!(/_$/o, '')
  46518. if default =~ /DEFAULT\s+(.+)/o
  46519. default_value = $1.strip
  46520. default_value = default_value[1...-1] if default_value =~ /^['"]/o
  46521. end
  46522. nullable = (status & 8) == 8
  46523. identity = status >= 128
  46524. primary = (sysstat2 & 8) == 8
  46525. columns << [name, default_value, type, nullable, identity, primary]
  46526. end
  46527. columns
  46528. else
  46529. nil
  46530. end
  46531. end
  46532. def normalize_type(field_type, prec, scale, length)
  46533. if field_type =~ /numeric/i and (scale.nil? or scale == 0)
  46534. type = 'int'
  46535. elsif field_type =~ /money/i
  46536. type = 'numeric'
  46537. else
  46538. type = field_type
  46539. end
  46540. size = ''
  46541. if prec
  46542. size = "(#{prec})"
  46543. elsif length
  46544. size = "(#{length})"
  46545. end
  46546. return type + size
  46547. end
  46548. def default_value(value)
  46549. end
  46550. end # class SybaseAdapter
  46551. class SybaseAdapterContext < SybSQLContext
  46552. DEADLOCK = 1205
  46553. attr_reader :message
  46554. def init(logger = nil)
  46555. @deadlocked = false
  46556. @failed = false
  46557. @logger = logger
  46558. @message = nil
  46559. end
  46560. def srvmsgCB(con, msg)
  46561. # Do not log change of context messages.
  46562. if msg['severity'] == 10 or msg['severity'] == 0
  46563. return true
  46564. end
  46565. if msg['msgnumber'] == DEADLOCK
  46566. @deadlocked = true
  46567. else
  46568. @logger.info "SQL Command failed!" if @logger
  46569. @failed = true
  46570. end
  46571. if @logger
  46572. @logger.error "** SybSQLContext Server Message: **"
  46573. @logger.error " Message number #{msg['msgnumber']} Severity #{msg['severity']} State #{msg['state']} Line #{msg['line']}"
  46574. @logger.error " Server #{msg['srvname']}"
  46575. @logger.error " Procedure #{msg['proc']}"
  46576. @logger.error " Message String: #{msg['text']}"
  46577. end
  46578. @message = msg['text']
  46579. true
  46580. end
  46581. def deadlocked?
  46582. @deadlocked
  46583. end
  46584. def failed?
  46585. @failed
  46586. end
  46587. def reset
  46588. @deadlocked = false
  46589. @failed = false
  46590. @message = nil
  46591. end
  46592. def cltmsgCB(con, msg)
  46593. return true unless ( msg.kind_of?(Hash) )
  46594. unless ( msg[ "severity" ] ) then
  46595. return true
  46596. end
  46597. if @logger
  46598. @logger.error "** SybSQLContext Client-Message: **"
  46599. @logger.error " Message number: LAYER=#{msg[ 'layer' ]} ORIGIN=#{msg[ 'origin' ]} SEVERITY=#{msg[ 'severity' ]} NUMBER=#{msg[ 'number' ]}"
  46600. @logger.error " Message String: #{msg['msgstring']}"
  46601. @logger.error " OS Error: #{msg['osstring']}"
  46602. @message = msg['msgstring']
  46603. end
  46604. @failed = true
  46605. # Not retry , CS_CV_RETRY_FAIL( probability TimeOut )
  46606. if( msg[ 'severity' ] == "RETRY_FAIL" ) then
  46607. @timeout_p = true
  46608. return false
  46609. end
  46610. return true
  46611. end
  46612. end # class SybaseAdapterContext
  46613. end # module ConnectionAdapters
  46614. end # module ActiveRecord
  46615. # Allow identity inserts for fixtures.
  46616. require "active_record/fixtures"
  46617. class Fixtures
  46618. alias :original_insert_fixtures :insert_fixtures
  46619. def insert_fixtures
  46620. values.each do |fixture|
  46621. allow_identity_inserts table_name, true
  46622. @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
  46623. allow_identity_inserts table_name, false
  46624. end
  46625. end
  46626. def allow_identity_inserts(table_name, enable)
  46627. @connection.execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" rescue nil
  46628. end
  46629. end
  46630. rescue LoadError => cannot_require_sybase
  46631. # Couldn't load sybase adapter
  46632. endmodule ActiveRecord
  46633. module Associations # :nodoc:
  46634. module ClassMethods
  46635. def deprecated_collection_count_method(collection_name)# :nodoc:
  46636. module_eval <<-"end_eval", __FILE__, __LINE__
  46637. def #{collection_name}_count(force_reload = false)
  46638. #{collection_name}.reload if force_reload
  46639. #{collection_name}.size
  46640. end
  46641. end_eval
  46642. end
  46643. def deprecated_add_association_relation(association_name)# :nodoc:
  46644. module_eval <<-"end_eval", __FILE__, __LINE__
  46645. def add_#{association_name}(*items)
  46646. #{association_name}.concat(items)
  46647. end
  46648. end_eval
  46649. end
  46650. def deprecated_remove_association_relation(association_name)# :nodoc:
  46651. module_eval <<-"end_eval", __FILE__, __LINE__
  46652. def remove_#{association_name}(*items)
  46653. #{association_name}.delete(items)
  46654. end
  46655. end_eval
  46656. end
  46657. def deprecated_has_collection_method(collection_name)# :nodoc:
  46658. module_eval <<-"end_eval", __FILE__, __LINE__
  46659. def has_#{collection_name}?(force_reload = false)
  46660. !#{collection_name}(force_reload).empty?
  46661. end
  46662. end_eval
  46663. end
  46664. def deprecated_find_in_collection_method(collection_name)# :nodoc:
  46665. module_eval <<-"end_eval", __FILE__, __LINE__
  46666. def find_in_#{collection_name}(association_id)
  46667. #{collection_name}.find(association_id)
  46668. end
  46669. end_eval
  46670. end
  46671. def deprecated_find_all_in_collection_method(collection_name)# :nodoc:
  46672. module_eval <<-"end_eval", __FILE__, __LINE__
  46673. def find_all_in_#{collection_name}(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
  46674. #{collection_name}.find_all(runtime_conditions, orderings, limit, joins)
  46675. end
  46676. end_eval
  46677. end
  46678. def deprecated_collection_create_method(collection_name)# :nodoc:
  46679. module_eval <<-"end_eval", __FILE__, __LINE__
  46680. def create_in_#{collection_name}(attributes = {})
  46681. #{collection_name}.create(attributes)
  46682. end
  46683. end_eval
  46684. end
  46685. def deprecated_collection_build_method(collection_name)# :nodoc:
  46686. module_eval <<-"end_eval", __FILE__, __LINE__
  46687. def build_to_#{collection_name}(attributes = {})
  46688. #{collection_name}.build(attributes)
  46689. end
  46690. end_eval
  46691. end
  46692. def deprecated_association_comparison_method(association_name, association_class_name) # :nodoc:
  46693. module_eval <<-"end_eval", __FILE__, __LINE__
  46694. def #{association_name}?(comparison_object, force_reload = false)
  46695. if comparison_object.kind_of?(#{association_class_name})
  46696. #{association_name}(force_reload) == comparison_object
  46697. else
  46698. raise "Comparison object is a #{association_class_name}, should have been \#{comparison_object.class.name}"
  46699. end
  46700. end
  46701. end_eval
  46702. end
  46703. def deprecated_has_association_method(association_name) # :nodoc:
  46704. module_eval <<-"end_eval", __FILE__, __LINE__
  46705. def has_#{association_name}?(force_reload = false)
  46706. !#{association_name}(force_reload).nil?
  46707. end
  46708. end_eval
  46709. end
  46710. end
  46711. end
  46712. end
  46713. module ActiveRecord
  46714. class Base
  46715. class << self
  46716. # This method is deprecated in favor of find with the :conditions option.
  46717. #
  46718. # Works like find, but the record matching +id+ must also meet the +conditions+.
  46719. # +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
  46720. # Example:
  46721. # Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
  46722. def find_on_conditions(ids, conditions) # :nodoc:
  46723. find(ids, :conditions => conditions)
  46724. end
  46725. # This method is deprecated in favor of find(:first, options).
  46726. #
  46727. # Returns the object for the first record responding to the conditions in +conditions+,
  46728. # such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
  46729. # be used to create the object. In such cases, it might be beneficial to also specify
  46730. # +orderings+, like "income DESC, name", to control exactly which record is to be used. Example:
  46731. # Employee.find_first "income > 50000", "income DESC, name"
  46732. def find_first(conditions = nil, orderings = nil, joins = nil) # :nodoc:
  46733. find(:first, :conditions => conditions, :order => orderings, :joins => joins)
  46734. end
  46735. # This method is deprecated in favor of find(:all, options).
  46736. #
  46737. # Returns an array of all the objects that could be instantiated from the associated
  46738. # table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
  46739. # such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
  46740. # such as by "last_name, first_name DESC". A maximum of returned objects and their offset can be specified in
  46741. # +limit+ with either just a single integer as the limit or as an array with the first element as the limit,
  46742. # the second as the offset. Examples:
  46743. # Project.find_all "category = 'accounts'", "last_accessed DESC", 15
  46744. # Project.find_all ["category = ?", category_name], "created ASC", [15, 20]
  46745. def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil) # :nodoc:
  46746. limit, offset = limit.is_a?(Array) ? limit : [ limit, nil ]
  46747. find(:all, :conditions => conditions, :order => orderings, :joins => joins, :limit => limit, :offset => offset)
  46748. end
  46749. end
  46750. end
  46751. endrequire 'erb'
  46752. require 'yaml'
  46753. require 'csv'
  46754. module YAML #:nodoc:
  46755. class Omap #:nodoc:
  46756. def keys; map { |k, v| k } end
  46757. def values; map { |k, v| v } end
  46758. end
  46759. end
  46760. class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
  46761. end
  46762. # Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
  46763. #
  46764. # 1. YAML fixtures
  46765. # 2. CSV fixtures
  46766. # 3. Single-file fixtures
  46767. #
  46768. # = YAML fixtures
  46769. #
  46770. # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
  46771. # in a non-verbose, humanly-readable format. It ships with Ruby 1.8.1+.
  46772. #
  46773. # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
  46774. # by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
  46775. # put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
  46776. # "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
  46777. #
  46778. # rubyonrails:
  46779. # id: 1
  46780. # name: Ruby on Rails
  46781. # url: http://www.rubyonrails.org
  46782. #
  46783. # google:
  46784. # id: 2
  46785. # name: Google
  46786. # url: http://www.google.com
  46787. #
  46788. # This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
  46789. # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
  46790. # pleasure.
  46791. #
  46792. # Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
  46793. # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
  46794. # This is commonly needed for tree structures. Example:
  46795. #
  46796. # --- !omap
  46797. # - parent:
  46798. # id: 1
  46799. # parent_id: NULL
  46800. # title: Parent
  46801. # - child:
  46802. # id: 2
  46803. # parent_id: 1
  46804. # title: Child
  46805. #
  46806. # = CSV fixtures
  46807. #
  46808. # Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
  46809. # in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
  46810. #
  46811. # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
  46812. # humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
  46813. # of the actual data (1 per line). Here's an example:
  46814. #
  46815. # id, name, url
  46816. # 1, Ruby On Rails, http://www.rubyonrails.org
  46817. # 2, Google, http://www.google.com
  46818. #
  46819. # Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
  46820. # need to use a double quote character, you must escape it with another double quote.
  46821. #
  46822. # Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
  46823. # fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
  46824. # number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
  46825. # "web_site_2".
  46826. #
  46827. # Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
  46828. # have existing data somewhere already.
  46829. #
  46830. # = Single-file fixtures
  46831. #
  46832. # This type of fixtures was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
  46833. # Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
  46834. # appointed by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
  46835. # put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ for the WebSite
  46836. # model).
  46837. #
  46838. # Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
  46839. # extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. Here's what the
  46840. # above example might look like:
  46841. #
  46842. # web_sites/google
  46843. # web_sites/yahoo.txt
  46844. # web_sites/ruby-on-rails
  46845. #
  46846. # The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
  46847. # of "name => value". Here's an example of the ruby-on-rails fixture above:
  46848. #
  46849. # id => 1
  46850. # name => Ruby on Rails
  46851. # url => http://www.rubyonrails.org
  46852. #
  46853. # = Using Fixtures
  46854. #
  46855. # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
  46856. # fixtures, but first let's take a look at a sample unit test found:
  46857. #
  46858. # require 'web_site'
  46859. #
  46860. # class WebSiteTest < Test::Unit::TestCase
  46861. # def test_web_site_count
  46862. # assert_equal 2, WebSite.count
  46863. # end
  46864. # end
  46865. #
  46866. # As it stands, unless we pre-load the web_site table in our database with two records, this test will fail. Here's the
  46867. # easiest way to add fixtures to the database:
  46868. #
  46869. # ...
  46870. # class WebSiteTest < Test::Unit::TestCase
  46871. # fixtures :web_sites # add more by separating the symbols with commas
  46872. # ...
  46873. #
  46874. # By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger
  46875. # the testing environment to automatically load the appropriate fixtures into the database before each test.
  46876. # To ensure consistent data, the environment deletes the fixtures before running the load.
  46877. #
  46878. # In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
  46879. # of the test case. It is named after the symbol... so, in our example, there would be a hash available called
  46880. # @web_sites. This is where the "fixture name" comes into play.
  46881. #
  46882. # On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
  46883. # So for the YAML fixtures, we'd get @rubyonrails and @google, which could be interrogated using regular Active Record semantics:
  46884. #
  46885. # # test if the object created from the fixture data has the same attributes as the data itself
  46886. # def test_find
  46887. # assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
  46888. # end
  46889. #
  46890. # As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
  46891. # "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
  46892. # from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
  46893. # fixtures available as instance variables @web_site_1 and @web_site_2.
  46894. #
  46895. # If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
  46896. #
  46897. # - to completely disable instantiated fixtures:
  46898. # self.use_instantiated_fixtures = false
  46899. #
  46900. # - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
  46901. # self.use_instantiated_fixtures = :no_instances
  46902. #
  46903. # Even if auto-instantiated fixtures are disabled, you can still access them
  46904. # by name via special dynamic methods. Each method has the same name as the
  46905. # model, and accepts the name of the fixture to instantiate:
  46906. #
  46907. # fixtures :web_sites
  46908. #
  46909. # def test_find
  46910. # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
  46911. # end
  46912. #
  46913. # = Dynamic fixtures with ERb
  46914. #
  46915. # Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
  46916. # mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
  46917. #
  46918. # <% for i in 1..1000 %>
  46919. # fix_<%= i %>:
  46920. # id: <%= i %>
  46921. # name: guy_<%= 1 %>
  46922. # <% end %>
  46923. #
  46924. # This will create 1000 very simple YAML fixtures.
  46925. #
  46926. # Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
  46927. # This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
  46928. # sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
  46929. # is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
  46930. #
  46931. # = Transactional fixtures
  46932. #
  46933. # TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
  46934. # They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
  46935. #
  46936. # class FooTest < Test::Unit::TestCase
  46937. # self.use_transactional_fixtures = true
  46938. # self.use_instantiated_fixtures = false
  46939. #
  46940. # fixtures :foos
  46941. #
  46942. # def test_godzilla
  46943. # assert !Foo.find(:all).empty?
  46944. # Foo.destroy_all
  46945. # assert Foo.find(:all).empty?
  46946. # end
  46947. #
  46948. # def test_godzilla_aftermath
  46949. # assert !Foo.find(:all).empty?
  46950. # end
  46951. # end
  46952. #
  46953. # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
  46954. # then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
  46955. #
  46956. # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
  46957. # access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
  46958. #
  46959. # When *not* to use transactional fixtures:
  46960. # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
  46961. # particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
  46962. # the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
  46963. # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
  46964. # Use InnoDB, MaxDB, or NDB instead.
  46965. class Fixtures < YAML::Omap
  46966. DEFAULT_FILTER_RE = /\.ya?ml$/
  46967. def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
  46968. object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
  46969. if load_instances
  46970. ActiveRecord::Base.silence do
  46971. fixtures.each do |name, fixture|
  46972. begin
  46973. object.instance_variable_set "@#{name}", fixture.find
  46974. rescue FixtureClassNotFound
  46975. nil
  46976. end
  46977. end
  46978. end
  46979. end
  46980. end
  46981. def self.instantiate_all_loaded_fixtures(object, load_instances=true)
  46982. all_loaded_fixtures.each do |table_name, fixtures|
  46983. Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
  46984. end
  46985. end
  46986. cattr_accessor :all_loaded_fixtures
  46987. self.all_loaded_fixtures = {}
  46988. def self.create_fixtures(fixtures_directory, table_names, class_names = {})
  46989. table_names = [table_names].flatten.map { |n| n.to_s }
  46990. connection = block_given? ? yield : ActiveRecord::Base.connection
  46991. ActiveRecord::Base.silence do
  46992. fixtures_map = {}
  46993. fixtures = table_names.map do |table_name|
  46994. fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
  46995. end
  46996. all_loaded_fixtures.merge! fixtures_map
  46997. connection.transaction do
  46998. fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
  46999. fixtures.each { |fixture| fixture.insert_fixtures }
  47000. # Cap primary key sequences to max(pk).
  47001. if connection.respond_to?(:reset_pk_sequence!)
  47002. table_names.each do |table_name|
  47003. connection.reset_pk_sequence!(table_name)
  47004. end
  47005. end
  47006. end
  47007. return fixtures.size > 1 ? fixtures : fixtures.first
  47008. end
  47009. end
  47010. attr_reader :table_name
  47011. def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
  47012. @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
  47013. @class_name = class_name ||
  47014. (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
  47015. @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
  47016. read_fixture_files
  47017. end
  47018. def delete_existing_fixtures
  47019. @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
  47020. end
  47021. def insert_fixtures
  47022. values.each do |fixture|
  47023. @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
  47024. end
  47025. end
  47026. private
  47027. def read_fixture_files
  47028. if File.file?(yaml_file_path)
  47029. # YAML fixtures
  47030. begin
  47031. yaml_string = ""
  47032. Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
  47033. yaml_string << IO.read(subfixture_path)
  47034. end
  47035. yaml_string << IO.read(yaml_file_path)
  47036. if yaml = YAML::load(erb_render(yaml_string))
  47037. yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
  47038. yaml.each do |name, data|
  47039. self[name] = Fixture.new(data, @class_name)
  47040. end
  47041. end
  47042. rescue Exception=>boom
  47043. raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
  47044. end
  47045. elsif File.file?(csv_file_path)
  47046. # CSV fixtures
  47047. reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
  47048. header = reader.shift
  47049. i = 0
  47050. reader.each do |row|
  47051. data = {}
  47052. row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
  47053. self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name)
  47054. end
  47055. elsif File.file?(deprecated_yaml_file_path)
  47056. raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
  47057. else
  47058. # Standard fixtures
  47059. Dir.entries(@fixture_path).each do |file|
  47060. path = File.join(@fixture_path, file)
  47061. if File.file?(path) and file !~ @file_filter
  47062. self[file] = Fixture.new(path, @class_name)
  47063. end
  47064. end
  47065. end
  47066. end
  47067. def yaml_file_path
  47068. "#{@fixture_path}.yml"
  47069. end
  47070. def deprecated_yaml_file_path
  47071. "#{@fixture_path}.yaml"
  47072. end
  47073. def csv_file_path
  47074. @fixture_path + ".csv"
  47075. end
  47076. def yaml_fixtures_key(path)
  47077. File.basename(@fixture_path).split(".").first
  47078. end
  47079. def erb_render(fixture_content)
  47080. ERB.new(fixture_content).result
  47081. end
  47082. end
  47083. class Fixture #:nodoc:
  47084. include Enumerable
  47085. class FixtureError < StandardError#:nodoc:
  47086. end
  47087. class FormatError < FixtureError#:nodoc:
  47088. end
  47089. def initialize(fixture, class_name)
  47090. case fixture
  47091. when Hash, YAML::Omap
  47092. @fixture = fixture
  47093. when String
  47094. @fixture = read_fixture_file(fixture)
  47095. else
  47096. raise ArgumentError, "Bad fixture argument #{fixture.inspect}"
  47097. end
  47098. @class_name = class_name
  47099. end
  47100. def each
  47101. @fixture.each { |item| yield item }
  47102. end
  47103. def [](key)
  47104. @fixture[key]
  47105. end
  47106. def to_hash
  47107. @fixture
  47108. end
  47109. def key_list
  47110. columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
  47111. columns.join(", ")
  47112. end
  47113. def value_list
  47114. @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
  47115. end
  47116. def find
  47117. klass = @class_name.is_a?(Class) ? @class_name : Object.const_get(@class_name) rescue nil
  47118. if klass
  47119. klass.find(self[klass.primary_key])
  47120. else
  47121. raise FixtureClassNotFound, "The class #{@class_name.inspect} was not found."
  47122. end
  47123. end
  47124. private
  47125. def read_fixture_file(fixture_file_path)
  47126. IO.readlines(fixture_file_path).inject({}) do |fixture, line|
  47127. # Mercifully skip empty lines.
  47128. next if line =~ /^\s*$/
  47129. # Use the same regular expression for attributes as Active Record.
  47130. unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
  47131. raise FormatError, "#{fixture_file_path}: fixture format error at '#{line}'. Expecting 'key => value'."
  47132. end
  47133. key, value = md.captures
  47134. # Disallow duplicate keys to catch typos.
  47135. raise FormatError, "#{fixture_file_path}: duplicate '#{key}' in fixture." if fixture[key]
  47136. fixture[key] = value.strip
  47137. fixture
  47138. end
  47139. end
  47140. end
  47141. module Test #:nodoc:
  47142. module Unit #:nodoc:
  47143. class TestCase #:nodoc:
  47144. cattr_accessor :fixture_path
  47145. class_inheritable_accessor :fixture_table_names
  47146. class_inheritable_accessor :fixture_class_names
  47147. class_inheritable_accessor :use_transactional_fixtures
  47148. class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances
  47149. class_inheritable_accessor :pre_loaded_fixtures
  47150. self.fixture_table_names = []
  47151. self.use_transactional_fixtures = false
  47152. self.use_instantiated_fixtures = true
  47153. self.pre_loaded_fixtures = false
  47154. self.fixture_class_names = {}
  47155. @@already_loaded_fixtures = {}
  47156. self.fixture_class_names = {}
  47157. def self.set_fixture_class(class_names = {})
  47158. self.fixture_class_names = self.fixture_class_names.merge(class_names)
  47159. end
  47160. def self.fixtures(*table_names)
  47161. table_names = table_names.flatten.map { |n| n.to_s }
  47162. self.fixture_table_names |= table_names
  47163. require_fixture_classes(table_names)
  47164. setup_fixture_accessors(table_names)
  47165. end
  47166. def self.require_fixture_classes(table_names=nil)
  47167. (table_names || fixture_table_names).each do |table_name|
  47168. file_name = table_name.to_s
  47169. file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
  47170. begin
  47171. require file_name
  47172. rescue LoadError
  47173. # Let's hope the developer has included it himself
  47174. end
  47175. end
  47176. end
  47177. def self.setup_fixture_accessors(table_names=nil)
  47178. (table_names || fixture_table_names).each do |table_name|
  47179. table_name = table_name.to_s.tr('.','_')
  47180. define_method(table_name) do |fixture, *optionals|
  47181. force_reload = optionals.shift
  47182. @fixture_cache[table_name] ||= Hash.new
  47183. @fixture_cache[table_name][fixture] = nil if force_reload
  47184. if @loaded_fixtures[table_name][fixture.to_s]
  47185. @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
  47186. else
  47187. raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
  47188. end
  47189. end
  47190. end
  47191. end
  47192. def self.uses_transaction(*methods)
  47193. @uses_transaction ||= []
  47194. @uses_transaction.concat methods.map { |m| m.to_s }
  47195. end
  47196. def self.uses_transaction?(method)
  47197. @uses_transaction && @uses_transaction.include?(method.to_s)
  47198. end
  47199. def use_transactional_fixtures?
  47200. use_transactional_fixtures &&
  47201. !self.class.uses_transaction?(method_name)
  47202. end
  47203. def setup_with_fixtures
  47204. if pre_loaded_fixtures && !use_transactional_fixtures
  47205. raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
  47206. end
  47207. @fixture_cache = Hash.new
  47208. # Load fixtures once and begin transaction.
  47209. if use_transactional_fixtures?
  47210. if @@already_loaded_fixtures[self.class]
  47211. @loaded_fixtures = @@already_loaded_fixtures[self.class]
  47212. else
  47213. load_fixtures
  47214. @@already_loaded_fixtures[self.class] = @loaded_fixtures
  47215. end
  47216. ActiveRecord::Base.lock_mutex
  47217. ActiveRecord::Base.connection.begin_db_transaction
  47218. # Load fixtures for every test.
  47219. else
  47220. @@already_loaded_fixtures[self.class] = nil
  47221. load_fixtures
  47222. end
  47223. # Instantiate fixtures for every test if requested.
  47224. instantiate_fixtures if use_instantiated_fixtures
  47225. end
  47226. alias_method :setup, :setup_with_fixtures
  47227. def teardown_with_fixtures
  47228. # Rollback changes.
  47229. if use_transactional_fixtures?
  47230. ActiveRecord::Base.connection.rollback_db_transaction
  47231. ActiveRecord::Base.unlock_mutex
  47232. end
  47233. ActiveRecord::Base.verify_active_connections!
  47234. end
  47235. alias_method :teardown, :teardown_with_fixtures
  47236. def self.method_added(method)
  47237. case method.to_s
  47238. when 'setup'
  47239. unless method_defined?(:setup_without_fixtures)
  47240. alias_method :setup_without_fixtures, :setup
  47241. define_method(:setup) do
  47242. setup_with_fixtures
  47243. setup_without_fixtures
  47244. end
  47245. end
  47246. when 'teardown'
  47247. unless method_defined?(:teardown_without_fixtures)
  47248. alias_method :teardown_without_fixtures, :teardown
  47249. define_method(:teardown) do
  47250. teardown_without_fixtures
  47251. teardown_with_fixtures
  47252. end
  47253. end
  47254. end
  47255. end
  47256. private
  47257. def load_fixtures
  47258. @loaded_fixtures = {}
  47259. fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
  47260. unless fixtures.nil?
  47261. if fixtures.instance_of?(Fixtures)
  47262. @loaded_fixtures[fixtures.table_name] = fixtures
  47263. else
  47264. fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
  47265. end
  47266. end
  47267. end
  47268. # for pre_loaded_fixtures, only require the classes once. huge speed improvement
  47269. @@required_fixture_classes = false
  47270. def instantiate_fixtures
  47271. if pre_loaded_fixtures
  47272. raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
  47273. unless @@required_fixture_classes
  47274. self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
  47275. @@required_fixture_classes = true
  47276. end
  47277. Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
  47278. else
  47279. raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
  47280. @loaded_fixtures.each do |table_name, fixtures|
  47281. Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
  47282. end
  47283. end
  47284. end
  47285. def load_instances?
  47286. use_instantiated_fixtures != :no_instances
  47287. end
  47288. end
  47289. end
  47290. end
  47291. module ActiveRecord
  47292. # Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
  47293. # record increments the lock_version column and the locking facilities ensure that records instantiated twice
  47294. # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
  47295. #
  47296. # p1 = Person.find(1)
  47297. # p2 = Person.find(1)
  47298. #
  47299. # p1.first_name = "Michael"
  47300. # p1.save
  47301. #
  47302. # p2.first_name = "should fail"
  47303. # p2.save # Raises a ActiveRecord::StaleObjectError
  47304. #
  47305. # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
  47306. # or otherwise apply the business logic needed to resolve the conflict.
  47307. #
  47308. # You must ensure that your database schema defaults the lock_version column to 0.
  47309. #
  47310. # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
  47311. # To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
  47312. # This method uses the same syntax as <tt>set_table_name</tt>
  47313. module Locking
  47314. def self.append_features(base) #:nodoc:
  47315. super
  47316. base.class_eval do
  47317. alias_method :update_without_lock, :update
  47318. alias_method :update, :update_with_lock
  47319. end
  47320. end
  47321. def update_with_lock #:nodoc:
  47322. return update_without_lock unless locking_enabled?
  47323. lock_col = self.class.locking_column
  47324. previous_value = send(lock_col)
  47325. send(lock_col + '=', previous_value + 1)
  47326. affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
  47327. UPDATE #{self.class.table_name}
  47328. SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
  47329. WHERE #{self.class.primary_key} = #{quote(id)}
  47330. AND #{lock_col} = #{quote(previous_value)}
  47331. end_sql
  47332. unless affected_rows == 1
  47333. raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
  47334. end
  47335. return true
  47336. end
  47337. end
  47338. class Base
  47339. @@lock_optimistically = true
  47340. cattr_accessor :lock_optimistically
  47341. def locking_enabled? #:nodoc:
  47342. lock_optimistically && respond_to?(self.class.locking_column)
  47343. end
  47344. class << self
  47345. def set_locking_column(value = nil, &block)
  47346. define_attr_method :locking_column, value, &block
  47347. end
  47348. def locking_column #:nodoc:
  47349. reset_locking_column
  47350. end
  47351. def reset_locking_column #:nodoc:
  47352. default = 'lock_version'
  47353. set_locking_column(default)
  47354. default
  47355. end
  47356. end
  47357. end
  47358. end
  47359. module ActiveRecord
  47360. class IrreversibleMigration < ActiveRecordError#:nodoc:
  47361. end
  47362. class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
  47363. def initialize(version)
  47364. super("Multiple migrations have the version number #{version}")
  47365. end
  47366. end
  47367. # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
  47368. # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
  47369. # push that change to other developers and to the production server. With migrations, you can describe the transformations
  47370. # in self-contained classes that can be checked into version control systems and executed against another database that
  47371. # might be one, two, or five versions behind.
  47372. #
  47373. # Example of a simple migration:
  47374. #
  47375. # class AddSsl < ActiveRecord::Migration
  47376. # def self.up
  47377. # add_column :accounts, :ssl_enabled, :boolean, :default => 1
  47378. # end
  47379. #
  47380. # def self.down
  47381. # remove_column :accounts, :ssl_enabled
  47382. # end
  47383. # end
  47384. #
  47385. # This migration will add a boolean flag to the accounts table and remove it again, if you're backing out of the migration.
  47386. # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
  47387. # or remove the migration. These methods can consist of both the migration specific methods, like add_column and remove_column,
  47388. # but may also contain regular Ruby code for generating data needed for the transformations.
  47389. #
  47390. # Example of a more complex migration that also needs to initialize data:
  47391. #
  47392. # class AddSystemSettings < ActiveRecord::Migration
  47393. # def self.up
  47394. # create_table :system_settings do |t|
  47395. # t.column :name, :string
  47396. # t.column :label, :string
  47397. # t.column :value, :text
  47398. # t.column :type, :string
  47399. # t.column :position, :integer
  47400. # end
  47401. #
  47402. # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
  47403. # end
  47404. #
  47405. # def self.down
  47406. # drop_table :system_settings
  47407. # end
  47408. # end
  47409. #
  47410. # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
  47411. # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
  47412. # in one block call.
  47413. #
  47414. # == Available transformations
  47415. #
  47416. # * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
  47417. # that can then add columns to it, following the same format as add_column. See example above. The options hash is for
  47418. # fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
  47419. # * <tt>drop_table(name)</tt>: Drops the table called +name+.
  47420. # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
  47421. # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
  47422. # named +column_name+ specified to be one of the following types:
  47423. # :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified
  47424. # by passing an +options+ hash like { :default => 11 }.
  47425. # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
  47426. # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
  47427. # parameters as add_column.
  47428. # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
  47429. # * <tt>add_index(table_name, column_names, index_type, index_name)</tt>: Add a new index with the name of the column, or +index_name+ (if specified) on the column(s). Specify an optional +index_type+ (e.g. UNIQUE).
  47430. # * <tt>remove_index(table_name, index_name)</tt>: Remove the index specified by +index_name+.
  47431. #
  47432. # == Irreversible transformations
  47433. #
  47434. # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
  47435. # an <tt>IrreversibleMigration</tt> exception in their +down+ method.
  47436. #
  47437. # == Running migrations from within Rails
  47438. #
  47439. # The Rails package has several tools to help create and apply migrations.
  47440. #
  47441. # To generate a new migration, use <tt>script/generate migration MyNewMigration</tt>
  47442. # where MyNewMigration is the name of your migration. The generator will
  47443. # create a file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
  47444. # directory, where <tt>nnn</tt> is the next largest migration number.
  47445. # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
  47446. # n MyNewMigration.
  47447. #
  47448. # To run migrations against the currently configured database, use
  47449. # <tt>rake migrate</tt>. This will update the database by running all of the
  47450. # pending migrations, creating the <tt>schema_info</tt> table if missing.
  47451. #
  47452. # To roll the database back to a previous migration version, use
  47453. # <tt>rake migrate VERSION=X</tt> where <tt>X</tt> is the version to which
  47454. # you wish to downgrade. If any of the migrations throw an
  47455. # <tt>IrreversibleMigration</tt> exception, that step will fail and you'll
  47456. # have some manual work to do.
  47457. #
  47458. # == Database support
  47459. #
  47460. # Migrations are currently supported in MySQL, PostgreSQL, SQLite,
  47461. # SQL Server, Sybase, and Oracle (all supported databases except DB2).
  47462. #
  47463. # == More examples
  47464. #
  47465. # Not all migrations change the schema. Some just fix the data:
  47466. #
  47467. # class RemoveEmptyTags < ActiveRecord::Migration
  47468. # def self.up
  47469. # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
  47470. # end
  47471. #
  47472. # def self.down
  47473. # # not much we can do to restore deleted data
  47474. # raise IrreversibleMigration
  47475. # end
  47476. # end
  47477. #
  47478. # Others remove columns when they migrate up instead of down:
  47479. #
  47480. # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
  47481. # def self.up
  47482. # remove_column :items, :incomplete_items_count
  47483. # remove_column :items, :completed_items_count
  47484. # end
  47485. #
  47486. # def self.down
  47487. # add_column :items, :incomplete_items_count
  47488. # add_column :items, :completed_items_count
  47489. # end
  47490. # end
  47491. #
  47492. # And sometimes you need to do something in SQL not abstracted directly by migrations:
  47493. #
  47494. # class MakeJoinUnique < ActiveRecord::Migration
  47495. # def self.up
  47496. # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
  47497. # end
  47498. #
  47499. # def self.down
  47500. # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
  47501. # end
  47502. # end
  47503. #
  47504. # == Using a model after changing its table
  47505. #
  47506. # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
  47507. # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
  47508. # after the new column was added. Example:
  47509. #
  47510. # class AddPeopleSalary < ActiveRecord::Migration
  47511. # def self.up
  47512. # add_column :people, :salary, :integer
  47513. # Person.reset_column_information
  47514. # Person.find(:all).each do |p|
  47515. # p.salary = SalaryCalculator.compute(p)
  47516. # end
  47517. # end
  47518. # end
  47519. #
  47520. # == Controlling verbosity
  47521. #
  47522. # By default, migrations will describe the actions they are taking, writing
  47523. # them to the console as they happen, along with benchmarks describing how
  47524. # long each step took.
  47525. #
  47526. # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
  47527. #
  47528. # You can also insert your own messages and benchmarks by using the #say_with_time
  47529. # method:
  47530. #
  47531. # def self.up
  47532. # ...
  47533. # say_with_time "Updating salaries..." do
  47534. # Person.find(:all).each do |p|
  47535. # p.salary = SalaryCalculator.compute(p)
  47536. # end
  47537. # end
  47538. # ...
  47539. # end
  47540. #
  47541. # The phrase "Updating salaries..." would then be printed, along with the
  47542. # benchmark for the block when the block completes.
  47543. class Migration
  47544. @@verbose = true
  47545. cattr_accessor :verbose
  47546. class << self
  47547. def up_using_benchmarks #:nodoc:
  47548. migrate(:up)
  47549. end
  47550. def down_using_benchmarks #:nodoc:
  47551. migrate(:down)
  47552. end
  47553. # Execute this migration in the named direction
  47554. def migrate(direction)
  47555. return unless respond_to?(direction)
  47556. case direction
  47557. when :up then announce "migrating"
  47558. when :down then announce "reverting"
  47559. end
  47560. result = nil
  47561. time = Benchmark.measure { result = send("real_#{direction}") }
  47562. case direction
  47563. when :up then announce "migrated (%.4fs)" % time.real; write
  47564. when :down then announce "reverted (%.4fs)" % time.real; write
  47565. end
  47566. result
  47567. end
  47568. # Because the method added may do an alias_method, it can be invoked
  47569. # recursively. We use @ignore_new_methods as a guard to indicate whether
  47570. # it is safe for the call to proceed.
  47571. def singleton_method_added(sym) #:nodoc:
  47572. return if @ignore_new_methods
  47573. begin
  47574. @ignore_new_methods = true
  47575. case sym
  47576. when :up, :down
  47577. klass = (class << self; self; end)
  47578. klass.send(:alias_method, "real_#{sym}", sym)
  47579. klass.send(:alias_method, sym, "#{sym}_using_benchmarks")
  47580. end
  47581. ensure
  47582. @ignore_new_methods = false
  47583. end
  47584. end
  47585. def write(text="")
  47586. puts(text) if verbose
  47587. end
  47588. def announce(message)
  47589. text = "#{name}: #{message}"
  47590. length = [0, 75 - text.length].max
  47591. write "== %s %s" % [text, "=" * length]
  47592. end
  47593. def say(message, subitem=false)
  47594. write "#{subitem ? " ->" : "--"} #{message}"
  47595. end
  47596. def say_with_time(message)
  47597. say(message)
  47598. result = nil
  47599. time = Benchmark.measure { result = yield }
  47600. say "%.4fs" % time.real, :subitem
  47601. result
  47602. end
  47603. def suppress_messages
  47604. save = verbose
  47605. self.verbose = false
  47606. yield
  47607. ensure
  47608. self.verbose = save
  47609. end
  47610. def method_missing(method, *arguments, &block)
  47611. say_with_time "#{method}(#{arguments.map { |a| a.inspect }.join(", ")})" do
  47612. arguments[0] = Migrator.proper_table_name(arguments.first) unless arguments.empty? || method == :execute
  47613. ActiveRecord::Base.connection.send(method, *arguments, &block)
  47614. end
  47615. end
  47616. end
  47617. end
  47618. class Migrator#:nodoc:
  47619. class << self
  47620. def migrate(migrations_path, target_version = nil)
  47621. Base.connection.initialize_schema_information
  47622. case
  47623. when target_version.nil?, current_version < target_version
  47624. up(migrations_path, target_version)
  47625. when current_version > target_version
  47626. down(migrations_path, target_version)
  47627. when current_version == target_version
  47628. return # You're on the right version
  47629. end
  47630. end
  47631. def up(migrations_path, target_version = nil)
  47632. self.new(:up, migrations_path, target_version).migrate
  47633. end
  47634. def down(migrations_path, target_version = nil)
  47635. self.new(:down, migrations_path, target_version).migrate
  47636. end
  47637. def schema_info_table_name
  47638. Base.table_name_prefix + "schema_info" + Base.table_name_suffix
  47639. end
  47640. def current_version
  47641. (Base.connection.select_one("SELECT version FROM #{schema_info_table_name}") || {"version" => 0})["version"].to_i
  47642. end
  47643. def proper_table_name(name)
  47644. # Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
  47645. name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
  47646. end
  47647. end
  47648. def initialize(direction, migrations_path, target_version = nil)
  47649. raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
  47650. @direction, @migrations_path, @target_version = direction, migrations_path, target_version
  47651. Base.connection.initialize_schema_information
  47652. end
  47653. def current_version
  47654. self.class.current_version
  47655. end
  47656. def migrate
  47657. migration_classes.each do |(version, migration_class)|
  47658. Base.logger.info("Reached target version: #{@target_version}") and break if reached_target_version?(version)
  47659. next if irrelevant_migration?(version)
  47660. Base.logger.info "Migrating to #{migration_class} (#{version})"
  47661. migration_class.migrate(@direction)
  47662. set_schema_version(version)
  47663. end
  47664. end
  47665. private
  47666. def migration_classes
  47667. migrations = migration_files.inject([]) do |migrations, migration_file|
  47668. load(migration_file)
  47669. version, name = migration_version_and_name(migration_file)
  47670. assert_unique_migration_version(migrations, version.to_i)
  47671. migrations << [ version.to_i, migration_class(name) ]
  47672. end
  47673. down? ? migrations.sort.reverse : migrations.sort
  47674. end
  47675. def assert_unique_migration_version(migrations, version)
  47676. if !migrations.empty? && migrations.transpose.first.include?(version)
  47677. raise DuplicateMigrationVersionError.new(version)
  47678. end
  47679. end
  47680. def migration_files
  47681. files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
  47682. migration_version_and_name(f).first.to_i
  47683. end
  47684. down? ? files.reverse : files
  47685. end
  47686. def migration_class(migration_name)
  47687. migration_name.camelize.constantize
  47688. end
  47689. def migration_version_and_name(migration_file)
  47690. return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
  47691. end
  47692. def set_schema_version(version)
  47693. Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
  47694. end
  47695. def up?
  47696. @direction == :up
  47697. end
  47698. def down?
  47699. @direction == :down
  47700. end
  47701. def reached_target_version?(version)
  47702. (up? && version.to_i - 1 == @target_version) || (down? && version.to_i == @target_version)
  47703. end
  47704. def irrelevant_migration?(version)
  47705. (up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
  47706. end
  47707. end
  47708. end
  47709. require 'singleton'
  47710. module ActiveRecord
  47711. module Observing # :nodoc:
  47712. def self.append_features(base)
  47713. super
  47714. base.extend(ClassMethods)
  47715. end
  47716. module ClassMethods
  47717. # Activates the observers assigned. Examples:
  47718. #
  47719. # # Calls PersonObserver.instance
  47720. # ActiveRecord::Base.observers = :person_observer
  47721. #
  47722. # # Calls Cacher.instance and GarbageCollector.instance
  47723. # ActiveRecord::Base.observers = :cacher, :garbage_collector
  47724. #
  47725. # # Same as above, just using explicit class references
  47726. # ActiveRecord::Base.observers = Cacher, GarbageCollector
  47727. def observers=(*observers)
  47728. observers = [ observers ].flatten.each do |observer|
  47729. observer.is_a?(Symbol) ?
  47730. observer.to_s.camelize.constantize.instance :
  47731. observer.instance
  47732. end
  47733. end
  47734. end
  47735. end
  47736. # Observer classes respond to lifecycle callbacks to implement trigger-like
  47737. # behavior outside the original class. This is a great way to reduce the
  47738. # clutter that normally comes when the model class is burdened with
  47739. # functionality that doesn't pertain to the core responsibility of the
  47740. # class. Example:
  47741. #
  47742. # class CommentObserver < ActiveRecord::Observer
  47743. # def after_save(comment)
  47744. # Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
  47745. # end
  47746. # end
  47747. #
  47748. # This Observer sends an email when a Comment#save is finished.
  47749. #
  47750. # class ContactObserver < ActiveRecord::Observer
  47751. # def after_create(contact)
  47752. # contact.logger.info('New contact added!')
  47753. # end
  47754. #
  47755. # def after_destroy(contact)
  47756. # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
  47757. # end
  47758. # end
  47759. #
  47760. # This Observer uses logger to log when specific callbacks are triggered.
  47761. #
  47762. # == Observing a class that can't be inferred
  47763. #
  47764. # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
  47765. # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
  47766. # differently than the class you're interested in observing, you can use the Observer.observe class method:
  47767. #
  47768. # class AuditObserver < ActiveRecord::Observer
  47769. # observe Account
  47770. #
  47771. # def after_update(account)
  47772. # AuditTrail.new(account, "UPDATED")
  47773. # end
  47774. # end
  47775. #
  47776. # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
  47777. #
  47778. # class AuditObserver < ActiveRecord::Observer
  47779. # observe Account, Balance
  47780. #
  47781. # def after_update(record)
  47782. # AuditTrail.new(record, "UPDATED")
  47783. # end
  47784. # end
  47785. #
  47786. # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
  47787. #
  47788. # == Available callback methods
  47789. #
  47790. # The observer can implement callback methods for each of the methods described in the Callbacks module.
  47791. #
  47792. # == Storing Observers in Rails
  47793. #
  47794. # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
  47795. # naming convention of app/models/audit_observer.rb.
  47796. #
  47797. # == Configuration
  47798. #
  47799. # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
  47800. # <tt>config/environment.rb</tt> file.
  47801. #
  47802. # config.active_record.observers = :comment_observer, :signup_observer
  47803. #
  47804. # Observers will not be invoked unless you define these in your application configuration.
  47805. #
  47806. class Observer
  47807. include Singleton
  47808. # Observer subclasses should be reloaded by the dispatcher in Rails
  47809. # when Dependencies.mechanism = :load.
  47810. include Reloadable::Subclasses
  47811. # Attaches the observer to the supplied model classes.
  47812. def self.observe(*models)
  47813. define_method(:observed_class) { models }
  47814. end
  47815. def initialize
  47816. observed_classes = [ observed_class ].flatten
  47817. observed_subclasses_class = observed_classes.collect {|c| c.send(:subclasses) }.flatten!
  47818. (observed_classes + observed_subclasses_class).each do |klass|
  47819. klass.add_observer(self)
  47820. klass.send(:define_method, :after_find) unless klass.respond_to?(:after_find)
  47821. end
  47822. end
  47823. def update(callback_method, object) #:nodoc:
  47824. send(callback_method, object) if respond_to?(callback_method)
  47825. end
  47826. private
  47827. def observed_class
  47828. if self.class.respond_to? "observed_class"
  47829. self.class.observed_class
  47830. else
  47831. Object.const_get(infer_observed_class_name)
  47832. end
  47833. end
  47834. def infer_observed_class_name
  47835. self.class.name.scan(/(.*)Observer/)[0][0]
  47836. end
  47837. end
  47838. end
  47839. module ActiveRecord
  47840. class QueryCache #:nodoc:
  47841. def initialize(connection)
  47842. @connection = connection
  47843. @query_cache = {}
  47844. end
  47845. def clear_query_cache
  47846. @query_cache = {}
  47847. end
  47848. def select_all(sql, name = nil)
  47849. (@query_cache[sql] ||= @connection.select_all(sql, name)).dup
  47850. end
  47851. def select_one(sql, name = nil)
  47852. @query_cache[sql] ||= @connection.select_one(sql, name)
  47853. end
  47854. def columns(table_name, name = nil)
  47855. @query_cache["SHOW FIELDS FROM #{table_name}"] ||= @connection.columns(table_name, name)
  47856. end
  47857. def insert(sql, name = nil, pk = nil, id_value = nil)
  47858. clear_query_cache
  47859. @connection.insert(sql, name, pk, id_value)
  47860. end
  47861. def update(sql, name = nil)
  47862. clear_query_cache
  47863. @connection.update(sql, name)
  47864. end
  47865. def delete(sql, name = nil)
  47866. clear_query_cache
  47867. @connection.delete(sql, name)
  47868. end
  47869. private
  47870. def method_missing(method, *arguments, &proc)
  47871. @connection.send(method, *arguments, &proc)
  47872. end
  47873. end
  47874. class Base
  47875. # Set the connection for the class with caching on
  47876. class << self
  47877. alias_method :connection_without_query_cache=, :connection=
  47878. def connection=(spec)
  47879. if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache]
  47880. spec = QueryCache.new(self.send(spec.adapter_method, spec.config))
  47881. end
  47882. self.connection_without_query_cache = spec
  47883. end
  47884. end
  47885. end
  47886. class AbstractAdapter #:nodoc:
  47887. # Stub method to be able to treat the connection the same whether the query cache has been turned on or not
  47888. def clear_query_cache
  47889. end
  47890. end
  47891. end
  47892. module ActiveRecord
  47893. module Reflection # :nodoc:
  47894. def self.included(base)
  47895. base.extend(ClassMethods)
  47896. end
  47897. # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
  47898. # This information can, for example, be used in a form builder that took an Active Record object and created input
  47899. # fields for all of the attributes depending on their type and displayed the associations to other objects.
  47900. #
  47901. # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
  47902. module ClassMethods
  47903. def create_reflection(macro, name, options, active_record)
  47904. case macro
  47905. when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
  47906. reflection = AssociationReflection.new(macro, name, options, active_record)
  47907. when :composed_of
  47908. reflection = AggregateReflection.new(macro, name, options, active_record)
  47909. end
  47910. write_inheritable_hash :reflections, name => reflection
  47911. reflection
  47912. end
  47913. def reflections
  47914. read_inheritable_attribute(:reflections) or write_inheritable_attribute(:reflections, {})
  47915. end
  47916. # Returns an array of AggregateReflection objects for all the aggregations in the class.
  47917. def reflect_on_all_aggregations
  47918. reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
  47919. end
  47920. # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
  47921. # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
  47922. def reflect_on_aggregation(aggregation)
  47923. reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
  47924. end
  47925. # Returns an array of AssociationReflection objects for all the aggregations in the class. If you only want to reflect on a
  47926. # certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter. Example:
  47927. # Account.reflect_on_all_associations # returns an array of all associations
  47928. # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
  47929. def reflect_on_all_associations(macro = nil)
  47930. association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
  47931. macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
  47932. end
  47933. # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
  47934. # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
  47935. # Invoice.reflect_on_association(:line_items).macro # returns :has_many
  47936. def reflect_on_association(association)
  47937. reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
  47938. end
  47939. end
  47940. # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
  47941. # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
  47942. class MacroReflection
  47943. attr_reader :active_record
  47944. def initialize(macro, name, options, active_record)
  47945. @macro, @name, @options, @active_record = macro, name, options, active_record
  47946. end
  47947. # Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
  47948. # :clients for "has_many :clients".
  47949. def name
  47950. @name
  47951. end
  47952. # Returns the name of the macro, so it would return :composed_of for
  47953. # "composed_of :balance, :class_name => 'Money'" or :has_many for "has_many :clients".
  47954. def macro
  47955. @macro
  47956. end
  47957. # Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
  47958. # "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
  47959. def options
  47960. @options
  47961. end
  47962. # Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
  47963. # "has_many :clients" would return the Client class.
  47964. def klass() end
  47965. def class_name
  47966. @class_name ||= name_to_class_name(name.id2name)
  47967. end
  47968. def ==(other_aggregation)
  47969. name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
  47970. end
  47971. end
  47972. # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
  47973. class AggregateReflection < MacroReflection #:nodoc:
  47974. def klass
  47975. @klass ||= Object.const_get(options[:class_name] || class_name)
  47976. end
  47977. private
  47978. def name_to_class_name(name)
  47979. name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
  47980. end
  47981. end
  47982. # Holds all the meta-data about an association as it was specified in the Active Record class.
  47983. class AssociationReflection < MacroReflection #:nodoc:
  47984. def klass
  47985. @klass ||= active_record.send(:compute_type, class_name)
  47986. end
  47987. def table_name
  47988. @table_name ||= klass.table_name
  47989. end
  47990. def primary_key_name
  47991. return @primary_key_name if @primary_key_name
  47992. case
  47993. when macro == :belongs_to
  47994. @primary_key_name = options[:foreign_key] || class_name.foreign_key
  47995. when options[:as]
  47996. @primary_key_name = options[:foreign_key] || "#{options[:as]}_id"
  47997. else
  47998. @primary_key_name = options[:foreign_key] || active_record.name.foreign_key
  47999. end
  48000. end
  48001. def association_foreign_key
  48002. @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
  48003. end
  48004. def counter_cache_column
  48005. if options[:counter_cache] == true
  48006. "#{active_record.name.underscore.pluralize}_count"
  48007. elsif options[:counter_cache]
  48008. options[:counter_cache]
  48009. end
  48010. end
  48011. def through_reflection
  48012. @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
  48013. end
  48014. # Gets an array of possible :through source reflection names
  48015. #
  48016. # [singularized, pluralized]
  48017. def source_reflection_names
  48018. @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
  48019. end
  48020. # Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.
  48021. # (The :tags association on Tagging below)
  48022. #
  48023. # class Post
  48024. # has_many :tags, :through => :taggings
  48025. # end
  48026. #
  48027. def source_reflection
  48028. return nil unless through_reflection
  48029. @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
  48030. end
  48031. def check_validity!
  48032. if options[:through]
  48033. if through_reflection.nil?
  48034. raise HasManyThroughAssociationNotFoundError.new(self)
  48035. end
  48036. if source_reflection.nil?
  48037. raise HasManyThroughSourceAssociationNotFoundError.new(self)
  48038. end
  48039. if source_reflection.options[:polymorphic]
  48040. raise HasManyThroughAssociationPolymorphicError.new(class_name, self, source_reflection)
  48041. end
  48042. unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
  48043. raise HasManyThroughSourceAssociationMacroError.new(self)
  48044. end
  48045. end
  48046. end
  48047. private
  48048. def name_to_class_name(name)
  48049. if name =~ /::/
  48050. name
  48051. else
  48052. if options[:class_name]
  48053. options[:class_name]
  48054. elsif through_reflection # get the class_name of the belongs_to association of the through reflection
  48055. source_reflection.class_name
  48056. else
  48057. class_name = name.to_s.camelize
  48058. class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
  48059. class_name
  48060. end
  48061. end
  48062. end
  48063. end
  48064. end
  48065. end
  48066. module ActiveRecord
  48067. # Allows programmers to programmatically define a schema in a portable
  48068. # DSL. This means you can define tables, indexes, etc. without using SQL
  48069. # directly, so your applications can more easily support multiple
  48070. # databases.
  48071. #
  48072. # Usage:
  48073. #
  48074. # ActiveRecord::Schema.define do
  48075. # create_table :authors do |t|
  48076. # t.column :name, :string, :null => false
  48077. # end
  48078. #
  48079. # add_index :authors, :name, :unique
  48080. #
  48081. # create_table :posts do |t|
  48082. # t.column :author_id, :integer, :null => false
  48083. # t.column :subject, :string
  48084. # t.column :body, :text
  48085. # t.column :private, :boolean, :default => false
  48086. # end
  48087. #
  48088. # add_index :posts, :author_id
  48089. # end
  48090. #
  48091. # ActiveRecord::Schema is only supported by database adapters that also
  48092. # support migrations, the two features being very similar.
  48093. class Schema < Migration
  48094. private_class_method :new
  48095. # Eval the given block. All methods available to the current connection
  48096. # adapter are available within the block, so you can easily use the
  48097. # database definition DSL to build up your schema (#create_table,
  48098. # #add_index, etc.).
  48099. #
  48100. # The +info+ hash is optional, and if given is used to define metadata
  48101. # about the current schema (like the schema's version):
  48102. #
  48103. # ActiveRecord::Schema.define(:version => 15) do
  48104. # ...
  48105. # end
  48106. def self.define(info={}, &block)
  48107. instance_eval(&block)
  48108. unless info.empty?
  48109. initialize_schema_information
  48110. cols = columns('schema_info')
  48111. info = info.map do |k,v|
  48112. v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
  48113. "#{k} = #{v}"
  48114. end
  48115. Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
  48116. end
  48117. end
  48118. end
  48119. end
  48120. module ActiveRecord
  48121. # This class is used to dump the database schema for some connection to some
  48122. # output format (i.e., ActiveRecord::Schema).
  48123. class SchemaDumper #:nodoc:
  48124. private_class_method :new
  48125. # A list of tables which should not be dumped to the schema.
  48126. # Acceptable values are strings as well as regexp.
  48127. # This setting is only used if ActiveRecord::Base.schema_format == :ruby
  48128. cattr_accessor :ignore_tables
  48129. @@ignore_tables = []
  48130. def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
  48131. new(connection).dump(stream)
  48132. stream
  48133. end
  48134. def dump(stream)
  48135. header(stream)
  48136. tables(stream)
  48137. trailer(stream)
  48138. stream
  48139. end
  48140. private
  48141. def initialize(connection)
  48142. @connection = connection
  48143. @types = @connection.native_database_types
  48144. @info = @connection.select_one("SELECT * FROM schema_info") rescue nil
  48145. end
  48146. def header(stream)
  48147. define_params = @info ? ":version => #{@info['version']}" : ""
  48148. stream.puts <<HEADER
  48149. # This file is autogenerated. Instead of editing this file, please use the
  48150. # migrations feature of ActiveRecord to incrementally modify your database, and
  48151. # then regenerate this schema definition.
  48152. ActiveRecord::Schema.define(#{define_params}) do
  48153. HEADER
  48154. end
  48155. def trailer(stream)
  48156. stream.puts "end"
  48157. end
  48158. def tables(stream)
  48159. @connection.tables.sort.each do |tbl|
  48160. next if ["schema_info", ignore_tables].flatten.any? do |ignored|
  48161. case ignored
  48162. when String: tbl == ignored
  48163. when Regexp: tbl =~ ignored
  48164. else
  48165. raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
  48166. end
  48167. end
  48168. table(tbl, stream)
  48169. end
  48170. end
  48171. def table(table, stream)
  48172. columns = @connection.columns(table)
  48173. begin
  48174. tbl = StringIO.new
  48175. if @connection.respond_to?(:pk_and_sequence_for)
  48176. pk, pk_seq = @connection.pk_and_sequence_for(table)
  48177. end
  48178. pk ||= 'id'
  48179. tbl.print " create_table #{table.inspect}"
  48180. if columns.detect { |c| c.name == pk }
  48181. if pk != 'id'
  48182. tbl.print %Q(, :primary_key => "#{pk}")
  48183. end
  48184. else
  48185. tbl.print ", :id => false"
  48186. end
  48187. tbl.print ", :force => true"
  48188. tbl.puts " do |t|"
  48189. columns.each do |column|
  48190. raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
  48191. next if column.name == pk
  48192. tbl.print " t.column #{column.name.inspect}, #{column.type.inspect}"
  48193. tbl.print ", :limit => #{column.limit.inspect}" if column.limit != @types[column.type][:limit]
  48194. tbl.print ", :default => #{column.default.inspect}" if !column.default.nil?
  48195. tbl.print ", :null => false" if !column.null
  48196. tbl.puts
  48197. end
  48198. tbl.puts " end"
  48199. tbl.puts
  48200. indexes(table, tbl)
  48201. tbl.rewind
  48202. stream.print tbl.read
  48203. rescue => e
  48204. stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
  48205. stream.puts "# #{e.message}"
  48206. stream.puts
  48207. end
  48208. stream
  48209. end
  48210. def indexes(table, stream)
  48211. indexes = @connection.indexes(table)
  48212. indexes.each do |index|
  48213. stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
  48214. stream.print ", :unique => true" if index.unique
  48215. stream.puts
  48216. end
  48217. stream.puts unless indexes.empty?
  48218. end
  48219. end
  48220. end
  48221. module ActiveRecord
  48222. # Active Records will automatically record creation and/or update timestamps of database objects
  48223. # if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
  48224. # automatically included, so you don't need to do that manually.
  48225. #
  48226. # This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
  48227. # This behavior by default uses local time, but can use UTC by setting <tt>ActiveRecord::Base.default_timezone = :utc</tt>
  48228. module Timestamp
  48229. def self.append_features(base) # :nodoc:
  48230. super
  48231. base.class_eval do
  48232. alias_method :create_without_timestamps, :create
  48233. alias_method :create, :create_with_timestamps
  48234. alias_method :update_without_timestamps, :update
  48235. alias_method :update, :update_with_timestamps
  48236. end
  48237. end
  48238. def create_with_timestamps #:nodoc:
  48239. if record_timestamps
  48240. t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
  48241. write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
  48242. write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
  48243. write_attribute('updated_at', t) if respond_to?(:updated_at)
  48244. write_attribute('updated_on', t) if respond_to?(:updated_on)
  48245. end
  48246. create_without_timestamps
  48247. end
  48248. def update_with_timestamps #:nodoc:
  48249. if record_timestamps
  48250. t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
  48251. write_attribute('updated_at', t) if respond_to?(:updated_at)
  48252. write_attribute('updated_on', t) if respond_to?(:updated_on)
  48253. end
  48254. update_without_timestamps
  48255. end
  48256. end
  48257. class Base
  48258. # Records the creation date and possibly time in created_on (date only) or created_at (date and time) and the update date and possibly
  48259. # time in updated_on and updated_at. This only happens if the object responds to either of these messages, which they will do automatically
  48260. # if the table has columns of either of these names. This feature is turned on by default.
  48261. @@record_timestamps = true
  48262. cattr_accessor :record_timestamps
  48263. # deprecated: use ActiveRecord::Base.default_timezone instead.
  48264. @@timestamps_gmt = false
  48265. def self.timestamps_gmt=( gmt ) #:nodoc:
  48266. warn "timestamps_gmt= is deprecated. use default_timezone= instead"
  48267. self.default_timezone = ( gmt ? :utc : :local )
  48268. end
  48269. def self.timestamps_gmt #:nodoc:
  48270. warn "timestamps_gmt is deprecated. use default_timezone instead"
  48271. self.default_timezone == :utc
  48272. end
  48273. end
  48274. end
  48275. require 'active_record/vendor/simple.rb'
  48276. Transaction::Simple.send(:remove_method, :transaction)
  48277. require 'thread'
  48278. module ActiveRecord
  48279. module Transactions # :nodoc:
  48280. TRANSACTION_MUTEX = Mutex.new
  48281. class TransactionError < ActiveRecordError # :nodoc:
  48282. end
  48283. def self.append_features(base)
  48284. super
  48285. base.extend(ClassMethods)
  48286. base.class_eval do
  48287. alias_method :destroy_without_transactions, :destroy
  48288. alias_method :destroy, :destroy_with_transactions
  48289. alias_method :save_without_transactions, :save
  48290. alias_method :save, :save_with_transactions
  48291. end
  48292. end
  48293. # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
  48294. # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
  48295. # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
  48296. # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
  48297. # not at all. Example:
  48298. #
  48299. # transaction do
  48300. # david.withdrawal(100)
  48301. # mary.deposit(100)
  48302. # end
  48303. #
  48304. # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
  48305. # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
  48306. # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
  48307. #
  48308. # == Transactions are not distributed across database connections
  48309. #
  48310. # A transaction acts on a single database connection. If you have
  48311. # multiple class-specific databases, the transaction will not protect
  48312. # interaction among them. One workaround is to begin a transaction
  48313. # on each class whose models you alter:
  48314. #
  48315. # Student.transaction do
  48316. # Course.transaction do
  48317. # course.enroll(student)
  48318. # student.units += course.units
  48319. # end
  48320. # end
  48321. #
  48322. # This is a poor solution, but full distributed transactions are beyond
  48323. # the scope of Active Record.
  48324. #
  48325. # == Save and destroy are automatically wrapped in a transaction
  48326. #
  48327. # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
  48328. # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
  48329. # depend on or you can raise exceptions in the callbacks to rollback.
  48330. #
  48331. # == Object-level transactions
  48332. #
  48333. # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
  48334. # that you want to enable object-level transactions for, like this:
  48335. #
  48336. # Account.transaction(david, mary) do
  48337. # david.withdrawal(100)
  48338. # mary.deposit(100)
  48339. # end
  48340. #
  48341. # If the transaction fails, David and Mary will be returned to their pre-transactional state. No money will have changed hands in
  48342. # neither object nor database.
  48343. #
  48344. # == Exception handling
  48345. #
  48346. # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
  48347. # should be ready to catch those in your application code.
  48348. #
  48349. # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
  48350. module ClassMethods
  48351. def transaction(*objects, &block)
  48352. previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
  48353. lock_mutex
  48354. begin
  48355. objects.each { |o| o.extend(Transaction::Simple) }
  48356. objects.each { |o| o.start_transaction }
  48357. result = connection.transaction(Thread.current['start_db_transaction'], &block)
  48358. objects.each { |o| o.commit_transaction }
  48359. return result
  48360. rescue Exception => object_transaction_rollback
  48361. objects.each { |o| o.abort_transaction }
  48362. raise
  48363. ensure
  48364. unlock_mutex
  48365. trap('TERM', previous_handler)
  48366. end
  48367. end
  48368. def lock_mutex#:nodoc:
  48369. Thread.current['open_transactions'] ||= 0
  48370. TRANSACTION_MUTEX.lock if Thread.current['open_transactions'] == 0
  48371. Thread.current['start_db_transaction'] = (Thread.current['open_transactions'] == 0)
  48372. Thread.current['open_transactions'] += 1
  48373. end
  48374. def unlock_mutex#:nodoc:
  48375. Thread.current['open_transactions'] -= 1
  48376. TRANSACTION_MUTEX.unlock if Thread.current['open_transactions'] == 0
  48377. end
  48378. end
  48379. def transaction(*objects, &block)
  48380. self.class.transaction(*objects, &block)
  48381. end
  48382. def destroy_with_transactions #:nodoc:
  48383. transaction { destroy_without_transactions }
  48384. end
  48385. def save_with_transactions(perform_validation = true) #:nodoc:
  48386. transaction { save_without_transactions(perform_validation) }
  48387. end
  48388. end
  48389. end
  48390. module ActiveRecord
  48391. # Raised by save! and create! when the record is invalid. Use the
  48392. # record method to retrieve the record which did not validate.
  48393. # begin
  48394. # complex_operation_that_calls_save!_internally
  48395. # rescue ActiveRecord::RecordInvalid => invalid
  48396. # puts invalid.record.errors
  48397. # end
  48398. class RecordInvalid < ActiveRecordError #:nodoc:
  48399. attr_reader :record
  48400. def initialize(record)
  48401. @record = record
  48402. super("Validation failed: #{@record.errors.full_messages.join(", ")}")
  48403. end
  48404. end
  48405. # Active Record validation is reported to and from this object, which is used by Base#save to
  48406. # determine whether the object in a valid state to be saved. See usage example in Validations.
  48407. class Errors
  48408. include Enumerable
  48409. def initialize(base) # :nodoc:
  48410. @base, @errors = base, {}
  48411. end
  48412. @@default_error_messages = {
  48413. :inclusion => "is not included in the list",
  48414. :exclusion => "is reserved",
  48415. :invalid => "is invalid",
  48416. :confirmation => "doesn't match confirmation",
  48417. :accepted => "must be accepted",
  48418. :empty => "can't be empty",
  48419. :blank => "can't be blank",
  48420. :too_long => "is too long (maximum is %d characters)",
  48421. :too_short => "is too short (minimum is %d characters)",
  48422. :wrong_length => "is the wrong length (should be %d characters)",
  48423. :taken => "has already been taken",
  48424. :not_a_number => "is not a number"
  48425. }
  48426. # Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
  48427. cattr_accessor :default_error_messages
  48428. # Adds an error to the base object instead of any particular attribute. This is used
  48429. # to report errors that don't tie to any specific attribute, but rather to the object
  48430. # as a whole. These error messages don't get prepended with any field name when iterating
  48431. # with each_full, so they should be complete sentences.
  48432. def add_to_base(msg)
  48433. add(:base, msg)
  48434. end
  48435. # Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
  48436. # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
  48437. # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
  48438. # If no +msg+ is supplied, "invalid" is assumed.
  48439. def add(attribute, msg = @@default_error_messages[:invalid])
  48440. @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
  48441. @errors[attribute.to_s] << msg
  48442. end
  48443. # Will add an error message to each of the attributes in +attributes+ that is empty.
  48444. def add_on_empty(attributes, msg = @@default_error_messages[:empty])
  48445. for attr in [attributes].flatten
  48446. value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
  48447. is_empty = value.respond_to?("empty?") ? value.empty? : false
  48448. add(attr, msg) unless !value.nil? && !is_empty
  48449. end
  48450. end
  48451. # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
  48452. def add_on_blank(attributes, msg = @@default_error_messages[:blank])
  48453. for attr in [attributes].flatten
  48454. value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
  48455. add(attr, msg) if value.blank?
  48456. end
  48457. end
  48458. # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
  48459. # If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
  48460. def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
  48461. for attr in [attributes].flatten
  48462. value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
  48463. add(attr, too_short_msg % range.begin) if value && value.length < range.begin
  48464. add(attr, too_long_msg % range.end) if value && value.length > range.end
  48465. end
  48466. end
  48467. alias :add_on_boundry_breaking :add_on_boundary_breaking
  48468. # Returns true if the specified +attribute+ has errors associated with it.
  48469. def invalid?(attribute)
  48470. !@errors[attribute.to_s].nil?
  48471. end
  48472. # * Returns nil, if no errors are associated with the specified +attribute+.
  48473. # * Returns the error message, if one error is associated with the specified +attribute+.
  48474. # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
  48475. def on(attribute)
  48476. if @errors[attribute.to_s].nil?
  48477. nil
  48478. elsif @errors[attribute.to_s].length == 1
  48479. @errors[attribute.to_s].first
  48480. else
  48481. @errors[attribute.to_s]
  48482. end
  48483. end
  48484. alias :[] :on
  48485. # Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
  48486. def on_base
  48487. on(:base)
  48488. end
  48489. # Yields each attribute and associated message per error added.
  48490. def each
  48491. @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
  48492. end
  48493. # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
  48494. # through iteration as "First name can't be empty".
  48495. def each_full
  48496. full_messages.each { |msg| yield msg }
  48497. end
  48498. # Returns all the full error messages in an array.
  48499. def full_messages
  48500. full_messages = []
  48501. @errors.each_key do |attr|
  48502. @errors[attr].each do |msg|
  48503. next if msg.nil?
  48504. if attr == "base"
  48505. full_messages << msg
  48506. else
  48507. full_messages << @base.class.human_attribute_name(attr) + " " + msg
  48508. end
  48509. end
  48510. end
  48511. return full_messages
  48512. end
  48513. # Returns true if no errors have been added.
  48514. def empty?
  48515. return @errors.empty?
  48516. end
  48517. # Removes all the errors that have been added.
  48518. def clear
  48519. @errors = {}
  48520. end
  48521. # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
  48522. # with this as well.
  48523. def size
  48524. error_count = 0
  48525. @errors.each_value { |attribute| error_count += attribute.length }
  48526. error_count
  48527. end
  48528. alias_method :count, :size
  48529. alias_method :length, :size
  48530. end
  48531. # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
  48532. # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
  48533. # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
  48534. #
  48535. # Example:
  48536. #
  48537. # class Person < ActiveRecord::Base
  48538. # protected
  48539. # def validate
  48540. # errors.add_on_empty %w( first_name last_name )
  48541. # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
  48542. # end
  48543. #
  48544. # def validate_on_create # is only run the first time a new object is saved
  48545. # unless valid_discount?(membership_discount)
  48546. # errors.add("membership_discount", "has expired")
  48547. # end
  48548. # end
  48549. #
  48550. # def validate_on_update
  48551. # errors.add_to_base("No changes have occurred") if unchanged_attributes?
  48552. # end
  48553. # end
  48554. #
  48555. # person = Person.new("first_name" => "David", "phone_number" => "what?")
  48556. # person.save # => false (and doesn't do the save)
  48557. # person.errors.empty? # => false
  48558. # person.errors.count # => 2
  48559. # person.errors.on "last_name" # => "can't be empty"
  48560. # person.errors.on "phone_number" # => "has invalid format"
  48561. # person.errors.each_full { |msg| puts msg }
  48562. # # => "Last name can't be empty\n" +
  48563. # "Phone number has invalid format"
  48564. #
  48565. # person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
  48566. # person.save # => true (and person is now saved in the database)
  48567. #
  48568. # An +Errors+ object is automatically created for every Active Record.
  48569. #
  48570. # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
  48571. module Validations
  48572. VALIDATIONS = %w( validate validate_on_create validate_on_update )
  48573. def self.append_features(base) # :nodoc:
  48574. super
  48575. base.extend ClassMethods
  48576. base.class_eval do
  48577. alias_method :save_without_validation, :save
  48578. alias_method :save, :save_with_validation
  48579. alias_method :save_without_validation!, :save!
  48580. alias_method :save!, :save_with_validation!
  48581. alias_method :update_attribute_without_validation_skipping, :update_attribute
  48582. alias_method :update_attribute, :update_attribute_with_validation_skipping
  48583. end
  48584. end
  48585. # All of the following validations are defined in the class scope of the model that you're interested in validating.
  48586. # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
  48587. # these over the low-level calls to validate and validate_on_create when possible.
  48588. module ClassMethods
  48589. DEFAULT_VALIDATION_OPTIONS = {
  48590. :on => :save,
  48591. :allow_nil => false,
  48592. :message => nil
  48593. }.freeze
  48594. ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
  48595. def validate(*methods, &block)
  48596. methods << block if block_given?
  48597. write_inheritable_set(:validate, methods)
  48598. end
  48599. def validate_on_create(*methods, &block)
  48600. methods << block if block_given?
  48601. write_inheritable_set(:validate_on_create, methods)
  48602. end
  48603. def validate_on_update(*methods, &block)
  48604. methods << block if block_given?
  48605. write_inheritable_set(:validate_on_update, methods)
  48606. end
  48607. def condition_block?(condition)
  48608. condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
  48609. end
  48610. # Determine from the given condition (whether a block, procedure, method or string)
  48611. # whether or not to validate the record. See #validates_each.
  48612. def evaluate_condition(condition, record)
  48613. case condition
  48614. when Symbol: record.send(condition)
  48615. when String: eval(condition, binding)
  48616. else
  48617. if condition_block?(condition)
  48618. condition.call(record)
  48619. else
  48620. raise(
  48621. ActiveRecordError,
  48622. "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
  48623. "class implementing a static validation method"
  48624. )
  48625. end
  48626. end
  48627. end
  48628. # Validates each attribute against a block.
  48629. #
  48630. # class Person < ActiveRecord::Base
  48631. # validates_each :first_name, :last_name do |record, attr, value|
  48632. # record.errors.add attr, 'starts with z.' if value[0] == ?z
  48633. # end
  48634. # end
  48635. #
  48636. # Options:
  48637. # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
  48638. # * <tt>allow_nil</tt> - Skip validation if attribute is nil.
  48639. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48640. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48641. # method, proc or string should return or evaluate to a true or false value.
  48642. def validates_each(*attrs)
  48643. options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
  48644. attrs = attrs.flatten
  48645. # Declare the validation.
  48646. send(validation_method(options[:on] || :save)) do |record|
  48647. # Don't validate when there is an :if condition and that condition is false
  48648. unless options[:if] && !evaluate_condition(options[:if], record)
  48649. attrs.each do |attr|
  48650. value = record.send(attr)
  48651. next if value.nil? && options[:allow_nil]
  48652. yield record, attr, value
  48653. end
  48654. end
  48655. end
  48656. end
  48657. # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
  48658. #
  48659. # Model:
  48660. # class Person < ActiveRecord::Base
  48661. # validates_confirmation_of :user_name, :password
  48662. # validates_confirmation_of :email_address, :message => "should match confirmation"
  48663. # end
  48664. #
  48665. # View:
  48666. # <%= password_field "person", "password" %>
  48667. # <%= password_field "person", "password_confirmation" %>
  48668. #
  48669. # The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
  48670. # It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation
  48671. # is not nil and by default on save.
  48672. #
  48673. # Configuration options:
  48674. # * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
  48675. # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
  48676. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48677. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48678. # method, proc or string should return or evaluate to a true or false value.
  48679. def validates_confirmation_of(*attr_names)
  48680. configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
  48681. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48682. attr_accessor *(attr_names.map { |n| "#{n}_confirmation" })
  48683. validates_each(attr_names, configuration) do |record, attr_name, value|
  48684. record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
  48685. end
  48686. end
  48687. # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
  48688. #
  48689. # class Person < ActiveRecord::Base
  48690. # validates_acceptance_of :terms_of_service
  48691. # validates_acceptance_of :eula, :message => "must be abided"
  48692. # end
  48693. #
  48694. # The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed only if
  48695. # terms_of_service is not nil and by default on save.
  48696. #
  48697. # Configuration options:
  48698. # * <tt>message</tt> - A custom error message (default is: "must be accepted")
  48699. # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
  48700. # * <tt>accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
  48701. # makes it easy to relate to an HTML checkbox.
  48702. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48703. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48704. # method, proc or string should return or evaluate to a true or false value.
  48705. def validates_acceptance_of(*attr_names)
  48706. configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
  48707. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48708. attr_accessor *attr_names
  48709. validates_each(attr_names,configuration) do |record, attr_name, value|
  48710. record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
  48711. end
  48712. end
  48713. # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
  48714. #
  48715. # class Person < ActiveRecord::Base
  48716. # validates_presence_of :first_name
  48717. # end
  48718. #
  48719. # The first_name attribute must be in the object and it cannot be blank.
  48720. #
  48721. # Configuration options:
  48722. # * <tt>message</tt> - A custom error message (default is: "can't be blank")
  48723. # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
  48724. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48725. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48726. # method, proc or string should return or evaluate to a true or false value.
  48727. #
  48728. # === Warning
  48729. # Validate the presence of the foreign key, not the instance variable itself.
  48730. # Do this:
  48731. # validate_presence_of :invoice_id
  48732. #
  48733. # Not this:
  48734. # validate_presence_of :invoice
  48735. #
  48736. # If you validate the presence of the associated object, you will get
  48737. # failures on saves when both the parent object and the child object are
  48738. # new.
  48739. def validates_presence_of(*attr_names)
  48740. configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
  48741. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48742. # can't use validates_each here, because it cannot cope with nonexistent attributes,
  48743. # while errors.add_on_empty can
  48744. attr_names.each do |attr_name|
  48745. send(validation_method(configuration[:on])) do |record|
  48746. unless configuration[:if] and not evaluate_condition(configuration[:if], record)
  48747. record.errors.add_on_blank(attr_name,configuration[:message])
  48748. end
  48749. end
  48750. end
  48751. end
  48752. # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
  48753. #
  48754. # class Person < ActiveRecord::Base
  48755. # validates_length_of :first_name, :maximum=>30
  48756. # validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
  48757. # validates_length_of :fax, :in => 7..32, :allow_nil => true
  48758. # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
  48759. # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
  48760. # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
  48761. # end
  48762. #
  48763. # Configuration options:
  48764. # * <tt>minimum</tt> - The minimum size of the attribute
  48765. # * <tt>maximum</tt> - The maximum size of the attribute
  48766. # * <tt>is</tt> - The exact size of the attribute
  48767. # * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
  48768. # * <tt>in</tt> - A synonym(or alias) for :within
  48769. # * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
  48770. #
  48771. # * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
  48772. # * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
  48773. # * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
  48774. # * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message
  48775. # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
  48776. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48777. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48778. # method, proc or string should return or evaluate to a true or false value.
  48779. def validates_length_of(*attrs)
  48780. # Merge given options with defaults.
  48781. options = {
  48782. :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
  48783. :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
  48784. :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
  48785. }.merge(DEFAULT_VALIDATION_OPTIONS)
  48786. options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
  48787. # Ensure that one and only one range option is specified.
  48788. range_options = ALL_RANGE_OPTIONS & options.keys
  48789. case range_options.size
  48790. when 0
  48791. raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
  48792. when 1
  48793. # Valid number of options; do nothing.
  48794. else
  48795. raise ArgumentError, 'Too many range options specified. Choose only one.'
  48796. end
  48797. # Get range option and value.
  48798. option = range_options.first
  48799. option_value = options[range_options.first]
  48800. case option
  48801. when :within, :in
  48802. raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
  48803. too_short = options[:too_short] % option_value.begin
  48804. too_long = options[:too_long] % option_value.end
  48805. validates_each(attrs, options) do |record, attr, value|
  48806. if value.nil? or value.split(//).size < option_value.begin
  48807. record.errors.add(attr, too_short)
  48808. elsif value.split(//).size > option_value.end
  48809. record.errors.add(attr, too_long)
  48810. end
  48811. end
  48812. when :is, :minimum, :maximum
  48813. raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
  48814. # Declare different validations per option.
  48815. validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
  48816. message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
  48817. message = (options[:message] || options[message_options[option]]) % option_value
  48818. validates_each(attrs, options) do |record, attr, value|
  48819. if value.kind_of?(String)
  48820. record.errors.add(attr, message) unless !value.nil? and value.split(//).size.method(validity_checks[option])[option_value]
  48821. else
  48822. record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
  48823. end
  48824. end
  48825. end
  48826. end
  48827. alias_method :validates_size_of, :validates_length_of
  48828. # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
  48829. # can be named "davidhh".
  48830. #
  48831. # class Person < ActiveRecord::Base
  48832. # validates_uniqueness_of :user_name, :scope => :account_id
  48833. # end
  48834. #
  48835. # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
  48836. # making sure that a teacher can only be on the schedule once per semester for a particular class.
  48837. #
  48838. # class TeacherSchedule < ActiveRecord::Base
  48839. # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
  48840. # end
  48841. #
  48842. # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
  48843. # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
  48844. #
  48845. # Configuration options:
  48846. # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
  48847. # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniquness constraint.
  48848. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48849. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48850. # method, proc or string should return or evaluate to a true or false value.
  48851. def validates_uniqueness_of(*attr_names)
  48852. configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
  48853. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48854. validates_each(attr_names,configuration) do |record, attr_name, value|
  48855. condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}"
  48856. condition_params = [value]
  48857. if scope = configuration[:scope]
  48858. Array(scope).map do |scope_item|
  48859. scope_value = record.send(scope_item)
  48860. condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}"
  48861. condition_params << scope_value
  48862. end
  48863. end
  48864. unless record.new_record?
  48865. condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?"
  48866. condition_params << record.send(:id)
  48867. end
  48868. if record.class.find(:first, :conditions => [condition_sql, *condition_params])
  48869. record.errors.add(attr_name, configuration[:message])
  48870. end
  48871. end
  48872. end
  48873. # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
  48874. # provided.
  48875. #
  48876. # class Person < ActiveRecord::Base
  48877. # validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :on => :create
  48878. # end
  48879. #
  48880. # A regular expression must be provided or else an exception will be raised.
  48881. #
  48882. # Configuration options:
  48883. # * <tt>message</tt> - A custom error message (default is: "is invalid")
  48884. # * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
  48885. # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
  48886. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48887. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48888. # method, proc or string should return or evaluate to a true or false value.
  48889. def validates_format_of(*attr_names)
  48890. configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
  48891. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48892. raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
  48893. validates_each(attr_names, configuration) do |record, attr_name, value|
  48894. record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
  48895. end
  48896. end
  48897. # Validates whether the value of the specified attribute is available in a particular enumerable object.
  48898. #
  48899. # class Person < ActiveRecord::Base
  48900. # validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
  48901. # validates_inclusion_of :age, :in=>0..99
  48902. # end
  48903. #
  48904. # Configuration options:
  48905. # * <tt>in</tt> - An enumerable object of available items
  48906. # * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list")
  48907. # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
  48908. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48909. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48910. # method, proc or string should return or evaluate to a true or false value.
  48911. def validates_inclusion_of(*attr_names)
  48912. configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
  48913. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48914. enum = configuration[:in] || configuration[:within]
  48915. raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
  48916. validates_each(attr_names, configuration) do |record, attr_name, value|
  48917. record.errors.add(attr_name, configuration[:message]) unless enum.include?(value)
  48918. end
  48919. end
  48920. # Validates that the value of the specified attribute is not in a particular enumerable object.
  48921. #
  48922. # class Person < ActiveRecord::Base
  48923. # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
  48924. # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
  48925. # end
  48926. #
  48927. # Configuration options:
  48928. # * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of
  48929. # * <tt>message</tt> - Specifies a customer error message (default is: "is reserved")
  48930. # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
  48931. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48932. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48933. # method, proc or string should return or evaluate to a true or false value.
  48934. def validates_exclusion_of(*attr_names)
  48935. configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
  48936. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48937. enum = configuration[:in] || configuration[:within]
  48938. raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
  48939. validates_each(attr_names, configuration) do |record, attr_name, value|
  48940. record.errors.add(attr_name, configuration[:message]) if enum.include?(value)
  48941. end
  48942. end
  48943. # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
  48944. #
  48945. # class Book < ActiveRecord::Base
  48946. # has_many :pages
  48947. # belongs_to :library
  48948. #
  48949. # validates_associated :pages, :library
  48950. # end
  48951. #
  48952. # Warning: If, after the above definition, you then wrote:
  48953. #
  48954. # class Page < ActiveRecord::Base
  48955. # belongs_to :book
  48956. #
  48957. # validates_associated :book
  48958. # end
  48959. #
  48960. # ...this would specify a circular dependency and cause infinite recursion.
  48961. #
  48962. # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
  48963. # is both present and guaranteed to be valid, you also need to use validates_presence_of.
  48964. #
  48965. # Configuration options:
  48966. # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
  48967. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48968. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48969. # method, proc or string should return or evaluate to a true or false value.
  48970. def validates_associated(*attr_names)
  48971. configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
  48972. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48973. validates_each(attr_names, configuration) do |record, attr_name, value|
  48974. record.errors.add(attr_name, configuration[:message]) unless
  48975. (value.is_a?(Array) ? value : [value]).all? { |r| r.nil? or r.valid? }
  48976. end
  48977. end
  48978. # Validates whether the value of the specified attribute is numeric by trying to convert it to
  48979. # a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
  48980. # <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
  48981. #
  48982. # class Person < ActiveRecord::Base
  48983. # validates_numericality_of :value, :on => :create
  48984. # end
  48985. #
  48986. # Configuration options:
  48987. # * <tt>message</tt> - A custom error message (default is: "is not a number")
  48988. # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
  48989. # * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
  48990. # * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columns empty strings are converted to nil
  48991. # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
  48992. # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
  48993. # method, proc or string should return or evaluate to a true or false value.
  48994. def validates_numericality_of(*attr_names)
  48995. configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save,
  48996. :only_integer => false, :allow_nil => false }
  48997. configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
  48998. if configuration[:only_integer]
  48999. validates_each(attr_names,configuration) do |record, attr_name,value|
  49000. record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /^[+-]?\d+$/
  49001. end
  49002. else
  49003. validates_each(attr_names,configuration) do |record, attr_name,value|
  49004. next if configuration[:allow_nil] and record.send("#{attr_name}_before_type_cast").nil?
  49005. begin
  49006. Kernel.Float(record.send("#{attr_name}_before_type_cast").to_s)
  49007. rescue ArgumentError, TypeError
  49008. record.errors.add(attr_name, configuration[:message])
  49009. end
  49010. end
  49011. end
  49012. end
  49013. # Creates an object just like Base.create but calls save! instead of save
  49014. # so an exception is raised if the record is invalid.
  49015. def create!(attributes = nil)
  49016. if attributes.is_a?(Array)
  49017. attributes.collect { |attr| create!(attr) }
  49018. else
  49019. attributes.reverse_merge!(scope(:create)) if scoped?(:create)
  49020. object = new(attributes)
  49021. object.save!
  49022. object
  49023. end
  49024. end
  49025. private
  49026. def write_inheritable_set(key, methods)
  49027. existing_methods = read_inheritable_attribute(key) || []
  49028. write_inheritable_attribute(key, methods | existing_methods)
  49029. end
  49030. def validation_method(on)
  49031. case on
  49032. when :save then :validate
  49033. when :create then :validate_on_create
  49034. when :update then :validate_on_update
  49035. end
  49036. end
  49037. end
  49038. # The validation process on save can be skipped by passing false. The regular Base#save method is
  49039. # replaced with this when the validations module is mixed in, which it is by default.
  49040. def save_with_validation(perform_validation = true)
  49041. if perform_validation && valid? || !perform_validation
  49042. save_without_validation
  49043. else
  49044. false
  49045. end
  49046. end
  49047. # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
  49048. # if the record is not valid.
  49049. def save_with_validation!
  49050. if valid?
  49051. save_without_validation!
  49052. else
  49053. raise RecordInvalid.new(self)
  49054. end
  49055. end
  49056. # Updates a single attribute and saves the record without going through the normal validation procedure.
  49057. # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
  49058. # in Base is replaced with this when the validations module is mixed in, which it is by default.
  49059. def update_attribute_with_validation_skipping(name, value)
  49060. send(name.to_s + '=', value)
  49061. save(false)
  49062. end
  49063. # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false.
  49064. def valid?
  49065. errors.clear
  49066. run_validations(:validate)
  49067. validate
  49068. if new_record?
  49069. run_validations(:validate_on_create)
  49070. validate_on_create
  49071. else
  49072. run_validations(:validate_on_update)
  49073. validate_on_update
  49074. end
  49075. errors.empty?
  49076. end
  49077. # Returns the Errors object that holds all information about attribute error messages.
  49078. def errors
  49079. @errors ||= Errors.new(self)
  49080. end
  49081. protected
  49082. # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
  49083. def validate #:doc:
  49084. end
  49085. # Overwrite this method for validation checks used only on creation.
  49086. def validate_on_create #:doc:
  49087. end
  49088. # Overwrite this method for validation checks used only on updates.
  49089. def validate_on_update # :doc:
  49090. end
  49091. private
  49092. def run_validations(validation_method)
  49093. validations = self.class.read_inheritable_attribute(validation_method.to_sym)
  49094. if validations.nil? then return end
  49095. validations.each do |validation|
  49096. if validation.is_a?(Symbol)
  49097. self.send(validation)
  49098. elsif validation.is_a?(String)
  49099. eval(validation, binding)
  49100. elsif validation_block?(validation)
  49101. validation.call(self)
  49102. elsif validation_class?(validation, validation_method)
  49103. validation.send(validation_method, self)
  49104. else
  49105. raise(
  49106. ActiveRecordError,
  49107. "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
  49108. "class implementing a static validation method"
  49109. )
  49110. end
  49111. end
  49112. end
  49113. def validation_block?(validation)
  49114. validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)
  49115. end
  49116. def validation_class?(validation, validation_method)
  49117. validation.respond_to?(validation_method)
  49118. end
  49119. end
  49120. end
  49121. require 'db2/db2cli.rb'
  49122. module DB2
  49123. module DB2Util
  49124. include DB2CLI
  49125. def free() SQLFreeHandle(@handle_type, @handle); end
  49126. def handle() @handle; end
  49127. def check_rc(rc)
  49128. if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc)
  49129. rec = 1
  49130. msg = ''
  49131. loop do
  49132. a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
  49133. break if a[0] != SQL_SUCCESS
  49134. msg << a[3] if !a[3].nil? and a[3] != '' # Create message.
  49135. rec += 1
  49136. end
  49137. raise "DB2 error: #{msg}"
  49138. end
  49139. end
  49140. end
  49141. class Environment
  49142. include DB2Util
  49143. def initialize
  49144. @handle_type = SQL_HANDLE_ENV
  49145. rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
  49146. check_rc(rc)
  49147. end
  49148. def data_sources(buffer_length = 1024)
  49149. retval = []
  49150. max_buffer_length = buffer_length
  49151. a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length)
  49152. retval << [a[1], a[3]]
  49153. max_buffer_length = [max_buffer_length, a[4]].max
  49154. loop do
  49155. a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length)
  49156. break if a[0] == SQL_NO_DATA_FOUND
  49157. retval << [a[1], a[3]]
  49158. max_buffer_length = [max_buffer_length, a[4]].max
  49159. end
  49160. if max_buffer_length > buffer_length
  49161. get_data_sources(max_buffer_length)
  49162. else
  49163. retval
  49164. end
  49165. end
  49166. end
  49167. class Connection
  49168. include DB2Util
  49169. def initialize(environment)
  49170. @env = environment
  49171. @handle_type = SQL_HANDLE_DBC
  49172. rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
  49173. check_rc(rc)
  49174. end
  49175. def connect(server_name, user_name = '', auth = '')
  49176. check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s))
  49177. end
  49178. def set_connect_attr(attr, value)
  49179. value += "\0" if value.class == String
  49180. check_rc(SQLSetConnectAttr(@handle, attr, value))
  49181. end
  49182. def set_auto_commit_on
  49183. set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
  49184. end
  49185. def set_auto_commit_off
  49186. set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
  49187. end
  49188. def disconnect
  49189. check_rc(SQLDisconnect(@handle))
  49190. end
  49191. def rollback
  49192. check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK))
  49193. end
  49194. def commit
  49195. check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT))
  49196. end
  49197. end
  49198. class Statement
  49199. include DB2Util
  49200. def initialize(connection)
  49201. @conn = connection
  49202. @handle_type = SQL_HANDLE_STMT
  49203. @parms = [] #yun
  49204. @sql = '' #yun
  49205. @numParms = 0 #yun
  49206. @prepared = false #yun
  49207. @parmArray = [] #yun. attributes of the parameter markers
  49208. rc, @handle = SQLAllocHandle(@handle_type, @conn.handle)
  49209. check_rc(rc)
  49210. end
  49211. def columns(table_name, schema_name = '%')
  49212. check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%'))
  49213. fetch_all
  49214. end
  49215. def tables(schema_name = '%')
  49216. check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE'))
  49217. fetch_all
  49218. end
  49219. def indexes(table_name, schema_name = '')
  49220. check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE))
  49221. fetch_all
  49222. end
  49223. def prepare(sql)
  49224. @sql = sql
  49225. check_rc(SQLPrepare(@handle, sql))
  49226. rc, @numParms = SQLNumParams(@handle) #number of question marks
  49227. check_rc(rc)
  49228. #--------------------------------------------------------------------------
  49229. # parameter attributes are stored in instance variable @parmArray so that
  49230. # they are available when execute method is called.
  49231. #--------------------------------------------------------------------------
  49232. if @numParms > 0 # get parameter marker attributes
  49233. 1.upto(@numParms) do |i| # parameter number starts from 1
  49234. rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
  49235. check_rc(rc)
  49236. @parmArray << Parameter.new(type, size, decimalDigits)
  49237. end
  49238. end
  49239. @prepared = true
  49240. self
  49241. end
  49242. def execute(*parms)
  49243. raise "The statement was not prepared" if @prepared == false
  49244. if parms.size == 1 and parms[0].class == Array
  49245. parms = parms[0]
  49246. end
  49247. if @numParms != parms.size
  49248. raise "Number of parameters supplied does not match with the SQL statement"
  49249. end
  49250. if @numParms > 0 #need to bind parameters
  49251. #--------------------------------------------------------------------
  49252. #calling bindParms may not be safe. Look comment below.
  49253. #--------------------------------------------------------------------
  49254. #bindParms(parms)
  49255. valueArray = []
  49256. 1.upto(@numParms) do |i| # parameter number starts from 1
  49257. type = @parmArray[i - 1].class
  49258. size = @parmArray[i - 1].size
  49259. decimalDigits = @parmArray[i - 1].decimalDigits
  49260. if parms[i - 1].class == String
  49261. valueArray << parms[i - 1]
  49262. else
  49263. valueArray << parms[i - 1].to_s
  49264. end
  49265. rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1])
  49266. check_rc(rc)
  49267. end
  49268. end
  49269. check_rc(SQLExecute(@handle))
  49270. if @numParms != 0
  49271. check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters
  49272. end
  49273. self
  49274. end
  49275. #-------------------------------------------------------------------------------
  49276. # The last argument(value) to SQLBindParameter is a deferred argument, that is,
  49277. # it should be available when SQLExecute is called. Even though "value" is
  49278. # local to bindParms method, it seems that it is available when SQLExecute
  49279. # is called. I am not sure whether it would still work if garbage collection
  49280. # is done between bindParms call and SQLExecute call inside the execute method
  49281. # above.
  49282. #-------------------------------------------------------------------------------
  49283. def bindParms(parms) # This is the real thing. It uses SQLBindParms
  49284. 1.upto(@numParms) do |i| # parameter number starts from 1
  49285. rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
  49286. check_rc(rc)
  49287. if parms[i - 1].class == String
  49288. value = parms[i - 1]
  49289. else
  49290. value = parms[i - 1].to_s
  49291. end
  49292. rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value)
  49293. check_rc(rc)
  49294. end
  49295. end
  49296. #------------------------------------------------------------------------------
  49297. # bind method does not use DB2's SQLBindParams, but replaces "?" in the
  49298. # SQL statement with the value before passing the SQL statement to DB2.
  49299. # It is not efficient and can handle only strings since it puts everything in
  49300. # quotes.
  49301. #------------------------------------------------------------------------------
  49302. def bind(sql, args) #does not use SQLBindParams
  49303. arg_index = 0
  49304. result = ""
  49305. tokens(sql).each do |part|
  49306. case part
  49307. when '?'
  49308. result << "'" + (args[arg_index]) + "'" #put it into quotes
  49309. arg_index += 1
  49310. when '??'
  49311. result << "?"
  49312. else
  49313. result << part
  49314. end
  49315. end
  49316. if arg_index < args.size
  49317. raise "Too many SQL parameters"
  49318. elsif arg_index > args.size
  49319. raise "Not enough SQL parameters"
  49320. end
  49321. result
  49322. end
  49323. ## Break the sql string into parts.
  49324. #
  49325. # This is NOT a full lexer for SQL. It just breaks up the SQL
  49326. # string enough so that question marks, double question marks and
  49327. # quoted strings are separated. This is used when binding
  49328. # arguments to "?" in the SQL string. Note: comments are not
  49329. # handled.
  49330. #
  49331. def tokens(sql)
  49332. toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/)
  49333. toks.collect { |t| t[0] }
  49334. end
  49335. def exec_direct(sql)
  49336. check_rc(SQLExecDirect(@handle, sql))
  49337. self
  49338. end
  49339. def set_cursor_name(name)
  49340. check_rc(SQLSetCursorName(@handle, name))
  49341. self
  49342. end
  49343. def get_cursor_name
  49344. rc, name = SQLGetCursorName(@handle)
  49345. check_rc(rc)
  49346. name
  49347. end
  49348. def row_count
  49349. rc, rowcount = SQLRowCount(@handle)
  49350. check_rc(rc)
  49351. rowcount
  49352. end
  49353. def num_result_cols
  49354. rc, cols = SQLNumResultCols(@handle)
  49355. check_rc(rc)
  49356. cols
  49357. end
  49358. def fetch_all
  49359. if block_given?
  49360. while row = fetch do
  49361. yield row
  49362. end
  49363. else
  49364. res = []
  49365. while row = fetch do
  49366. res << row
  49367. end
  49368. res
  49369. end
  49370. end
  49371. def fetch
  49372. cols = get_col_desc
  49373. rc = SQLFetch(@handle)
  49374. if rc == SQL_NO_DATA_FOUND
  49375. SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
  49376. SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
  49377. return nil
  49378. end
  49379. raise "ERROR" unless rc == SQL_SUCCESS
  49380. retval = []
  49381. cols.each_with_index do |c, i|
  49382. rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
  49383. retval << adjust_content(content)
  49384. end
  49385. retval
  49386. end
  49387. def fetch_as_hash
  49388. cols = get_col_desc
  49389. rc = SQLFetch(@handle)
  49390. if rc == SQL_NO_DATA_FOUND
  49391. SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
  49392. SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
  49393. return nil
  49394. end
  49395. raise "ERROR" unless rc == SQL_SUCCESS
  49396. retval = {}
  49397. cols.each_with_index do |c, i|
  49398. rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
  49399. retval[c[0]] = adjust_content(content)
  49400. end
  49401. retval
  49402. end
  49403. def get_col_desc
  49404. rc, nr_cols = SQLNumResultCols(@handle)
  49405. cols = (1..nr_cols).collect do |c|
  49406. rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
  49407. [name.downcase, type, col_sz]
  49408. end
  49409. end
  49410. def adjust_content(c)
  49411. case c.class.to_s
  49412. when 'DB2CLI::NullClass'
  49413. return nil
  49414. when 'DB2CLI::Time'
  49415. "%02d:%02d:%02d" % [c.hour, c.minute, c.second]
  49416. when 'DB2CLI::Date'
  49417. "%04d-%02d-%02d" % [c.year, c.month, c.day]
  49418. when 'DB2CLI::Timestamp'
  49419. "%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second]
  49420. else
  49421. return c
  49422. end
  49423. end
  49424. end
  49425. class Parameter
  49426. attr_reader :type, :size, :decimalDigits
  49427. def initialize(type, size, decimalDigits)
  49428. @type, @size, @decimalDigits = type, size, decimalDigits
  49429. end
  49430. end
  49431. end
  49432. # $Id: mysql.rb,v 1.24 2005/02/12 11:37:15 tommy Exp $
  49433. #
  49434. # Copyright (C) 2003-2005 TOMITA Masahiro
  49435. # tommy@tmtm.org
  49436. #
  49437. class Mysql
  49438. VERSION = "4.0-ruby-0.2.5"
  49439. require "socket"
  49440. require "digest/sha1"
  49441. MAX_PACKET_LENGTH = 256*256*256-1
  49442. MAX_ALLOWED_PACKET = 1024*1024*1024
  49443. MYSQL_UNIX_ADDR = "/tmp/mysql.sock"
  49444. MYSQL_PORT = 3306
  49445. PROTOCOL_VERSION = 10
  49446. # Command
  49447. COM_SLEEP = 0
  49448. COM_QUIT = 1
  49449. COM_INIT_DB = 2
  49450. COM_QUERY = 3
  49451. COM_FIELD_LIST = 4
  49452. COM_CREATE_DB = 5
  49453. COM_DROP_DB = 6
  49454. COM_REFRESH = 7
  49455. COM_SHUTDOWN = 8
  49456. COM_STATISTICS = 9
  49457. COM_PROCESS_INFO = 10
  49458. COM_CONNECT = 11
  49459. COM_PROCESS_KILL = 12
  49460. COM_DEBUG = 13
  49461. COM_PING = 14
  49462. COM_TIME = 15
  49463. COM_DELAYED_INSERT = 16
  49464. COM_CHANGE_USER = 17
  49465. COM_BINLOG_DUMP = 18
  49466. COM_TABLE_DUMP = 19
  49467. COM_CONNECT_OUT = 20
  49468. COM_REGISTER_SLAVE = 21
  49469. # Client flag
  49470. CLIENT_LONG_PASSWORD = 1
  49471. CLIENT_FOUND_ROWS = 1 << 1
  49472. CLIENT_LONG_FLAG = 1 << 2
  49473. CLIENT_CONNECT_WITH_DB= 1 << 3
  49474. CLIENT_NO_SCHEMA = 1 << 4
  49475. CLIENT_COMPRESS = 1 << 5
  49476. CLIENT_ODBC = 1 << 6
  49477. CLIENT_LOCAL_FILES = 1 << 7
  49478. CLIENT_IGNORE_SPACE = 1 << 8
  49479. CLIENT_PROTOCOL_41 = 1 << 9
  49480. CLIENT_INTERACTIVE = 1 << 10
  49481. CLIENT_SSL = 1 << 11
  49482. CLIENT_IGNORE_SIGPIPE = 1 << 12
  49483. CLIENT_TRANSACTIONS = 1 << 13
  49484. CLIENT_RESERVED = 1 << 14
  49485. CLIENT_SECURE_CONNECTION = 1 << 15
  49486. CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS
  49487. PROTO_AUTH41 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
  49488. # Connection Option
  49489. OPT_CONNECT_TIMEOUT = 0
  49490. OPT_COMPRESS = 1
  49491. OPT_NAMED_PIPE = 2
  49492. INIT_COMMAND = 3
  49493. READ_DEFAULT_FILE = 4
  49494. READ_DEFAULT_GROUP = 5
  49495. SET_CHARSET_DIR = 6
  49496. SET_CHARSET_NAME = 7
  49497. OPT_LOCAL_INFILE = 8
  49498. # Server Status
  49499. SERVER_STATUS_IN_TRANS = 1
  49500. SERVER_STATUS_AUTOCOMMIT = 2
  49501. # Refresh parameter
  49502. REFRESH_GRANT = 1
  49503. REFRESH_LOG = 2
  49504. REFRESH_TABLES = 4
  49505. REFRESH_HOSTS = 8
  49506. REFRESH_STATUS = 16
  49507. REFRESH_THREADS = 32
  49508. REFRESH_SLAVE = 64
  49509. REFRESH_MASTER = 128
  49510. def initialize(*args)
  49511. @client_flag = 0
  49512. @max_allowed_packet = MAX_ALLOWED_PACKET
  49513. @query_with_result = true
  49514. @status = :STATUS_READY
  49515. if args[0] != :INIT then
  49516. real_connect(*args)
  49517. end
  49518. end
  49519. def real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
  49520. @server_status = SERVER_STATUS_AUTOCOMMIT
  49521. if (host == nil or host == "localhost") and defined? UNIXSocket then
  49522. unix_socket = socket || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_ADDR
  49523. sock = UNIXSocket::new(unix_socket)
  49524. @host_info = Error::err(Error::CR_LOCALHOST_CONNECTION)
  49525. @unix_socket = unix_socket
  49526. else
  49527. sock = TCPSocket::new(host, port||ENV["MYSQL_TCP_PORT"]||(Socket::getservbyname("mysql","tcp") rescue MYSQL_PORT))
  49528. @host_info = sprintf Error::err(Error::CR_TCP_CONNECTION), host
  49529. end
  49530. @host = host ? host.dup : nil
  49531. sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true
  49532. @net = Net::new sock
  49533. a = read
  49534. @protocol_version = a.slice!(0)
  49535. @server_version, a = a.split(/\0/,2)
  49536. @thread_id, @scramble_buff = a.slice!(0,13).unpack("La8")
  49537. if a.size >= 2 then
  49538. @server_capabilities, = a.slice!(0,2).unpack("v")
  49539. end
  49540. if a.size >= 16 then
  49541. @server_language, @server_status = a.slice!(0,3).unpack("cv")
  49542. end
  49543. flag = 0 if flag == nil
  49544. flag |= @client_flag | CLIENT_CAPABILITIES
  49545. flag |= CLIENT_CONNECT_WITH_DB if db
  49546. @pre_411 = (0 == @server_capabilities & PROTO_AUTH41)
  49547. if @pre_411
  49548. data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+
  49549. (user||"")+"\0"+
  49550. scramble(passwd, @scramble_buff, @protocol_version==9)
  49551. else
  49552. dummy, @salt2 = a.unpack("a13a12")
  49553. @scramble_buff += @salt2
  49554. flag |= PROTO_AUTH41
  49555. data = Net::int4str(flag) + Net::int4str(@max_allowed_packet) +
  49556. ([8] + Array.new(23, 0)).pack("c24") + (user||"")+"\0"+
  49557. scramble41(passwd, @scramble_buff)
  49558. end
  49559. if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0
  49560. data << "\0" if @pre_411
  49561. data << db
  49562. @db = db.dup
  49563. end
  49564. write data
  49565. read
  49566. ObjectSpace.define_finalizer(self, Mysql.finalizer(@net))
  49567. self
  49568. end
  49569. alias :connect :real_connect
  49570. def escape_string(str)
  49571. Mysql::escape_string str
  49572. end
  49573. alias :quote :escape_string
  49574. def get_client_info()
  49575. VERSION
  49576. end
  49577. alias :client_info :get_client_info
  49578. def options(option, arg=nil)
  49579. if option == OPT_LOCAL_INFILE then
  49580. if arg == false or arg == 0 then
  49581. @client_flag &= ~CLIENT_LOCAL_FILES
  49582. else
  49583. @client_flag |= CLIENT_LOCAL_FILES
  49584. end
  49585. else
  49586. raise "not implemented"
  49587. end
  49588. end
  49589. def real_query(query)
  49590. command COM_QUERY, query, true
  49591. read_query_result
  49592. self
  49593. end
  49594. def use_result()
  49595. if @status != :STATUS_GET_RESULT then
  49596. error Error::CR_COMMANDS_OUT_OF_SYNC
  49597. end
  49598. res = Result::new self, @fields, @field_count
  49599. @status = :STATUS_USE_RESULT
  49600. res
  49601. end
  49602. def store_result()
  49603. if @status != :STATUS_GET_RESULT then
  49604. error Error::CR_COMMANDS_OUT_OF_SYNC
  49605. end
  49606. @status = :STATUS_READY
  49607. data = read_rows @field_count
  49608. res = Result::new self, @fields, @field_count, data
  49609. @fields = nil
  49610. @affected_rows = data.length
  49611. res
  49612. end
  49613. def change_user(user="", passwd="", db="")
  49614. if @pre_411
  49615. data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db
  49616. else
  49617. data = user+"\0"+scramble41(passwd, @scramble_buff)+db
  49618. end
  49619. command COM_CHANGE_USER, data
  49620. @user = user
  49621. @passwd = passwd
  49622. @db = db
  49623. end
  49624. def character_set_name()
  49625. raise "not implemented"
  49626. end
  49627. def close()
  49628. @status = :STATUS_READY
  49629. command COM_QUIT, nil, true
  49630. @net.close
  49631. self
  49632. end
  49633. def create_db(db)
  49634. command COM_CREATE_DB, db
  49635. self
  49636. end
  49637. def drop_db(db)
  49638. command COM_DROP_DB, db
  49639. self
  49640. end
  49641. def dump_debug_info()
  49642. command COM_DEBUG
  49643. self
  49644. end
  49645. def get_host_info()
  49646. @host_info
  49647. end
  49648. alias :host_info :get_host_info
  49649. def get_proto_info()
  49650. @protocol_version
  49651. end
  49652. alias :proto_info :get_proto_info
  49653. def get_server_info()
  49654. @server_version
  49655. end
  49656. alias :server_info :get_server_info
  49657. def kill(id)
  49658. command COM_PROCESS_KILL, Net::int4str(id)
  49659. self
  49660. end
  49661. def list_dbs(db=nil)
  49662. real_query "show databases #{db}"
  49663. @status = :STATUS_READY
  49664. read_rows(1).flatten
  49665. end
  49666. def list_fields(table, field=nil)
  49667. command COM_FIELD_LIST, "#{table}\0#{field}", true
  49668. if @pre_411
  49669. f = read_rows 6
  49670. else
  49671. f = read_rows 7
  49672. end
  49673. fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0)
  49674. res = Result::new self, fields, f.length
  49675. res.eof = true
  49676. res
  49677. end
  49678. def list_processes()
  49679. data = command COM_PROCESS_INFO
  49680. @field_count = get_length data
  49681. if @pre_411
  49682. fields = read_rows 5
  49683. else
  49684. fields = read_rows 7
  49685. end
  49686. @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
  49687. @status = :STATUS_GET_RESULT
  49688. store_result
  49689. end
  49690. def list_tables(table=nil)
  49691. real_query "show tables #{table}"
  49692. @status = :STATUS_READY
  49693. read_rows(1).flatten
  49694. end
  49695. def ping()
  49696. command COM_PING
  49697. self
  49698. end
  49699. def query(query)
  49700. real_query query
  49701. if not @query_with_result then
  49702. return self
  49703. end
  49704. if @field_count == 0 then
  49705. return nil
  49706. end
  49707. store_result
  49708. end
  49709. def refresh(r)
  49710. command COM_REFRESH, r.chr
  49711. self
  49712. end
  49713. def reload()
  49714. refresh REFRESH_GRANT
  49715. self
  49716. end
  49717. def select_db(db)
  49718. command COM_INIT_DB, db
  49719. @db = db
  49720. self
  49721. end
  49722. def shutdown()
  49723. command COM_SHUTDOWN
  49724. self
  49725. end
  49726. def stat()
  49727. command COM_STATISTICS
  49728. end
  49729. attr_reader :info, :insert_id, :affected_rows, :field_count, :thread_id
  49730. attr_accessor :query_with_result, :status
  49731. def read_one_row(field_count)
  49732. data = read
  49733. if data[0] == 254 and data.length == 1 ## EOF
  49734. return
  49735. elsif data[0] == 254 and data.length == 5
  49736. return
  49737. end
  49738. rec = []
  49739. field_count.times do
  49740. len = get_length data
  49741. if len == nil then
  49742. rec << len
  49743. else
  49744. rec << data.slice!(0,len)
  49745. end
  49746. end
  49747. rec
  49748. end
  49749. def skip_result()
  49750. if @status == :STATUS_USE_RESULT then
  49751. loop do
  49752. data = read
  49753. break if data[0] == 254 and data.length == 1
  49754. end
  49755. @status = :STATUS_READY
  49756. end
  49757. end
  49758. def inspect()
  49759. "#<#{self.class}>"
  49760. end
  49761. private
  49762. def read_query_result()
  49763. data = read
  49764. @field_count = get_length(data)
  49765. if @field_count == nil then # LOAD DATA LOCAL INFILE
  49766. File::open(data) do |f|
  49767. write f.read
  49768. end
  49769. write "" # mark EOF
  49770. data = read
  49771. @field_count = get_length(data)
  49772. end
  49773. if @field_count == 0 then
  49774. @affected_rows = get_length(data, true)
  49775. @insert_id = get_length(data, true)
  49776. if @server_capabilities & CLIENT_TRANSACTIONS != 0 then
  49777. a = data.slice!(0,2)
  49778. @server_status = a[0]+a[1]*256
  49779. end
  49780. if data.size > 0 and get_length(data) then
  49781. @info = data
  49782. end
  49783. else
  49784. @extra_info = get_length(data, true)
  49785. if @pre_411
  49786. fields = read_rows(5)
  49787. else
  49788. fields = read_rows(7)
  49789. end
  49790. @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
  49791. @status = :STATUS_GET_RESULT
  49792. end
  49793. self
  49794. end
  49795. def unpack_fields(data, long_flag_protocol)
  49796. ret = []
  49797. data.each do |f|
  49798. if @pre_411
  49799. table = org_table = f[0]
  49800. name = f[1]
  49801. length = f[2][0]+f[2][1]*256+f[2][2]*256*256
  49802. type = f[3][0]
  49803. if long_flag_protocol then
  49804. flags = f[4][0]+f[4][1]*256
  49805. decimals = f[4][2]
  49806. else
  49807. flags = f[4][0]
  49808. decimals = f[4][1]
  49809. end
  49810. def_value = f[5]
  49811. max_length = 0
  49812. else
  49813. catalog = f[0]
  49814. db = f[1]
  49815. table = f[2]
  49816. org_table = f[3]
  49817. name = f[4]
  49818. org_name = f[5]
  49819. length = f[6][2]+f[6][3]*256+f[6][4]*256*256
  49820. type = f[6][6]
  49821. flags = f[6][7]+f[6][8]*256
  49822. decimals = f[6][9]
  49823. def_value = ""
  49824. max_length = 0
  49825. end
  49826. ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length)
  49827. end
  49828. ret
  49829. end
  49830. def read_rows(field_count)
  49831. ret = []
  49832. while rec = read_one_row(field_count) do
  49833. ret << rec
  49834. end
  49835. ret
  49836. end
  49837. def get_length(data, longlong=nil)
  49838. return if data.length == 0
  49839. c = data.slice!(0)
  49840. case c
  49841. when 251
  49842. return nil
  49843. when 252
  49844. a = data.slice!(0,2)
  49845. return a[0]+a[1]*256
  49846. when 253
  49847. a = data.slice!(0,3)
  49848. return a[0]+a[1]*256+a[2]*256**2
  49849. when 254
  49850. a = data.slice!(0,8)
  49851. if longlong then
  49852. return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3+
  49853. a[4]*256**4+a[5]*256**5+a[6]*256**6+a[7]*256**7
  49854. else
  49855. return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3
  49856. end
  49857. else
  49858. c
  49859. end
  49860. end
  49861. def command(cmd, arg=nil, skip_check=nil)
  49862. unless @net then
  49863. error Error::CR_SERVER_GONE_ERROR
  49864. end
  49865. if @status != :STATUS_READY then
  49866. error Error::CR_COMMANDS_OUT_OF_SYNC
  49867. end
  49868. @net.clear
  49869. write cmd.chr+(arg||"")
  49870. read unless skip_check
  49871. end
  49872. def read()
  49873. unless @net then
  49874. error Error::CR_SERVER_GONE_ERROR
  49875. end
  49876. a = @net.read
  49877. if a[0] == 255 then
  49878. if a.length > 3 then
  49879. @errno = a[1]+a[2]*256
  49880. @error = a[3 .. -1]
  49881. else
  49882. @errno = Error::CR_UNKNOWN_ERROR
  49883. @error = Error::err @errno
  49884. end
  49885. raise Error::new(@errno, @error)
  49886. end
  49887. a
  49888. end
  49889. def write(arg)
  49890. unless @net then
  49891. error Error::CR_SERVER_GONE_ERROR
  49892. end
  49893. @net.write arg
  49894. end
  49895. def hash_password(password)
  49896. nr = 1345345333
  49897. add = 7
  49898. nr2 = 0x12345671
  49899. password.each_byte do |i|
  49900. next if i == 0x20 or i == 9
  49901. nr ^= (((nr & 63) + add) * i) + (nr << 8)
  49902. nr2 += (nr2 << 8) ^ nr
  49903. add += i
  49904. end
  49905. [nr & ((1 << 31) - 1), nr2 & ((1 << 31) - 1)]
  49906. end
  49907. def scramble(password, message, old_ver)
  49908. return "" if password == nil or password == ""
  49909. raise "old version password is not implemented" if old_ver
  49910. hash_pass = hash_password password
  49911. hash_message = hash_password message
  49912. rnd = Random::new hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1]
  49913. to = []
  49914. 1.upto(message.length) do
  49915. to << ((rnd.rnd*31)+64).floor
  49916. end
  49917. extra = (rnd.rnd*31).floor
  49918. to.map! do |t| (t ^ extra).chr end
  49919. to.join
  49920. end
  49921. def scramble41(password, message)
  49922. return 0x00.chr if password.nil? or password.empty?
  49923. buf = [0x14]
  49924. s1 = Digest::SHA1.new(password).digest
  49925. s2 = Digest::SHA1.new(s1).digest
  49926. x = Digest::SHA1.new(message + s2).digest
  49927. (0..s1.length - 1).each {|i| buf.push(s1[i] ^ x[i])}
  49928. buf.pack("C*")
  49929. end
  49930. def error(errno)
  49931. @errno = errno
  49932. @error = Error::err errno
  49933. raise Error::new(@errno, @error)
  49934. end
  49935. class Result
  49936. def initialize(mysql, fields, field_count, data=nil)
  49937. @handle = mysql
  49938. @fields = fields
  49939. @field_count = field_count
  49940. @data = data
  49941. @current_field = 0
  49942. @current_row = 0
  49943. @eof = false
  49944. @row_count = 0
  49945. end
  49946. attr_accessor :eof
  49947. def data_seek(n)
  49948. @current_row = n
  49949. end
  49950. def fetch_field()
  49951. return if @current_field >= @field_count
  49952. f = @fields[@current_field]
  49953. @current_field += 1
  49954. f
  49955. end
  49956. def fetch_fields()
  49957. @fields
  49958. end
  49959. def fetch_field_direct(n)
  49960. @fields[n]
  49961. end
  49962. def fetch_lengths()
  49963. @data ? @data[@current_row].map{|i| i ? i.length : 0} : @lengths
  49964. end
  49965. def fetch_row()
  49966. if @data then
  49967. if @current_row >= @data.length then
  49968. @handle.status = :STATUS_READY
  49969. return
  49970. end
  49971. ret = @data[@current_row]
  49972. @current_row += 1
  49973. else
  49974. return if @eof
  49975. ret = @handle.read_one_row @field_count
  49976. if ret == nil then
  49977. @eof = true
  49978. return
  49979. end
  49980. @lengths = ret.map{|i| i ? i.length : 0}
  49981. @row_count += 1
  49982. end
  49983. ret
  49984. end
  49985. def fetch_hash(with_table=nil)
  49986. row = fetch_row
  49987. return if row == nil
  49988. hash = {}
  49989. @fields.each_index do |i|
  49990. f = with_table ? @fields[i].table+"."+@fields[i].name : @fields[i].name
  49991. hash[f] = row[i]
  49992. end
  49993. hash
  49994. end
  49995. def field_seek(n)
  49996. @current_field = n
  49997. end
  49998. def field_tell()
  49999. @current_field
  50000. end
  50001. def free()
  50002. @handle.skip_result
  50003. @handle = @fields = @data = nil
  50004. end
  50005. def num_fields()
  50006. @field_count
  50007. end
  50008. def num_rows()
  50009. @data ? @data.length : @row_count
  50010. end
  50011. def row_seek(n)
  50012. @current_row = n
  50013. end
  50014. def row_tell()
  50015. @current_row
  50016. end
  50017. def each()
  50018. while row = fetch_row do
  50019. yield row
  50020. end
  50021. end
  50022. def each_hash(with_table=nil)
  50023. while hash = fetch_hash(with_table) do
  50024. yield hash
  50025. end
  50026. end
  50027. def inspect()
  50028. "#<#{self.class}>"
  50029. end
  50030. end
  50031. class Field
  50032. # Field type
  50033. TYPE_DECIMAL = 0
  50034. TYPE_TINY = 1
  50035. TYPE_SHORT = 2
  50036. TYPE_LONG = 3
  50037. TYPE_FLOAT = 4
  50038. TYPE_DOUBLE = 5
  50039. TYPE_NULL = 6
  50040. TYPE_TIMESTAMP = 7
  50041. TYPE_LONGLONG = 8
  50042. TYPE_INT24 = 9
  50043. TYPE_DATE = 10
  50044. TYPE_TIME = 11
  50045. TYPE_DATETIME = 12
  50046. TYPE_YEAR = 13
  50047. TYPE_NEWDATE = 14
  50048. TYPE_ENUM = 247
  50049. TYPE_SET = 248
  50050. TYPE_TINY_BLOB = 249
  50051. TYPE_MEDIUM_BLOB = 250
  50052. TYPE_LONG_BLOB = 251
  50053. TYPE_BLOB = 252
  50054. TYPE_VAR_STRING = 253
  50055. TYPE_STRING = 254
  50056. TYPE_GEOMETRY = 255
  50057. TYPE_CHAR = TYPE_TINY
  50058. TYPE_INTERVAL = TYPE_ENUM
  50059. # Flag
  50060. NOT_NULL_FLAG = 1
  50061. PRI_KEY_FLAG = 2
  50062. UNIQUE_KEY_FLAG = 4
  50063. MULTIPLE_KEY_FLAG = 8
  50064. BLOB_FLAG = 16
  50065. UNSIGNED_FLAG = 32
  50066. ZEROFILL_FLAG = 64
  50067. BINARY_FLAG = 128
  50068. ENUM_FLAG = 256
  50069. AUTO_INCREMENT_FLAG = 512
  50070. TIMESTAMP_FLAG = 1024
  50071. SET_FLAG = 2048
  50072. NUM_FLAG = 32768
  50073. PART_KEY_FLAG = 16384
  50074. GROUP_FLAG = 32768
  50075. UNIQUE_FLAG = 65536
  50076. def initialize(table, org_table, name, length, type, flags, decimals, def_value, max_length)
  50077. @table = table
  50078. @org_table = org_table
  50079. @name = name
  50080. @length = length
  50081. @type = type
  50082. @flags = flags
  50083. @decimals = decimals
  50084. @def = def_value
  50085. @max_length = max_length
  50086. if (type <= TYPE_INT24 and (type != TYPE_TIMESTAMP or length == 14 or length == 8)) or type == TYPE_YEAR then
  50087. @flags |= NUM_FLAG
  50088. end
  50089. end
  50090. attr_reader :table, :org_table, :name, :length, :type, :flags, :decimals, :def, :max_length
  50091. def inspect()
  50092. "#<#{self.class}:#{@name}>"
  50093. end
  50094. end
  50095. class Error < StandardError
  50096. # Server Error
  50097. ER_HASHCHK = 1000
  50098. ER_NISAMCHK = 1001
  50099. ER_NO = 1002
  50100. ER_YES = 1003
  50101. ER_CANT_CREATE_FILE = 1004
  50102. ER_CANT_CREATE_TABLE = 1005
  50103. ER_CANT_CREATE_DB = 1006
  50104. ER_DB_CREATE_EXISTS = 1007
  50105. ER_DB_DROP_EXISTS = 1008
  50106. ER_DB_DROP_DELETE = 1009
  50107. ER_DB_DROP_RMDIR = 1010
  50108. ER_CANT_DELETE_FILE = 1011
  50109. ER_CANT_FIND_SYSTEM_REC = 1012
  50110. ER_CANT_GET_STAT = 1013
  50111. ER_CANT_GET_WD = 1014
  50112. ER_CANT_LOCK = 1015
  50113. ER_CANT_OPEN_FILE = 1016
  50114. ER_FILE_NOT_FOUND = 1017
  50115. ER_CANT_READ_DIR = 1018
  50116. ER_CANT_SET_WD = 1019
  50117. ER_CHECKREAD = 1020
  50118. ER_DISK_FULL = 1021
  50119. ER_DUP_KEY = 1022
  50120. ER_ERROR_ON_CLOSE = 1023
  50121. ER_ERROR_ON_READ = 1024
  50122. ER_ERROR_ON_RENAME = 1025
  50123. ER_ERROR_ON_WRITE = 1026
  50124. ER_FILE_USED = 1027
  50125. ER_FILSORT_ABORT = 1028
  50126. ER_FORM_NOT_FOUND = 1029
  50127. ER_GET_ERRNO = 1030
  50128. ER_ILLEGAL_HA = 1031
  50129. ER_KEY_NOT_FOUND = 1032
  50130. ER_NOT_FORM_FILE = 1033
  50131. ER_NOT_KEYFILE = 1034
  50132. ER_OLD_KEYFILE = 1035
  50133. ER_OPEN_AS_READONLY = 1036
  50134. ER_OUTOFMEMORY = 1037
  50135. ER_OUT_OF_SORTMEMORY = 1038
  50136. ER_UNEXPECTED_EOF = 1039
  50137. ER_CON_COUNT_ERROR = 1040
  50138. ER_OUT_OF_RESOURCES = 1041
  50139. ER_BAD_HOST_ERROR = 1042
  50140. ER_HANDSHAKE_ERROR = 1043
  50141. ER_DBACCESS_DENIED_ERROR = 1044
  50142. ER_ACCESS_DENIED_ERROR = 1045
  50143. ER_NO_DB_ERROR = 1046
  50144. ER_UNKNOWN_COM_ERROR = 1047
  50145. ER_BAD_NULL_ERROR = 1048
  50146. ER_BAD_DB_ERROR = 1049
  50147. ER_TABLE_EXISTS_ERROR = 1050
  50148. ER_BAD_TABLE_ERROR = 1051
  50149. ER_NON_UNIQ_ERROR = 1052
  50150. ER_SERVER_SHUTDOWN = 1053
  50151. ER_BAD_FIELD_ERROR = 1054
  50152. ER_WRONG_FIELD_WITH_GROUP = 1055
  50153. ER_WRONG_GROUP_FIELD = 1056
  50154. ER_WRONG_SUM_SELECT = 1057
  50155. ER_WRONG_VALUE_COUNT = 1058
  50156. ER_TOO_LONG_IDENT = 1059
  50157. ER_DUP_FIELDNAME = 1060
  50158. ER_DUP_KEYNAME = 1061
  50159. ER_DUP_ENTRY = 1062
  50160. ER_WRONG_FIELD_SPEC = 1063
  50161. ER_PARSE_ERROR = 1064
  50162. ER_EMPTY_QUERY = 1065
  50163. ER_NONUNIQ_TABLE = 1066
  50164. ER_INVALID_DEFAULT = 1067
  50165. ER_MULTIPLE_PRI_KEY = 1068
  50166. ER_TOO_MANY_KEYS = 1069
  50167. ER_TOO_MANY_KEY_PARTS = 1070
  50168. ER_TOO_LONG_KEY = 1071
  50169. ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
  50170. ER_BLOB_USED_AS_KEY = 1073
  50171. ER_TOO_BIG_FIELDLENGTH = 1074
  50172. ER_WRONG_AUTO_KEY = 1075
  50173. ER_READY = 1076
  50174. ER_NORMAL_SHUTDOWN = 1077
  50175. ER_GOT_SIGNAL = 1078
  50176. ER_SHUTDOWN_COMPLETE = 1079
  50177. ER_FORCING_CLOSE = 1080
  50178. ER_IPSOCK_ERROR = 1081
  50179. ER_NO_SUCH_INDEX = 1082
  50180. ER_WRONG_FIELD_TERMINATORS = 1083
  50181. ER_BLOBS_AND_NO_TERMINATED = 1084
  50182. ER_TEXTFILE_NOT_READABLE = 1085
  50183. ER_FILE_EXISTS_ERROR = 1086
  50184. ER_LOAD_INFO = 1087
  50185. ER_ALTER_INFO = 1088
  50186. ER_WRONG_SUB_KEY = 1089
  50187. ER_CANT_REMOVE_ALL_FIELDS = 1090
  50188. ER_CANT_DROP_FIELD_OR_KEY = 1091
  50189. ER_INSERT_INFO = 1092
  50190. ER_INSERT_TABLE_USED = 1093
  50191. ER_NO_SUCH_THREAD = 1094
  50192. ER_KILL_DENIED_ERROR = 1095
  50193. ER_NO_TABLES_USED = 1096
  50194. ER_TOO_BIG_SET = 1097
  50195. ER_NO_UNIQUE_LOGFILE = 1098
  50196. ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
  50197. ER_TABLE_NOT_LOCKED = 1100
  50198. ER_BLOB_CANT_HAVE_DEFAULT = 1101
  50199. ER_WRONG_DB_NAME = 1102
  50200. ER_WRONG_TABLE_NAME = 1103
  50201. ER_TOO_BIG_SELECT = 1104
  50202. ER_UNKNOWN_ERROR = 1105
  50203. ER_UNKNOWN_PROCEDURE = 1106
  50204. ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
  50205. ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
  50206. ER_UNKNOWN_TABLE = 1109
  50207. ER_FIELD_SPECIFIED_TWICE = 1110
  50208. ER_INVALID_GROUP_FUNC_USE = 1111
  50209. ER_UNSUPPORTED_EXTENSION = 1112
  50210. ER_TABLE_MUST_HAVE_COLUMNS = 1113
  50211. ER_RECORD_FILE_FULL = 1114
  50212. ER_UNKNOWN_CHARACTER_SET = 1115
  50213. ER_TOO_MANY_TABLES = 1116
  50214. ER_TOO_MANY_FIELDS = 1117
  50215. ER_TOO_BIG_ROWSIZE = 1118
  50216. ER_STACK_OVERRUN = 1119
  50217. ER_WRONG_OUTER_JOIN = 1120
  50218. ER_NULL_COLUMN_IN_INDEX = 1121
  50219. ER_CANT_FIND_UDF = 1122
  50220. ER_CANT_INITIALIZE_UDF = 1123
  50221. ER_UDF_NO_PATHS = 1124
  50222. ER_UDF_EXISTS = 1125
  50223. ER_CANT_OPEN_LIBRARY = 1126
  50224. ER_CANT_FIND_DL_ENTRY = 1127
  50225. ER_FUNCTION_NOT_DEFINED = 1128
  50226. ER_HOST_IS_BLOCKED = 1129
  50227. ER_HOST_NOT_PRIVILEGED = 1130
  50228. ER_PASSWORD_ANONYMOUS_USER = 1131
  50229. ER_PASSWORD_NOT_ALLOWED = 1132
  50230. ER_PASSWORD_NO_MATCH = 1133
  50231. ER_UPDATE_INFO = 1134
  50232. ER_CANT_CREATE_THREAD = 1135
  50233. ER_WRONG_VALUE_COUNT_ON_ROW = 1136
  50234. ER_CANT_REOPEN_TABLE = 1137
  50235. ER_INVALID_USE_OF_NULL = 1138
  50236. ER_REGEXP_ERROR = 1139
  50237. ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
  50238. ER_NONEXISTING_GRANT = 1141
  50239. ER_TABLEACCESS_DENIED_ERROR = 1142
  50240. ER_COLUMNACCESS_DENIED_ERROR = 1143
  50241. ER_ILLEGAL_GRANT_FOR_TABLE = 1144
  50242. ER_GRANT_WRONG_HOST_OR_USER = 1145
  50243. ER_NO_SUCH_TABLE = 1146
  50244. ER_NONEXISTING_TABLE_GRANT = 1147
  50245. ER_NOT_ALLOWED_COMMAND = 1148
  50246. ER_SYNTAX_ERROR = 1149
  50247. ER_DELAYED_CANT_CHANGE_LOCK = 1150
  50248. ER_TOO_MANY_DELAYED_THREADS = 1151
  50249. ER_ABORTING_CONNECTION = 1152
  50250. ER_NET_PACKET_TOO_LARGE = 1153
  50251. ER_NET_READ_ERROR_FROM_PIPE = 1154
  50252. ER_NET_FCNTL_ERROR = 1155
  50253. ER_NET_PACKETS_OUT_OF_ORDER = 1156
  50254. ER_NET_UNCOMPRESS_ERROR = 1157
  50255. ER_NET_READ_ERROR = 1158
  50256. ER_NET_READ_INTERRUPTED = 1159
  50257. ER_NET_ERROR_ON_WRITE = 1160
  50258. ER_NET_WRITE_INTERRUPTED = 1161
  50259. ER_TOO_LONG_STRING = 1162
  50260. ER_TABLE_CANT_HANDLE_BLOB = 1163
  50261. ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
  50262. ER_DELAYED_INSERT_TABLE_LOCKED = 1165
  50263. ER_WRONG_COLUMN_NAME = 1166
  50264. ER_WRONG_KEY_COLUMN = 1167
  50265. ER_WRONG_MRG_TABLE = 1168
  50266. ER_DUP_UNIQUE = 1169
  50267. ER_BLOB_KEY_WITHOUT_LENGTH = 1170
  50268. ER_PRIMARY_CANT_HAVE_NULL = 1171
  50269. ER_TOO_MANY_ROWS = 1172
  50270. ER_REQUIRES_PRIMARY_KEY = 1173
  50271. ER_NO_RAID_COMPILED = 1174
  50272. ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
  50273. ER_KEY_DOES_NOT_EXITS = 1176
  50274. ER_CHECK_NO_SUCH_TABLE = 1177
  50275. ER_CHECK_NOT_IMPLEMENTED = 1178
  50276. ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
  50277. ER_ERROR_DURING_COMMIT = 1180
  50278. ER_ERROR_DURING_ROLLBACK = 1181
  50279. ER_ERROR_DURING_FLUSH_LOGS = 1182
  50280. ER_ERROR_DURING_CHECKPOINT = 1183
  50281. ER_NEW_ABORTING_CONNECTION = 1184
  50282. ER_DUMP_NOT_IMPLEMENTED = 1185
  50283. ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
  50284. ER_INDEX_REBUILD = 1187
  50285. ER_MASTER = 1188
  50286. ER_MASTER_NET_READ = 1189
  50287. ER_MASTER_NET_WRITE = 1190
  50288. ER_FT_MATCHING_KEY_NOT_FOUND = 1191
  50289. ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
  50290. ER_UNKNOWN_SYSTEM_VARIABLE = 1193
  50291. ER_CRASHED_ON_USAGE = 1194
  50292. ER_CRASHED_ON_REPAIR = 1195
  50293. ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
  50294. ER_TRANS_CACHE_FULL = 1197
  50295. ER_SLAVE_MUST_STOP = 1198
  50296. ER_SLAVE_NOT_RUNNING = 1199
  50297. ER_BAD_SLAVE = 1200
  50298. ER_MASTER_INFO = 1201
  50299. ER_SLAVE_THREAD = 1202
  50300. ER_TOO_MANY_USER_CONNECTIONS = 1203
  50301. ER_SET_CONSTANTS_ONLY = 1204
  50302. ER_LOCK_WAIT_TIMEOUT = 1205
  50303. ER_LOCK_TABLE_FULL = 1206
  50304. ER_READ_ONLY_TRANSACTION = 1207
  50305. ER_DROP_DB_WITH_READ_LOCK = 1208
  50306. ER_CREATE_DB_WITH_READ_LOCK = 1209
  50307. ER_WRONG_ARGUMENTS = 1210
  50308. ER_NO_PERMISSION_TO_CREATE_USER = 1211
  50309. ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
  50310. ER_LOCK_DEADLOCK = 1213
  50311. ER_TABLE_CANT_HANDLE_FULLTEXT = 1214
  50312. ER_CANNOT_ADD_FOREIGN = 1215
  50313. ER_NO_REFERENCED_ROW = 1216
  50314. ER_ROW_IS_REFERENCED = 1217
  50315. ER_CONNECT_TO_MASTER = 1218
  50316. ER_QUERY_ON_MASTER = 1219
  50317. ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
  50318. ER_WRONG_USAGE = 1221
  50319. ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
  50320. ER_CANT_UPDATE_WITH_READLOCK = 1223
  50321. ER_MIXING_NOT_ALLOWED = 1224
  50322. ER_DUP_ARGUMENT = 1225
  50323. ER_USER_LIMIT_REACHED = 1226
  50324. ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
  50325. ER_LOCAL_VARIABLE = 1228
  50326. ER_GLOBAL_VARIABLE = 1229
  50327. ER_NO_DEFAULT = 1230
  50328. ER_WRONG_VALUE_FOR_VAR = 1231
  50329. ER_WRONG_TYPE_FOR_VAR = 1232
  50330. ER_VAR_CANT_BE_READ = 1233
  50331. ER_CANT_USE_OPTION_HERE = 1234
  50332. ER_NOT_SUPPORTED_YET = 1235
  50333. ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
  50334. ER_SLAVE_IGNORED_TABLE = 1237
  50335. ER_ERROR_MESSAGES = 238
  50336. # Client Error
  50337. CR_MIN_ERROR = 2000
  50338. CR_MAX_ERROR = 2999
  50339. CR_UNKNOWN_ERROR = 2000
  50340. CR_SOCKET_CREATE_ERROR = 2001
  50341. CR_CONNECTION_ERROR = 2002
  50342. CR_CONN_HOST_ERROR = 2003
  50343. CR_IPSOCK_ERROR = 2004
  50344. CR_UNKNOWN_HOST = 2005
  50345. CR_SERVER_GONE_ERROR = 2006
  50346. CR_VERSION_ERROR = 2007
  50347. CR_OUT_OF_MEMORY = 2008
  50348. CR_WRONG_HOST_INFO = 2009
  50349. CR_LOCALHOST_CONNECTION = 2010
  50350. CR_TCP_CONNECTION = 2011
  50351. CR_SERVER_HANDSHAKE_ERR = 2012
  50352. CR_SERVER_LOST = 2013
  50353. CR_COMMANDS_OUT_OF_SYNC = 2014
  50354. CR_NAMEDPIPE_CONNECTION = 2015
  50355. CR_NAMEDPIPEWAIT_ERROR = 2016
  50356. CR_NAMEDPIPEOPEN_ERROR = 2017
  50357. CR_NAMEDPIPESETSTATE_ERROR = 2018
  50358. CR_CANT_READ_CHARSET = 2019
  50359. CR_NET_PACKET_TOO_LARGE = 2020
  50360. CR_EMBEDDED_CONNECTION = 2021
  50361. CR_PROBE_SLAVE_STATUS = 2022
  50362. CR_PROBE_SLAVE_HOSTS = 2023
  50363. CR_PROBE_SLAVE_CONNECT = 2024
  50364. CR_PROBE_MASTER_CONNECT = 2025
  50365. CR_SSL_CONNECTION_ERROR = 2026
  50366. CR_MALFORMED_PACKET = 2027
  50367. CLIENT_ERRORS = [
  50368. "Unknown MySQL error",
  50369. "Can't create UNIX socket (%d)",
  50370. "Can't connect to local MySQL server through socket '%-.64s' (%d)",
  50371. "Can't connect to MySQL server on '%-.64s' (%d)",
  50372. "Can't create TCP/IP socket (%d)",
  50373. "Unknown MySQL Server Host '%-.64s' (%d)",
  50374. "MySQL server has gone away",
  50375. "Protocol mismatch. Server Version = %d Client Version = %d",
  50376. "MySQL client run out of memory",
  50377. "Wrong host info",
  50378. "Localhost via UNIX socket",
  50379. "%-.64s via TCP/IP",
  50380. "Error in server handshake",
  50381. "Lost connection to MySQL server during query",
  50382. "Commands out of sync; You can't run this command now",
  50383. "%-.64s via named pipe",
  50384. "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)",
  50385. "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)",
  50386. "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)",
  50387. "Can't initialize character set %-.64s (path: %-.64s)",
  50388. "Got packet bigger than 'max_allowed_packet'",
  50389. "Embedded server",
  50390. "Error on SHOW SLAVE STATUS:",
  50391. "Error on SHOW SLAVE HOSTS:",
  50392. "Error connecting to slave:",
  50393. "Error connecting to master:",
  50394. "SSL connection error",
  50395. "Malformed packet"
  50396. ]
  50397. def initialize(errno, error)
  50398. @errno = errno
  50399. @error = error
  50400. super error
  50401. end
  50402. attr_reader :errno, :error
  50403. def Error::err(errno)
  50404. CLIENT_ERRORS[errno - Error::CR_MIN_ERROR]
  50405. end
  50406. end
  50407. class Net
  50408. def initialize(sock)
  50409. @sock = sock
  50410. @pkt_nr = 0
  50411. end
  50412. def clear()
  50413. @pkt_nr = 0
  50414. end
  50415. def read()
  50416. buf = []
  50417. len = nil
  50418. @sock.sync = false
  50419. while len == nil or len == MAX_PACKET_LENGTH do
  50420. a = @sock.read(4)
  50421. len = a[0]+a[1]*256+a[2]*256*256
  50422. pkt_nr = a[3]
  50423. if @pkt_nr != pkt_nr then
  50424. raise "Packets out of order: #{@pkt_nr}<>#{pkt_nr}"
  50425. end
  50426. @pkt_nr = @pkt_nr + 1 & 0xff
  50427. buf << @sock.read(len)
  50428. end
  50429. @sock.sync = true
  50430. buf.join
  50431. rescue
  50432. errno = Error::CR_SERVER_LOST
  50433. raise Error::new(errno, Error::err(errno))
  50434. end
  50435. def write(data)
  50436. if data.is_a? Array then
  50437. data = data.join
  50438. end
  50439. @sock.sync = false
  50440. ptr = 0
  50441. while data.length >= MAX_PACKET_LENGTH do
  50442. @sock.write Net::int3str(MAX_PACKET_LENGTH)+@pkt_nr.chr+data[ptr, MAX_PACKET_LENGTH]
  50443. @pkt_nr = @pkt_nr + 1 & 0xff
  50444. ptr += MAX_PACKET_LENGTH
  50445. end
  50446. @sock.write Net::int3str(data.length-ptr)+@pkt_nr.chr+data[ptr .. -1]
  50447. @pkt_nr = @pkt_nr + 1 & 0xff
  50448. @sock.sync = true
  50449. @sock.flush
  50450. rescue
  50451. errno = Error::CR_SERVER_LOST
  50452. raise Error::new(errno, Error::err(errno))
  50453. end
  50454. def close()
  50455. @sock.close
  50456. end
  50457. def Net::int2str(n)
  50458. [n].pack("v")
  50459. end
  50460. def Net::int3str(n)
  50461. [n%256, n>>8].pack("cv")
  50462. end
  50463. def Net::int4str(n)
  50464. [n].pack("V")
  50465. end
  50466. end
  50467. class Random
  50468. def initialize(seed1, seed2)
  50469. @max_value = 0x3FFFFFFF
  50470. @seed1 = seed1 % @max_value
  50471. @seed2 = seed2 % @max_value
  50472. end
  50473. def rnd()
  50474. @seed1 = (@seed1*3+@seed2) % @max_value
  50475. @seed2 = (@seed1+@seed2+33) % @max_value
  50476. @seed1.to_f / @max_value
  50477. end
  50478. end
  50479. end
  50480. class << Mysql
  50481. def init()
  50482. Mysql::new :INIT
  50483. end
  50484. def real_connect(*args)
  50485. Mysql::new(*args)
  50486. end
  50487. alias :connect :real_connect
  50488. def finalizer(net)
  50489. proc {
  50490. net.clear
  50491. net.write Mysql::COM_QUIT.chr
  50492. }
  50493. end
  50494. def escape_string(str)
  50495. str.gsub(/([\0\n\r\032\'\"\\])/) do
  50496. case $1
  50497. when "\0" then "\\0"
  50498. when "\n" then "\\n"
  50499. when "\r" then "\\r"
  50500. when "\032" then "\\Z"
  50501. else "\\"+$1
  50502. end
  50503. end
  50504. end
  50505. alias :quote :escape_string
  50506. def get_client_info()
  50507. Mysql::VERSION
  50508. end
  50509. alias :client_info :get_client_info
  50510. def debug(str)
  50511. raise "not implemented"
  50512. end
  50513. end
  50514. #
  50515. # for compatibility
  50516. #
  50517. MysqlRes = Mysql::Result
  50518. MysqlField = Mysql::Field
  50519. MysqlError = Mysql::Error
  50520. # :title: Transaction::Simple -- Active Object Transaction Support for Ruby
  50521. # :main: Transaction::Simple
  50522. #
  50523. # == Licence
  50524. #
  50525. # Permission is hereby granted, free of charge, to any person obtaining a
  50526. # copy of this software and associated documentation files (the "Software"),
  50527. # to deal in the Software without restriction, including without limitation
  50528. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  50529. # and/or sell copies of the Software, and to permit persons to whom the
  50530. # Software is furnished to do so, subject to the following conditions:
  50531. #
  50532. # The above copyright notice and this permission notice shall be included in
  50533. # all copies or substantial portions of the Software.
  50534. #
  50535. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  50536. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  50537. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  50538. # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  50539. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  50540. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  50541. # DEALINGS IN THE SOFTWARE.
  50542. #--
  50543. # Transaction::Simple
  50544. # Simple object transaction support for Ruby
  50545. # Version 1.3.0
  50546. #
  50547. # Copyright (c) 2003 - 2005 Austin Ziegler
  50548. #
  50549. # $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $
  50550. #++
  50551. # The "Transaction" namespace can be used for additional transaction
  50552. # support objects and modules.
  50553. module Transaction
  50554. # A standard exception for transaction errors.
  50555. class TransactionError < StandardError; end
  50556. # The TransactionAborted exception is used to indicate when a
  50557. # transaction has been aborted in the block form.
  50558. class TransactionAborted < Exception; end
  50559. # The TransactionCommitted exception is used to indicate when a
  50560. # transaction has been committed in the block form.
  50561. class TransactionCommitted < Exception; end
  50562. te = "Transaction Error: %s"
  50563. Messages = {
  50564. :bad_debug_object =>
  50565. te % "the transaction debug object must respond to #<<.",
  50566. :unique_names =>
  50567. te % "named transactions must be unique.",
  50568. :no_transaction_open =>
  50569. te % "no transaction open.",
  50570. :cannot_rewind_no_transaction =>
  50571. te % "cannot rewind; there is no current transaction.",
  50572. :cannot_rewind_named_transaction =>
  50573. te % "cannot rewind to transaction %s because it does not exist.",
  50574. :cannot_rewind_transaction_before_block =>
  50575. te % "cannot rewind a transaction started before the execution block.",
  50576. :cannot_abort_no_transaction =>
  50577. te % "cannot abort; there is no current transaction.",
  50578. :cannot_abort_transaction_before_block =>
  50579. te % "cannot abort a transaction started before the execution block.",
  50580. :cannot_abort_named_transaction =>
  50581. te % "cannot abort nonexistant transaction %s.",
  50582. :cannot_commit_no_transaction =>
  50583. te % "cannot commit; there is no current transaction.",
  50584. :cannot_commit_transaction_before_block =>
  50585. te % "cannot commit a transaction started before the execution block.",
  50586. :cannot_commit_named_transaction =>
  50587. te % "cannot commit nonexistant transaction %s.",
  50588. :cannot_start_empty_block_transaction =>
  50589. te % "cannot start a block transaction with no objects.",
  50590. :cannot_obtain_transaction_lock =>
  50591. te % "cannot obtain transaction lock for #%s.",
  50592. }
  50593. # = Transaction::Simple for Ruby
  50594. # Simple object transaction support for Ruby
  50595. #
  50596. # == Introduction
  50597. # Transaction::Simple provides a generic way to add active transaction
  50598. # support to objects. The transaction methods added by this module will
  50599. # work with most objects, excluding those that cannot be
  50600. # <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
  50601. # singleton objects).
  50602. #
  50603. # The transactions supported by Transaction::Simple are not backed
  50604. # transactions; they are not associated with any sort of data store.
  50605. # They are "live" transactions occurring in memory and in the object
  50606. # itself. This is to allow "test" changes to be made to an object
  50607. # before making the changes permanent.
  50608. #
  50609. # Transaction::Simple can handle an "infinite" number of transaction
  50610. # levels (limited only by memory). If I open two transactions, commit
  50611. # the second, but abort the first, the object will revert to the
  50612. # original version.
  50613. #
  50614. # Transaction::Simple supports "named" transactions, so that multiple
  50615. # levels of transactions can be committed, aborted, or rewound by
  50616. # referring to the appropriate name of the transaction. Names may be any
  50617. # object *except* +nil+. As with Hash keys, String names will be
  50618. # duplicated and frozen before using.
  50619. #
  50620. # Copyright:: Copyright © 2003 - 2005 by Austin Ziegler
  50621. # Version:: 1.3.0
  50622. # Licence:: MIT-Style
  50623. #
  50624. # Thanks to David Black for help with the initial concept that led to
  50625. # this library.
  50626. #
  50627. # == Usage
  50628. # include 'transaction/simple'
  50629. #
  50630. # v = "Hello, you." # -> "Hello, you."
  50631. # v.extend(Transaction::Simple) # -> "Hello, you."
  50632. #
  50633. # v.start_transaction # -> ... (a Marshal string)
  50634. # v.transaction_open? # -> true
  50635. # v.gsub!(/you/, "world") # -> "Hello, world."
  50636. #
  50637. # v.rewind_transaction # -> "Hello, you."
  50638. # v.transaction_open? # -> true
  50639. #
  50640. # v.gsub!(/you/, "HAL") # -> "Hello, HAL."
  50641. # v.abort_transaction # -> "Hello, you."
  50642. # v.transaction_open? # -> false
  50643. #
  50644. # v.start_transaction # -> ... (a Marshal string)
  50645. # v.start_transaction # -> ... (a Marshal string)
  50646. #
  50647. # v.transaction_open? # -> true
  50648. # v.gsub!(/you/, "HAL") # -> "Hello, HAL."
  50649. #
  50650. # v.commit_transaction # -> "Hello, HAL."
  50651. # v.transaction_open? # -> true
  50652. # v.abort_transaction # -> "Hello, you."
  50653. # v.transaction_open? # -> false
  50654. #
  50655. # == Named Transaction Usage
  50656. # v = "Hello, you." # -> "Hello, you."
  50657. # v.extend(Transaction::Simple) # -> "Hello, you."
  50658. #
  50659. # v.start_transaction(:first) # -> ... (a Marshal string)
  50660. # v.transaction_open? # -> true
  50661. # v.transaction_open?(:first) # -> true
  50662. # v.transaction_open?(:second) # -> false
  50663. # v.gsub!(/you/, "world") # -> "Hello, world."
  50664. #
  50665. # v.start_transaction(:second) # -> ... (a Marshal string)
  50666. # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
  50667. # v.rewind_transaction(:first) # -> "Hello, you."
  50668. # v.transaction_open? # -> true
  50669. # v.transaction_open?(:first) # -> true
  50670. # v.transaction_open?(:second) # -> false
  50671. #
  50672. # v.gsub!(/you/, "world") # -> "Hello, world."
  50673. # v.start_transaction(:second) # -> ... (a Marshal string)
  50674. # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
  50675. # v.transaction_name # -> :second
  50676. # v.abort_transaction(:first) # -> "Hello, you."
  50677. # v.transaction_open? # -> false
  50678. #
  50679. # v.start_transaction(:first) # -> ... (a Marshal string)
  50680. # v.gsub!(/you/, "world") # -> "Hello, world."
  50681. # v.start_transaction(:second) # -> ... (a Marshal string)
  50682. # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
  50683. #
  50684. # v.commit_transaction(:first) # -> "Hello, HAL."
  50685. # v.transaction_open? # -> false
  50686. #
  50687. # == Block Usage
  50688. # v = "Hello, you." # -> "Hello, you."
  50689. # Transaction::Simple.start(v) do |tv|
  50690. # # v has been extended with Transaction::Simple and an unnamed
  50691. # # transaction has been started.
  50692. # tv.transaction_open? # -> true
  50693. # tv.gsub!(/you/, "world") # -> "Hello, world."
  50694. #
  50695. # tv.rewind_transaction # -> "Hello, you."
  50696. # tv.transaction_open? # -> true
  50697. #
  50698. # tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
  50699. # # The following breaks out of the transaction block after
  50700. # # aborting the transaction.
  50701. # tv.abort_transaction # -> "Hello, you."
  50702. # end
  50703. # # v still has Transaction::Simple applied from here on out.
  50704. # v.transaction_open? # -> false
  50705. #
  50706. # Transaction::Simple.start(v) do |tv|
  50707. # tv.start_transaction # -> ... (a Marshal string)
  50708. #
  50709. # tv.transaction_open? # -> true
  50710. # tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
  50711. #
  50712. # # If #commit_transaction were called without having started a
  50713. # # second transaction, then it would break out of the transaction
  50714. # # block after committing the transaction.
  50715. # tv.commit_transaction # -> "Hello, HAL."
  50716. # tv.transaction_open? # -> true
  50717. # tv.abort_transaction # -> "Hello, you."
  50718. # end
  50719. # v.transaction_open? # -> false
  50720. #
  50721. # == Named Transaction Usage
  50722. # v = "Hello, you." # -> "Hello, you."
  50723. # v.extend(Transaction::Simple) # -> "Hello, you."
  50724. #
  50725. # v.start_transaction(:first) # -> ... (a Marshal string)
  50726. # v.transaction_open? # -> true
  50727. # v.transaction_open?(:first) # -> true
  50728. # v.transaction_open?(:second) # -> false
  50729. # v.gsub!(/you/, "world") # -> "Hello, world."
  50730. #
  50731. # v.start_transaction(:second) # -> ... (a Marshal string)
  50732. # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
  50733. # v.rewind_transaction(:first) # -> "Hello, you."
  50734. # v.transaction_open? # -> true
  50735. # v.transaction_open?(:first) # -> true
  50736. # v.transaction_open?(:second) # -> false
  50737. #
  50738. # v.gsub!(/you/, "world") # -> "Hello, world."
  50739. # v.start_transaction(:second) # -> ... (a Marshal string)
  50740. # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
  50741. # v.transaction_name # -> :second
  50742. # v.abort_transaction(:first) # -> "Hello, you."
  50743. # v.transaction_open? # -> false
  50744. #
  50745. # v.start_transaction(:first) # -> ... (a Marshal string)
  50746. # v.gsub!(/you/, "world") # -> "Hello, world."
  50747. # v.start_transaction(:second) # -> ... (a Marshal string)
  50748. # v.gsub!(/world/, "HAL") # -> "Hello, HAL."
  50749. #
  50750. # v.commit_transaction(:first) # -> "Hello, HAL."
  50751. # v.transaction_open? # -> false
  50752. #
  50753. # == Thread Safety
  50754. # Threadsafe version of Transaction::Simple and
  50755. # Transaction::Simple::Group exist; these are loaded from
  50756. # 'transaction/simple/threadsafe' and
  50757. # 'transaction/simple/threadsafe/group', respectively, and are
  50758. # represented in Ruby code as Transaction::Simple::ThreadSafe and
  50759. # Transaction::Simple::ThreadSafe::Group, respectively.
  50760. #
  50761. # == Contraindications
  50762. # While Transaction::Simple is very useful, it has some severe
  50763. # limitations that must be understood. Transaction::Simple:
  50764. #
  50765. # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
  50766. # cannot use Transaction::Simple. In my experience, this affects
  50767. # singleton objects more often than any other object. It may be that
  50768. # Ruby 2.0 will solve this problem.
  50769. # * does not manage resources. Resources external to the object and its
  50770. # instance variables are not managed at all. However, all instance
  50771. # variables and objects "belonging" to those instance variables are
  50772. # managed. If there are object reference counts to be handled,
  50773. # Transaction::Simple will probably cause problems.
  50774. # * is not inherently thread-safe. In the ACID ("atomic, consistent,
  50775. # isolated, durable") test, Transaction::Simple provides CD, but it is
  50776. # up to the user of Transaction::Simple to provide isolation and
  50777. # atomicity. Transactions should be considered "critical sections" in
  50778. # multi-threaded applications. If thread safety and atomicity is
  50779. # absolutely required, use Transaction::Simple::ThreadSafe, which uses
  50780. # a Mutex object to synchronize the accesses on the object during the
  50781. # transaction operations.
  50782. # * does not necessarily maintain Object#__id__ values on rewind or
  50783. # abort. This may change for future versions that will be Ruby 1.8 or
  50784. # better *only*. Certain objects that support #replace will maintain
  50785. # Object#__id__.
  50786. # * Can be a memory hog if you use many levels of transactions on many
  50787. # objects.
  50788. #
  50789. module Simple
  50790. TRANSACTION_SIMPLE_VERSION = '1.3.0'
  50791. # Sets the Transaction::Simple debug object. It must respond to #<<.
  50792. # Sets the transaction debug object. Debugging will be performed
  50793. # automatically if there's a debug object. The generic transaction
  50794. # error class.
  50795. def self.debug_io=(io)
  50796. if io.nil?
  50797. @tdi = nil
  50798. @debugging = false
  50799. else
  50800. unless io.respond_to?(:<<)
  50801. raise TransactionError, Messages[:bad_debug_object]
  50802. end
  50803. @tdi = io
  50804. @debugging = true
  50805. end
  50806. end
  50807. # Returns +true+ if we are debugging.
  50808. def self.debugging?
  50809. @debugging
  50810. end
  50811. # Returns the Transaction::Simple debug object. It must respond to
  50812. # #<<.
  50813. def self.debug_io
  50814. @tdi ||= ""
  50815. @tdi
  50816. end
  50817. # If +name+ is +nil+ (default), then returns +true+ if there is
  50818. # currently a transaction open.
  50819. #
  50820. # If +name+ is specified, then returns +true+ if there is currently a
  50821. # transaction that responds to +name+ open.
  50822. def transaction_open?(name = nil)
  50823. if name.nil?
  50824. if Transaction::Simple.debugging?
  50825. Transaction::Simple.debug_io << "Transaction " <<
  50826. "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
  50827. end
  50828. return (not @__transaction_checkpoint__.nil?)
  50829. else
  50830. if Transaction::Simple.debugging?
  50831. Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " <<
  50832. "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
  50833. end
  50834. return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
  50835. end
  50836. end
  50837. # Returns the current name of the transaction. Transactions not
  50838. # explicitly named are named +nil+.
  50839. def transaction_name
  50840. if @__transaction_checkpoint__.nil?
  50841. raise TransactionError, Messages[:no_transaction_open]
  50842. end
  50843. if Transaction::Simple.debugging?
  50844. Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
  50845. "Transaction Name: #{@__transaction_names__[-1].inspect}\n"
  50846. end
  50847. if @__transaction_names__[-1].kind_of?(String)
  50848. @__transaction_names__[-1].dup
  50849. else
  50850. @__transaction_names__[-1]
  50851. end
  50852. end
  50853. # Starts a transaction. Stores the current object state. If a
  50854. # transaction name is specified, the transaction will be named.
  50855. # Transaction names must be unique. Transaction names of +nil+ will be
  50856. # treated as unnamed transactions.
  50857. def start_transaction(name = nil)
  50858. @__transaction_level__ ||= 0
  50859. @__transaction_names__ ||= []
  50860. if name.nil?
  50861. @__transaction_names__ << nil
  50862. ss = "" if Transaction::Simple.debugging?
  50863. else
  50864. if @__transaction_names__.include?(name)
  50865. raise TransactionError, Messages[:unique_names]
  50866. end
  50867. name = name.dup.freeze if name.kind_of?(String)
  50868. @__transaction_names__ << name
  50869. ss = "(#{name.inspect})" if Transaction::Simple.debugging?
  50870. end
  50871. @__transaction_level__ += 1
  50872. if Transaction::Simple.debugging?
  50873. Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " <<
  50874. "Start Transaction#{ss}\n"
  50875. end
  50876. @__transaction_checkpoint__ = Marshal.dump(self)
  50877. end
  50878. # Rewinds the transaction. If +name+ is specified, then the
  50879. # intervening transactions will be aborted and the named transaction
  50880. # will be rewound. Otherwise, only the current transaction is rewound.
  50881. def rewind_transaction(name = nil)
  50882. if @__transaction_checkpoint__.nil?
  50883. raise TransactionError, Messages[:cannot_rewind_no_transaction]
  50884. end
  50885. # Check to see if we are trying to rewind a transaction that is
  50886. # outside of the current transaction block.
  50887. if @__transaction_block__ and name
  50888. nix = @__transaction_names__.index(name) + 1
  50889. if nix < @__transaction_block__
  50890. raise TransactionError, Messages[:cannot_rewind_transaction_before_block]
  50891. end
  50892. end
  50893. if name.nil?
  50894. __rewind_this_transaction
  50895. ss = "" if Transaction::Simple.debugging?
  50896. else
  50897. unless @__transaction_names__.include?(name)
  50898. raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect
  50899. end
  50900. ss = "(#{name})" if Transaction::Simple.debugging?
  50901. while @__transaction_names__[-1] != name
  50902. @__transaction_checkpoint__ = __rewind_this_transaction
  50903. if Transaction::Simple.debugging?
  50904. Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
  50905. "Rewind Transaction#{ss}\n"
  50906. end
  50907. @__transaction_level__ -= 1
  50908. @__transaction_names__.pop
  50909. end
  50910. __rewind_this_transaction
  50911. end
  50912. if Transaction::Simple.debugging?
  50913. Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
  50914. "Rewind Transaction#{ss}\n"
  50915. end
  50916. self
  50917. end
  50918. # Aborts the transaction. Resets the object state to what it was
  50919. # before the transaction was started and closes the transaction. If
  50920. # +name+ is specified, then the intervening transactions and the named
  50921. # transaction will be aborted. Otherwise, only the current transaction
  50922. # is aborted.
  50923. #
  50924. # If the current or named transaction has been started by a block
  50925. # (Transaction::Simple.start), then the execution of the block will be
  50926. # halted with +break+ +self+.
  50927. def abort_transaction(name = nil)
  50928. if @__transaction_checkpoint__.nil?
  50929. raise TransactionError, Messages[:cannot_abort_no_transaction]
  50930. end
  50931. # Check to see if we are trying to abort a transaction that is
  50932. # outside of the current transaction block. Otherwise, raise
  50933. # TransactionAborted if they are the same.
  50934. if @__transaction_block__ and name
  50935. nix = @__transaction_names__.index(name) + 1
  50936. if nix < @__transaction_block__
  50937. raise TransactionError, Messages[:cannot_abort_transaction_before_block]
  50938. end
  50939. raise TransactionAborted if @__transaction_block__ == nix
  50940. end
  50941. raise TransactionAborted if @__transaction_block__ == @__transaction_level__
  50942. if name.nil?
  50943. __abort_transaction(name)
  50944. else
  50945. unless @__transaction_names__.include?(name)
  50946. raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect
  50947. end
  50948. __abort_transaction(name) while @__transaction_names__.include?(name)
  50949. end
  50950. self
  50951. end
  50952. # If +name+ is +nil+ (default), the current transaction level is
  50953. # closed out and the changes are committed.
  50954. #
  50955. # If +name+ is specified and +name+ is in the list of named
  50956. # transactions, then all transactions are closed and committed until
  50957. # the named transaction is reached.
  50958. def commit_transaction(name = nil)
  50959. if @__transaction_checkpoint__.nil?
  50960. raise TransactionError, Messages[:cannot_commit_no_transaction]
  50961. end
  50962. @__transaction_block__ ||= nil
  50963. # Check to see if we are trying to commit a transaction that is
  50964. # outside of the current transaction block. Otherwise, raise
  50965. # TransactionCommitted if they are the same.
  50966. if @__transaction_block__ and name
  50967. nix = @__transaction_names__.index(name) + 1
  50968. if nix < @__transaction_block__
  50969. raise TransactionError, Messages[:cannot_commit_transaction_before_block]
  50970. end
  50971. raise TransactionCommitted if @__transaction_block__ == nix
  50972. end
  50973. raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
  50974. if name.nil?
  50975. ss = "" if Transaction::Simple.debugging?
  50976. __commit_transaction
  50977. if Transaction::Simple.debugging?
  50978. Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
  50979. "Commit Transaction#{ss}\n"
  50980. end
  50981. else
  50982. unless @__transaction_names__.include?(name)
  50983. raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect
  50984. end
  50985. ss = "(#{name})" if Transaction::Simple.debugging?
  50986. while @__transaction_names__[-1] != name
  50987. if Transaction::Simple.debugging?
  50988. Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
  50989. "Commit Transaction#{ss}\n"
  50990. end
  50991. __commit_transaction
  50992. end
  50993. if Transaction::Simple.debugging?
  50994. Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
  50995. "Commit Transaction#{ss}\n"
  50996. end
  50997. __commit_transaction
  50998. end
  50999. self
  51000. end
  51001. # Alternative method for calling the transaction methods. An optional
  51002. # name can be specified for named transaction support.
  51003. #
  51004. # #transaction(:start):: #start_transaction
  51005. # #transaction(:rewind):: #rewind_transaction
  51006. # #transaction(:abort):: #abort_transaction
  51007. # #transaction(:commit):: #commit_transaction
  51008. # #transaction(:name):: #transaction_name
  51009. # #transaction:: #transaction_open?
  51010. def transaction(action = nil, name = nil)
  51011. case action
  51012. when :start
  51013. start_transaction(name)
  51014. when :rewind
  51015. rewind_transaction(name)
  51016. when :abort
  51017. abort_transaction(name)
  51018. when :commit
  51019. commit_transaction(name)
  51020. when :name
  51021. transaction_name
  51022. when nil
  51023. transaction_open?(name)
  51024. end
  51025. end
  51026. # Allows specific variables to be excluded from transaction support.
  51027. # Must be done after extending the object but before starting the
  51028. # first transaction on the object.
  51029. #
  51030. # vv.transaction_exclusions << "@io"
  51031. def transaction_exclusions
  51032. @transaction_exclusions ||= []
  51033. end
  51034. class << self
  51035. def __common_start(name, vars, &block)
  51036. if vars.empty?
  51037. raise TransactionError, Messages[:cannot_start_empty_block_transaction]
  51038. end
  51039. if block
  51040. begin
  51041. vlevel = {}
  51042. vars.each do |vv|
  51043. vv.extend(Transaction::Simple)
  51044. vv.start_transaction(name)
  51045. vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
  51046. vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
  51047. end
  51048. yield(*vars)
  51049. rescue TransactionAborted
  51050. vars.each do |vv|
  51051. if name.nil? and vv.transaction_open?
  51052. loop do
  51053. tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
  51054. vv.instance_variable_set(:@__transaction_block__, -1)
  51055. break if tlevel < vlevel[vv.__id__]
  51056. vv.abort_transaction if vv.transaction_open?
  51057. end
  51058. elsif vv.transaction_open?(name)
  51059. vv.instance_variable_set(:@__transaction_block__, -1)
  51060. vv.abort_transaction(name)
  51061. end
  51062. end
  51063. rescue TransactionCommitted
  51064. nil
  51065. ensure
  51066. vars.each do |vv|
  51067. if name.nil? and vv.transaction_open?
  51068. loop do
  51069. tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
  51070. break if tlevel < vlevel[vv.__id__]
  51071. vv.instance_variable_set(:@__transaction_block__, -1)
  51072. vv.commit_transaction if vv.transaction_open?
  51073. end
  51074. elsif vv.transaction_open?(name)
  51075. vv.instance_variable_set(:@__transaction_block__, -1)
  51076. vv.commit_transaction(name)
  51077. end
  51078. end
  51079. end
  51080. else
  51081. vars.each do |vv|
  51082. vv.extend(Transaction::Simple)
  51083. vv.start_transaction(name)
  51084. end
  51085. end
  51086. end
  51087. private :__common_start
  51088. def start_named(name, *vars, &block)
  51089. __common_start(name, vars, &block)
  51090. end
  51091. def start(*vars, &block)
  51092. __common_start(nil, vars, &block)
  51093. end
  51094. end
  51095. def __abort_transaction(name = nil) #:nodoc:
  51096. @__transaction_checkpoint__ = __rewind_this_transaction
  51097. if name.nil?
  51098. ss = "" if Transaction::Simple.debugging?
  51099. else
  51100. ss = "(#{name.inspect})" if Transaction::Simple.debugging?
  51101. end
  51102. if Transaction::Simple.debugging?
  51103. Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
  51104. "Abort Transaction#{ss}\n"
  51105. end
  51106. @__transaction_level__ -= 1
  51107. @__transaction_names__.pop
  51108. if @__transaction_level__ < 1
  51109. @__transaction_level__ = 0
  51110. @__transaction_names__ = []
  51111. end
  51112. end
  51113. TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
  51114. SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
  51115. def __rewind_this_transaction #:nodoc:
  51116. rr = Marshal.restore(@__transaction_checkpoint__)
  51117. begin
  51118. self.replace(rr) if respond_to?(:replace)
  51119. rescue
  51120. nil
  51121. end
  51122. rr.instance_variables.each do |vv|
  51123. next if SKIP_TRANSACTION_VARS.include?(vv)
  51124. next if self.transaction_exclusions.include?(vv)
  51125. if respond_to?(:instance_variable_get)
  51126. instance_variable_set(vv, rr.instance_variable_get(vv))
  51127. else
  51128. instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|)
  51129. end
  51130. end
  51131. new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS
  51132. new_ivar.each do |vv|
  51133. if respond_to?(:instance_variable_set)
  51134. instance_variable_set(vv, nil)
  51135. else
  51136. instance_eval(%q|#{vv} = nil|)
  51137. end
  51138. end
  51139. if respond_to?(:instance_variable_get)
  51140. rr.instance_variable_get(TRANSACTION_CHECKPOINT)
  51141. else
  51142. rr.instance_eval(TRANSACTION_CHECKPOINT)
  51143. end
  51144. end
  51145. def __commit_transaction #:nodoc:
  51146. if respond_to?(:instance_variable_get)
  51147. @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
  51148. else
  51149. @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
  51150. end
  51151. @__transaction_level__ -= 1
  51152. @__transaction_names__.pop
  51153. if @__transaction_level__ < 1
  51154. @__transaction_level__ = 0
  51155. @__transaction_names__ = []
  51156. end
  51157. end
  51158. private :__abort_transaction
  51159. private :__rewind_this_transaction
  51160. private :__commit_transaction
  51161. end
  51162. end
  51163. module ActiveRecord
  51164. module VERSION #:nodoc:
  51165. MAJOR = 1
  51166. MINOR = 14
  51167. TINY = 4
  51168. STRING = [MAJOR, MINOR, TINY].join('.')
  51169. end
  51170. end
  51171. require 'yaml'
  51172. module ActiveRecord
  51173. module Wrappings #:nodoc:
  51174. class YamlWrapper < AbstractWrapper #:nodoc:
  51175. def wrap(attribute) attribute.to_yaml end
  51176. def unwrap(attribute) YAML::load(attribute) end
  51177. end
  51178. module ClassMethods #:nodoc:
  51179. # Wraps the attribute in Yaml encoding
  51180. def wrap_in_yaml(*attributes) wrap_with(YamlWrapper, attributes) end
  51181. end
  51182. end
  51183. endmodule ActiveRecord
  51184. # A plugin framework for wrapping attribute values before they go in and unwrapping them after they go out of the database.
  51185. # This was intended primarily for YAML wrapping of arrays and hashes, but this behavior is now native in the Base class.
  51186. # So for now this framework is laying dormant until a need pops up.
  51187. module Wrappings #:nodoc:
  51188. module ClassMethods #:nodoc:
  51189. def wrap_with(wrapper, *attributes)
  51190. [ attributes ].flat.each { |attribute| wrapper.wrap(attribute) }
  51191. end
  51192. end
  51193. def self.append_features(base)
  51194. super
  51195. base.extend(ClassMethods)
  51196. end
  51197. class AbstractWrapper #:nodoc:
  51198. def self.wrap(attribute, record_binding) #:nodoc:
  51199. %w( before_save after_save after_initialize ).each do |callback|
  51200. eval "#{callback} #{name}.new('#{attribute}')", record_binding
  51201. end
  51202. end
  51203. def initialize(attribute) #:nodoc:
  51204. @attribute = attribute
  51205. end
  51206. def save_wrapped_attribute(record) #:nodoc:
  51207. if record.attribute_present?(@attribute)
  51208. record.send(
  51209. "write_attribute",
  51210. @attribute,
  51211. wrap(record.send("read_attribute", @attribute))
  51212. )
  51213. end
  51214. end
  51215. def load_wrapped_attribute(record) #:nodoc:
  51216. if record.attribute_present?(@attribute)
  51217. record.send(
  51218. "write_attribute",
  51219. @attribute,
  51220. unwrap(record.send("read_attribute", @attribute))
  51221. )
  51222. end
  51223. end
  51224. alias_method :before_save, :save_wrapped_attribute #:nodoc:
  51225. alias_method :after_save, :load_wrapped_attribute #:nodoc:
  51226. alias_method :after_initialize, :after_save #:nodoc:
  51227. # Overwrite to implement the logic that'll take the regular attribute and wrap it.
  51228. def wrap(attribute) end
  51229. # Overwrite to implement the logic that'll take the wrapped attribute and unwrap it.
  51230. def unwrap(attribute) end
  51231. end
  51232. end
  51233. end
  51234. #--
  51235. # Copyright (c) 2004 David Heinemeier Hansson
  51236. #
  51237. # Permission is hereby granted, free of charge, to any person obtaining
  51238. # a copy of this software and associated documentation files (the
  51239. # "Software"), to deal in the Software without restriction, including
  51240. # without limitation the rights to use, copy, modify, merge, publish,
  51241. # distribute, sublicense, and/or sell copies of the Software, and to
  51242. # permit persons to whom the Software is furnished to do so, subject to
  51243. # the following conditions:
  51244. #
  51245. # The above copyright notice and this permission notice shall be
  51246. # included in all copies or substantial portions of the Software.
  51247. #
  51248. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  51249. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  51250. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  51251. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  51252. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  51253. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  51254. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  51255. #++
  51256. $:.unshift(File.dirname(__FILE__)) unless
  51257. $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
  51258. unless defined?(ActiveSupport)
  51259. begin
  51260. $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
  51261. require 'active_support'
  51262. rescue LoadError
  51263. require 'rubygems'
  51264. require_gem 'activesupport'
  51265. end
  51266. end
  51267. require 'active_record/base'
  51268. require 'active_record/observer'
  51269. require 'active_record/validations'
  51270. require 'active_record/callbacks'
  51271. require 'active_record/reflection'
  51272. require 'active_record/associations'
  51273. require 'active_record/aggregations'
  51274. require 'active_record/transactions'
  51275. require 'active_record/timestamp'
  51276. require 'active_record/acts/list'
  51277. require 'active_record/acts/tree'
  51278. require 'active_record/acts/nested_set'
  51279. require 'active_record/locking'
  51280. require 'active_record/migration'
  51281. require 'active_record/schema'
  51282. require 'active_record/calculations'
  51283. ActiveRecord::Base.class_eval do
  51284. include ActiveRecord::Validations
  51285. include ActiveRecord::Locking
  51286. include ActiveRecord::Callbacks
  51287. include ActiveRecord::Observing
  51288. include ActiveRecord::Timestamp
  51289. include ActiveRecord::Associations
  51290. include ActiveRecord::Aggregations
  51291. include ActiveRecord::Transactions
  51292. include ActiveRecord::Reflection
  51293. include ActiveRecord::Acts::Tree
  51294. include ActiveRecord::Acts::List
  51295. include ActiveRecord::Acts::NestedSet
  51296. include ActiveRecord::Calculations
  51297. end
  51298. unless defined?(RAILS_CONNECTION_ADAPTERS)
  51299. RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase )
  51300. end
  51301. RAILS_CONNECTION_ADAPTERS.each do |adapter|
  51302. require "active_record/connection_adapters/" + adapter + "_adapter"
  51303. end
  51304. require 'active_record/query_cache'
  51305. require 'active_record/schema_dumper'
  51306. # The filename begins with "aaa" to ensure this is the first test.
  51307. require 'abstract_unit'
  51308. class AAACreateTablesTest < Test::Unit::TestCase
  51309. self.use_transactional_fixtures = false
  51310. def setup
  51311. @base_path = "#{File.dirname(__FILE__)}/fixtures/db_definitions"
  51312. end
  51313. def test_drop_and_create_main_tables
  51314. recreate ActiveRecord::Base
  51315. assert true
  51316. end
  51317. def test_load_schema
  51318. eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb"))
  51319. assert true
  51320. end
  51321. def test_drop_and_create_courses_table
  51322. recreate Course, '2'
  51323. assert true
  51324. end
  51325. private
  51326. def recreate(base, suffix = nil)
  51327. connection = base.connection
  51328. adapter_name = connection.adapter_name.downcase + suffix.to_s
  51329. execute_sql_file "#{@base_path}/#{adapter_name}.drop.sql", connection
  51330. execute_sql_file "#{@base_path}/#{adapter_name}.sql", connection
  51331. end
  51332. def execute_sql_file(path, connection)
  51333. # OpenBase has a different format for sql files
  51334. if current_adapter?(:OpenBaseAdapter) then
  51335. File.read(path).split("go").each_with_index do |sql, i|
  51336. begin
  51337. # OpenBase does not support comments embedded in sql
  51338. connection.execute(sql,"SQL statement ##{i}") unless sql.blank?
  51339. rescue ActiveRecord::StatementInvalid
  51340. #$stderr.puts "warning: #{$!}"
  51341. end
  51342. end
  51343. else
  51344. File.read(path).split(';').each_with_index do |sql, i|
  51345. begin
  51346. connection.execute("\n\n-- statement ##{i}\n#{sql}\n") unless sql.blank?
  51347. rescue ActiveRecord::StatementInvalid
  51348. #$stderr.puts "warning: #{$!}"
  51349. end
  51350. end
  51351. end
  51352. end
  51353. end
  51354. $:.unshift(File.dirname(__FILE__) + '/../lib')
  51355. $:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
  51356. require 'test/unit'
  51357. require 'active_record'
  51358. require 'active_record/fixtures'
  51359. require 'active_support/binding_of_caller'
  51360. require 'active_support/breakpoint'
  51361. require 'connection'
  51362. QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
  51363. class Test::Unit::TestCase #:nodoc:
  51364. self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
  51365. self.use_instantiated_fixtures = false
  51366. self.use_transactional_fixtures = (ENV['AR_NO_TX_FIXTURES'] != "yes")
  51367. def create_fixtures(*table_names, &block)
  51368. Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
  51369. end
  51370. def assert_date_from_db(expected, actual, message = nil)
  51371. # SQL Server doesn't have a separate column type just for dates,
  51372. # so the time is in the string and incorrectly formatted
  51373. if current_adapter?(:SQLServerAdapter)
  51374. assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
  51375. elsif current_adapter?(:SybaseAdapter)
  51376. assert_equal expected.to_s, actual.to_date.to_s, message
  51377. else
  51378. assert_equal expected.to_s, actual.to_s, message
  51379. end
  51380. end
  51381. def assert_queries(num = 1)
  51382. ActiveRecord::Base.connection.class.class_eval do
  51383. self.query_count = 0
  51384. alias_method :execute, :execute_with_query_counting
  51385. end
  51386. yield
  51387. ensure
  51388. ActiveRecord::Base.connection.class.class_eval do
  51389. alias_method :execute, :execute_without_query_counting
  51390. end
  51391. assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
  51392. end
  51393. def assert_no_queries(&block)
  51394. assert_queries(0, &block)
  51395. end
  51396. end
  51397. def current_adapter?(type)
  51398. ActiveRecord::ConnectionAdapters.const_defined?(type) &&
  51399. ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
  51400. end
  51401. ActiveRecord::Base.connection.class.class_eval do
  51402. cattr_accessor :query_count
  51403. alias_method :execute_without_query_counting, :execute
  51404. def execute_with_query_counting(sql, name = nil)
  51405. self.query_count += 1
  51406. execute_without_query_counting(sql, name)
  51407. end
  51408. end
  51409. #ActiveRecord::Base.logger = Logger.new(STDOUT)
  51410. #ActiveRecord::Base.colorize_logging = false
  51411. require 'abstract_unit'
  51412. class ActiveSchemaTest < Test::Unit::TestCase
  51413. def setup
  51414. ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
  51415. alias_method :real_execute, :execute
  51416. def execute(sql, name = nil) return sql end
  51417. end
  51418. end
  51419. def teardown
  51420. ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:alias_method, :execute, :real_execute)
  51421. end
  51422. def test_drop_table
  51423. assert_equal "DROP TABLE people", drop_table(:people)
  51424. end
  51425. def test_add_column
  51426. assert_equal "ALTER TABLE people ADD last_name varchar(255)", add_column(:people, :last_name, :string)
  51427. end
  51428. def test_add_column_with_limit
  51429. assert_equal "ALTER TABLE people ADD key varchar(32)", add_column(:people, :key, :string, :limit => 32)
  51430. end
  51431. private
  51432. def method_missing(method_symbol, *arguments)
  51433. ActiveRecord::Base.connection.send(method_symbol, *arguments)
  51434. end
  51435. endrequire 'abstract_unit'
  51436. class AdapterTest < Test::Unit::TestCase
  51437. def setup
  51438. @connection = ActiveRecord::Base.connection
  51439. end
  51440. def test_tables
  51441. if @connection.respond_to?(:tables)
  51442. tables = @connection.tables
  51443. assert tables.include?("accounts")
  51444. assert tables.include?("authors")
  51445. assert tables.include?("tasks")
  51446. assert tables.include?("topics")
  51447. else
  51448. warn "#{@connection.class} does not respond to #tables"
  51449. end
  51450. end
  51451. def test_indexes
  51452. idx_name = "accounts_idx"
  51453. if @connection.respond_to?(:indexes)
  51454. indexes = @connection.indexes("accounts")
  51455. assert indexes.empty?
  51456. @connection.add_index :accounts, :firm_id, :name => idx_name
  51457. indexes = @connection.indexes("accounts")
  51458. assert_equal "accounts", indexes.first.table
  51459. # OpenBase does not have the concept of a named index
  51460. # Indexes are merely properties of columns.
  51461. assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
  51462. assert !indexes.first.unique
  51463. assert_equal ["firm_id"], indexes.first.columns
  51464. else
  51465. warn "#{@connection.class} does not respond to #indexes"
  51466. end
  51467. ensure
  51468. @connection.remove_index(:accounts, :name => idx_name) rescue nil
  51469. end
  51470. def test_current_database
  51471. if @connection.respond_to?(:current_database)
  51472. assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database
  51473. end
  51474. end
  51475. def test_table_alias
  51476. def @connection.test_table_alias_length() 10; end
  51477. class << @connection
  51478. alias_method :old_table_alias_length, :table_alias_length
  51479. alias_method :table_alias_length, :test_table_alias_length
  51480. end
  51481. assert_equal 'posts', @connection.table_alias_for('posts')
  51482. assert_equal 'posts_comm', @connection.table_alias_for('posts_comments')
  51483. assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts')
  51484. class << @connection
  51485. alias_method :table_alias_length, :old_table_alias_length
  51486. end
  51487. end
  51488. # test resetting sequences in odd tables in postgreSQL
  51489. if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
  51490. require 'fixtures/movie'
  51491. require 'fixtures/subscriber'
  51492. def test_reset_empty_table_with_custom_pk
  51493. Movie.delete_all
  51494. Movie.connection.reset_pk_sequence! 'movies'
  51495. assert_equal 1, Movie.create(:name => 'fight club').id
  51496. end
  51497. def test_reset_table_with_non_integer_pk
  51498. Subscriber.delete_all
  51499. Subscriber.connection.reset_pk_sequence! 'subscribers'
  51500. sub = Subscriber.new(:name => 'robert drake')
  51501. sub.id = 'bob drake'
  51502. assert_nothing_raised { sub.save! }
  51503. end
  51504. end
  51505. end
  51506. require 'abstract_unit'
  51507. require 'fixtures/customer'
  51508. class AggregationsTest < Test::Unit::TestCase
  51509. fixtures :customers
  51510. def test_find_single_value_object
  51511. assert_equal 50, customers(:david).balance.amount
  51512. assert_kind_of Money, customers(:david).balance
  51513. assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
  51514. end
  51515. def test_find_multiple_value_object
  51516. assert_equal customers(:david).address_street, customers(:david).address.street
  51517. assert(
  51518. customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
  51519. )
  51520. end
  51521. def test_change_single_value_object
  51522. customers(:david).balance = Money.new(100)
  51523. customers(:david).save
  51524. assert_equal 100, Customer.find(1).balance.amount
  51525. end
  51526. def test_immutable_value_objects
  51527. customers(:david).balance = Money.new(100)
  51528. assert_raises(TypeError) { customers(:david).balance.instance_eval { @amount = 20 } }
  51529. end
  51530. def test_inferred_mapping
  51531. assert_equal "35.544623640962634", customers(:david).gps_location.latitude
  51532. assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
  51533. customers(:david).gps_location = GpsLocation.new("39x-110")
  51534. assert_equal "39", customers(:david).gps_location.latitude
  51535. assert_equal "-110", customers(:david).gps_location.longitude
  51536. customers(:david).save
  51537. customers(:david).reload
  51538. assert_equal "39", customers(:david).gps_location.latitude
  51539. assert_equal "-110", customers(:david).gps_location.longitude
  51540. end
  51541. def test_reloaded_instance_refreshes_aggregations
  51542. assert_equal "35.544623640962634", customers(:david).gps_location.latitude
  51543. assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
  51544. Customer.update_all("gps_location = '24x113'")
  51545. customers(:david).reload
  51546. assert_equal '24x113', customers(:david)['gps_location']
  51547. assert_equal GpsLocation.new('24x113'), customers(:david).gps_location
  51548. end
  51549. def test_gps_equality
  51550. assert GpsLocation.new('39x110') == GpsLocation.new('39x110')
  51551. end
  51552. def test_gps_inequality
  51553. assert GpsLocation.new('39x110') != GpsLocation.new('39x111')
  51554. end
  51555. end
  51556. require 'abstract_unit'
  51557. require "#{File.dirname(__FILE__)}/../lib/active_record/schema"
  51558. if ActiveRecord::Base.connection.supports_migrations?
  51559. class ActiveRecordSchemaTest < Test::Unit::TestCase
  51560. self.use_transactional_fixtures = false
  51561. def setup
  51562. @connection = ActiveRecord::Base.connection
  51563. end
  51564. def teardown
  51565. @connection.drop_table :fruits rescue nil
  51566. end
  51567. def test_schema_define
  51568. ActiveRecord::Schema.define(:version => 7) do
  51569. create_table :fruits do |t|
  51570. t.column :color, :string
  51571. t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
  51572. t.column :texture, :string
  51573. t.column :flavor, :string
  51574. end
  51575. end
  51576. assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
  51577. assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" }
  51578. assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i
  51579. end
  51580. end
  51581. end
  51582. require 'abstract_unit'
  51583. require 'fixtures/post'
  51584. require 'fixtures/comment'
  51585. require 'fixtures/author'
  51586. require 'fixtures/category'
  51587. require 'fixtures/project'
  51588. require 'fixtures/developer'
  51589. class AssociationCallbacksTest < Test::Unit::TestCase
  51590. fixtures :posts, :authors, :projects, :developers
  51591. def setup
  51592. @david = authors(:david)
  51593. @thinking = posts(:thinking)
  51594. @authorless = posts(:authorless)
  51595. assert @david.post_log.empty?
  51596. end
  51597. def test_adding_macro_callbacks
  51598. @david.posts_with_callbacks << @thinking
  51599. assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
  51600. @david.posts_with_callbacks << @thinking
  51601. assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}",
  51602. "after_adding#{@thinking.id}"], @david.post_log
  51603. end
  51604. def test_adding_with_proc_callbacks
  51605. @david.posts_with_proc_callbacks << @thinking
  51606. assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
  51607. @david.posts_with_proc_callbacks << @thinking
  51608. assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}",
  51609. "after_adding#{@thinking.id}"], @david.post_log
  51610. end
  51611. def test_removing_with_macro_callbacks
  51612. first_post, second_post = @david.posts_with_callbacks[0, 2]
  51613. @david.posts_with_callbacks.delete(first_post)
  51614. assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
  51615. @david.posts_with_callbacks.delete(second_post)
  51616. assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}",
  51617. "after_removing#{second_post.id}"], @david.post_log
  51618. end
  51619. def test_removing_with_proc_callbacks
  51620. first_post, second_post = @david.posts_with_callbacks[0, 2]
  51621. @david.posts_with_proc_callbacks.delete(first_post)
  51622. assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
  51623. @david.posts_with_proc_callbacks.delete(second_post)
  51624. assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}",
  51625. "after_removing#{second_post.id}"], @david.post_log
  51626. end
  51627. def test_multiple_callbacks
  51628. @david.posts_with_multiple_callbacks << @thinking
  51629. assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}",
  51630. "after_adding_proc#{@thinking.id}"], @david.post_log
  51631. @david.posts_with_multiple_callbacks << @thinking
  51632. assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}",
  51633. "after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}",
  51634. "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log
  51635. end
  51636. def test_has_and_belongs_to_many_add_callback
  51637. david = developers(:david)
  51638. ar = projects(:active_record)
  51639. assert ar.developers_log.empty?
  51640. ar.developers_with_callbacks << david
  51641. assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log
  51642. ar.developers_with_callbacks << david
  51643. assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}",
  51644. "after_adding#{david.id}"], ar.developers_log
  51645. end
  51646. def test_has_and_belongs_to_many_remove_callback
  51647. david = developers(:david)
  51648. jamis = developers(:jamis)
  51649. activerecord = projects(:active_record)
  51650. assert activerecord.developers_log.empty?
  51651. activerecord.developers_with_callbacks.delete(david)
  51652. assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log
  51653. activerecord.developers_with_callbacks.delete(jamis)
  51654. assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}",
  51655. "after_removing#{jamis.id}"], activerecord.developers_log
  51656. end
  51657. def test_has_and_belongs_to_many_remove_callback_on_clear
  51658. activerecord = projects(:active_record)
  51659. assert activerecord.developers_log.empty?
  51660. if activerecord.developers_with_callbacks.size == 0
  51661. activerecord.developers << developers(:david)
  51662. activerecord.developers << developers(:jamis)
  51663. activerecord.reload
  51664. assert activerecord.developers_with_callbacks.size == 2
  51665. end
  51666. log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
  51667. assert activerecord.developers_with_callbacks.clear
  51668. assert_equal log_array, activerecord.developers_log.sort
  51669. end
  51670. def test_dont_add_if_before_callback_raises_exception
  51671. assert !@david.unchangable_posts.include?(@authorless)
  51672. begin
  51673. @david.unchangable_posts << @authorless
  51674. rescue Exception => e
  51675. end
  51676. assert @david.post_log.empty?
  51677. assert !@david.unchangable_posts.include?(@authorless)
  51678. @david.reload
  51679. assert !@david.unchangable_posts.include?(@authorless)
  51680. end
  51681. def test_push_with_attributes
  51682. david = developers(:david)
  51683. activerecord = projects(:active_record)
  51684. assert activerecord.developers_log.empty?
  51685. activerecord.developers_with_callbacks.push_with_attributes(david, {})
  51686. assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], activerecord.developers_log
  51687. activerecord.developers_with_callbacks.push_with_attributes(david, {})
  51688. assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}",
  51689. "after_adding#{david.id}"], activerecord.developers_log
  51690. end
  51691. end
  51692. require 'abstract_unit'
  51693. require 'fixtures/company'
  51694. class AssociationInheritanceReloadTest < Test::Unit::TestCase
  51695. fixtures :companies
  51696. def test_set_attributes
  51697. assert_equal ["errors.add_on_empty('name', \"can't be empty\")"], Firm.read_inheritable_attribute("validate"), "Second run"
  51698. # ActiveRecord::Base.reset_column_information_and_inheritable_attributes_for_all_subclasses
  51699. remove_subclass_of(ActiveRecord::Base)
  51700. load 'fixtures/company.rb'
  51701. assert_equal ["errors.add_on_empty('name', \"can't be empty\")"], Firm.read_inheritable_attribute("validate"), "Second run"
  51702. end
  51703. endrequire 'abstract_unit'
  51704. require 'active_record/acts/list'
  51705. require 'fixtures/post'
  51706. require 'fixtures/comment'
  51707. require 'fixtures/author'
  51708. require 'fixtures/category'
  51709. require 'fixtures/categorization'
  51710. require 'fixtures/mixin'
  51711. require 'fixtures/company'
  51712. require 'fixtures/topic'
  51713. require 'fixtures/reply'
  51714. class CascadedEagerLoadingTest < Test::Unit::TestCase
  51715. fixtures :authors, :mixins, :companies, :posts, :categorizations, :topics
  51716. def test_eager_association_loading_with_cascaded_two_levels
  51717. authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
  51718. assert_equal 2, authors.size
  51719. assert_equal 5, authors[0].posts.size
  51720. assert_equal 1, authors[1].posts.size
  51721. assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  51722. end
  51723. def test_eager_association_loading_with_cascaded_two_levels_and_one_level
  51724. authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
  51725. assert_equal 2, authors.size
  51726. assert_equal 5, authors[0].posts.size
  51727. assert_equal 1, authors[1].posts.size
  51728. assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  51729. assert_equal 1, authors[0].categorizations.size
  51730. assert_equal 1, authors[1].categorizations.size
  51731. end
  51732. def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
  51733. authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
  51734. assert_equal 2, authors.size
  51735. assert_equal 5, authors[0].posts.size
  51736. assert_equal 1, authors[1].posts.size
  51737. assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  51738. end
  51739. def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
  51740. authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
  51741. assert_equal 2, authors.size
  51742. assert_equal 5, authors[0].posts.size
  51743. assert_equal authors(:david).name, authors[0].name
  51744. assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
  51745. end
  51746. def test_eager_association_loading_with_cascaded_two_levels_with_condition
  51747. authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
  51748. assert_equal 1, authors.size
  51749. assert_equal 5, authors[0].posts.size
  51750. end
  51751. def test_eager_association_loading_with_acts_as_tree
  51752. roots = TreeMixin.find(:all, :include=>"children", :conditions=>"mixins.parent_id IS NULL", :order=>"mixins.id")
  51753. assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], roots
  51754. assert_no_queries do
  51755. assert_equal 2, roots[0].children.size
  51756. assert_equal 0, roots[1].children.size
  51757. assert_equal 0, roots[2].children.size
  51758. end
  51759. end
  51760. def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
  51761. firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
  51762. assert_equal 2, firms.size
  51763. assert_equal firms.first.account, firms.first.account.firm.account
  51764. assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
  51765. assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account }
  51766. end
  51767. def test_eager_association_loading_with_has_many_sti
  51768. topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
  51769. assert_equal [topics(:first), topics(:second)], topics
  51770. assert_no_queries do
  51771. assert_equal 1, topics[0].replies.size
  51772. assert_equal 0, topics[1].replies.size
  51773. end
  51774. end
  51775. def test_eager_association_loading_with_belongs_to_sti
  51776. replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
  51777. assert_equal [topics(:second)], replies
  51778. assert_equal topics(:first), assert_no_queries { replies.first.topic }
  51779. end
  51780. def test_eager_association_loading_with_multiple_stis_and_order
  51781. author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
  51782. assert_equal authors(:david), author
  51783. assert_no_queries do
  51784. author.posts.first.special_comments
  51785. author.posts.first.very_special_comment
  51786. end
  51787. end
  51788. def test_eager_association_loading_of_stis_with_multiple_references
  51789. authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
  51790. assert_equal [authors(:david)], authors
  51791. assert_no_queries do
  51792. authors.first.posts.first.special_comments.first.post.special_comments
  51793. authors.first.posts.first.special_comments.first.post.very_special_comment
  51794. end
  51795. end
  51796. end
  51797. require 'abstract_unit'
  51798. require 'fixtures/post'
  51799. require 'fixtures/comment'
  51800. require 'fixtures/project'
  51801. require 'fixtures/developer'
  51802. class AssociationsExtensionsTest < Test::Unit::TestCase
  51803. fixtures :projects, :developers, :developers_projects, :comments, :posts
  51804. def test_extension_on_has_many
  51805. assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent
  51806. end
  51807. def test_extension_on_habtm
  51808. assert_equal projects(:action_controller), developers(:david).projects.find_most_recent
  51809. end
  51810. def test_named_extension_on_habtm
  51811. assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent
  51812. end
  51813. def test_marshalling_extensions
  51814. david = developers(:david)
  51815. assert_equal projects(:action_controller), david.projects.find_most_recent
  51816. david = Marshal.load(Marshal.dump(david))
  51817. assert_equal projects(:action_controller), david.projects.find_most_recent
  51818. end
  51819. def test_marshalling_named_extensions
  51820. david = developers(:david)
  51821. assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
  51822. david = Marshal.load(Marshal.dump(david))
  51823. assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
  51824. end
  51825. endrequire 'abstract_unit'
  51826. require 'fixtures/post'
  51827. require 'fixtures/comment'
  51828. require 'fixtures/author'
  51829. require 'fixtures/category'
  51830. require 'fixtures/company'
  51831. require 'fixtures/person'
  51832. require 'fixtures/reader'
  51833. class EagerAssociationTest < Test::Unit::TestCase
  51834. fixtures :posts, :comments, :authors, :categories, :categories_posts,
  51835. :companies, :accounts, :tags, :people, :readers
  51836. def test_loading_with_one_association
  51837. posts = Post.find(:all, :include => :comments)
  51838. post = posts.find { |p| p.id == 1 }
  51839. assert_equal 2, post.comments.size
  51840. assert post.comments.include?(comments(:greetings))
  51841. post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
  51842. assert_equal 2, post.comments.size
  51843. assert post.comments.include?(comments(:greetings))
  51844. end
  51845. def test_loading_conditions_with_or
  51846. posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
  51847. assert_nil posts.detect { |p| p.author_id != authors(:david).id },
  51848. "expected to find only david's posts"
  51849. end
  51850. def test_with_ordering
  51851. list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
  51852. [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
  51853. :authorless, :thinking, :welcome
  51854. ].each_with_index do |post, index|
  51855. assert_equal posts(post), list[index]
  51856. end
  51857. end
  51858. def test_loading_with_multiple_associations
  51859. posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id")
  51860. assert_equal 2, posts.first.comments.size
  51861. assert_equal 2, posts.first.categories.size
  51862. assert posts.first.comments.include?(comments(:greetings))
  51863. end
  51864. def test_loading_from_an_association
  51865. posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
  51866. assert_equal 2, posts.first.comments.size
  51867. end
  51868. def test_loading_with_no_associations
  51869. assert_nil Post.find(posts(:authorless).id, :include => :author).author
  51870. end
  51871. def test_eager_association_loading_with_belongs_to
  51872. comments = Comment.find(:all, :include => :post)
  51873. assert_equal 10, comments.length
  51874. titles = comments.map { |c| c.post.title }
  51875. assert titles.include?(posts(:welcome).title)
  51876. assert titles.include?(posts(:sti_post_and_comments).title)
  51877. end
  51878. def test_eager_association_loading_with_belongs_to_and_limit
  51879. comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
  51880. assert_equal 5, comments.length
  51881. assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
  51882. end
  51883. def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
  51884. comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
  51885. assert_equal 3, comments.length
  51886. assert_equal [5,6,7], comments.collect { |c| c.id }
  51887. end
  51888. def test_eager_association_loading_with_belongs_to_and_limit_and_offset
  51889. comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
  51890. assert_equal 3, comments.length
  51891. assert_equal [3,5,6], comments.collect { |c| c.id }
  51892. end
  51893. def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
  51894. comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
  51895. assert_equal 3, comments.length
  51896. assert_equal [6,7,8], comments.collect { |c| c.id }
  51897. end
  51898. def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
  51899. comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id')
  51900. assert_equal 3, comments.length
  51901. assert_equal [6,7,8], comments.collect { |c| c.id }
  51902. end
  51903. def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
  51904. posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
  51905. assert_equal 1, posts.length
  51906. assert_equal [1], posts.collect { |p| p.id }
  51907. end
  51908. def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
  51909. posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
  51910. assert_equal 1, posts.length
  51911. assert_equal [2], posts.collect { |p| p.id }
  51912. end
  51913. def test_eager_with_has_many_through
  51914. posts_with_comments = people(:michael).posts.find(:all, :include => :comments )
  51915. posts_with_author = people(:michael).posts.find(:all, :include => :author )
  51916. posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ])
  51917. assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
  51918. assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
  51919. assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
  51920. end
  51921. def test_eager_with_has_many_and_limit
  51922. posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
  51923. assert_equal 2, posts.size
  51924. assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
  51925. end
  51926. def test_eager_with_has_many_and_limit_and_conditions
  51927. posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id")
  51928. assert_equal 2, posts.size
  51929. assert_equal [4,5], posts.collect { |p| p.id }
  51930. end
  51931. def test_eager_with_has_many_and_limit_and_conditions_array
  51932. posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id")
  51933. assert_equal 2, posts.size
  51934. assert_equal [4,5], posts.collect { |p| p.id }
  51935. end
  51936. def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
  51937. posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
  51938. assert_equal 2, posts.size
  51939. count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
  51940. assert_equal count, posts.size
  51941. end
  51942. def test_eager_with_has_many_and_limit_ond_high_offset
  51943. posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
  51944. assert_equal 0, posts.size
  51945. end
  51946. def test_count_eager_with_has_many_and_limit_ond_high_offset
  51947. posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
  51948. assert_equal 0, posts
  51949. end
  51950. def test_eager_with_has_many_and_limit_with_no_results
  51951. posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
  51952. assert_equal 0, posts.size
  51953. end
  51954. def test_eager_with_has_and_belongs_to_many_and_limit
  51955. posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
  51956. assert_equal 3, posts.size
  51957. assert_equal 2, posts[0].categories.size
  51958. assert_equal 1, posts[1].categories.size
  51959. assert_equal 0, posts[2].categories.size
  51960. assert posts[0].categories.include?(categories(:technology))
  51961. assert posts[1].categories.include?(categories(:general))
  51962. end
  51963. def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
  51964. posts = authors(:david).posts.find(:all,
  51965. :include => :comments,
  51966. :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
  51967. :limit => 2
  51968. )
  51969. assert_equal 2, posts.size
  51970. count = Post.count(
  51971. :include => [ :comments, :author ],
  51972. :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
  51973. :limit => 2
  51974. )
  51975. assert_equal count, posts.size
  51976. end
  51977. def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
  51978. posts = nil
  51979. Post.with_scope(:find => {
  51980. :include => :comments,
  51981. :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'"
  51982. }) do
  51983. posts = authors(:david).posts.find(:all, :limit => 2)
  51984. assert_equal 2, posts.size
  51985. end
  51986. Post.with_scope(:find => {
  51987. :include => [ :comments, :author ],
  51988. :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')"
  51989. }) do
  51990. count = Post.count(:limit => 2)
  51991. assert_equal count, posts.size
  51992. end
  51993. end
  51994. def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
  51995. Post.with_scope(:find => { :conditions => "1=1" }) do
  51996. posts = authors(:david).posts.find(:all,
  51997. :include => :comments,
  51998. :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
  51999. :limit => 2
  52000. )
  52001. assert_equal 2, posts.size
  52002. count = Post.count(
  52003. :include => [ :comments, :author ],
  52004. :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
  52005. :limit => 2
  52006. )
  52007. assert_equal count, posts.size
  52008. end
  52009. end
  52010. def test_eager_association_loading_with_habtm
  52011. posts = Post.find(:all, :include => :categories, :order => "posts.id")
  52012. assert_equal 2, posts[0].categories.size
  52013. assert_equal 1, posts[1].categories.size
  52014. assert_equal 0, posts[2].categories.size
  52015. assert posts[0].categories.include?(categories(:technology))
  52016. assert posts[1].categories.include?(categories(:general))
  52017. end
  52018. def test_eager_with_inheritance
  52019. posts = SpecialPost.find(:all, :include => [ :comments ])
  52020. end
  52021. def test_eager_has_one_with_association_inheritance
  52022. post = Post.find(4, :include => [ :very_special_comment ])
  52023. assert_equal "VerySpecialComment", post.very_special_comment.class.to_s
  52024. end
  52025. def test_eager_has_many_with_association_inheritance
  52026. post = Post.find(4, :include => [ :special_comments ])
  52027. post.special_comments.each do |special_comment|
  52028. assert_equal "SpecialComment", special_comment.class.to_s
  52029. end
  52030. end
  52031. def test_eager_habtm_with_association_inheritance
  52032. post = Post.find(6, :include => [ :special_categories ])
  52033. assert_equal 1, post.special_categories.size
  52034. post.special_categories.each do |special_category|
  52035. assert_equal "SpecialCategory", special_category.class.to_s
  52036. end
  52037. end
  52038. def test_eager_with_has_one_dependent_does_not_destroy_dependent
  52039. assert_not_nil companies(:first_firm).account
  52040. f = Firm.find(:first, :include => :account,
  52041. :conditions => ["companies.name = ?", "37signals"])
  52042. assert_not_nil f.account
  52043. assert_equal companies(:first_firm, :reload).account, f.account
  52044. end
  52045. def test_eager_with_invalid_association_reference
  52046. assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
  52047. post = Post.find(6, :include=> :monkeys )
  52048. }
  52049. assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
  52050. post = Post.find(6, :include=>[ :monkeys ])
  52051. }
  52052. assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
  52053. post = Post.find(6, :include=>[ 'monkeys' ])
  52054. }
  52055. assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
  52056. post = Post.find(6, :include=>[ :monkeys, :elephants ])
  52057. }
  52058. end
  52059. def find_all_ordered(className, include=nil)
  52060. className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
  52061. end
  52062. def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm
  52063. # Eager includes of has many and habtm associations aren't necessarily sorted in the same way
  52064. def assert_equal_after_sort(item1, item2, item3 = nil)
  52065. assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id})
  52066. assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3
  52067. end
  52068. # Test regular association, association with conditions, association with
  52069. # STI, and association with conditions assured not to be true
  52070. post_types = [:posts, :hello_posts, :special_posts, :nonexistent_posts]
  52071. # test both has_many and has_and_belongs_to_many
  52072. [Author, Category].each do |className|
  52073. d1 = find_all_ordered(className)
  52074. # test including all post types at once
  52075. d2 = find_all_ordered(className, post_types)
  52076. d1.each_index do |i|
  52077. assert_equal(d1[i], d2[i])
  52078. assert_equal_after_sort(d1[i].posts, d2[i].posts)
  52079. post_types[1..-1].each do |post_type|
  52080. # test including post_types together
  52081. d3 = find_all_ordered(className, [:posts, post_type])
  52082. assert_equal(d1[i], d3[i])
  52083. assert_equal_after_sort(d1[i].posts, d3[i].posts)
  52084. assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type))
  52085. end
  52086. end
  52087. end
  52088. end
  52089. def test_eager_with_multiple_associations_with_same_table_has_one
  52090. d1 = find_all_ordered(Firm)
  52091. d2 = find_all_ordered(Firm, :account)
  52092. d1.each_index do |i|
  52093. assert_equal(d1[i], d2[i])
  52094. assert_equal(d1[i].account, d2[i].account)
  52095. end
  52096. end
  52097. def test_eager_with_multiple_associations_with_same_table_belongs_to
  52098. firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition]
  52099. d1 = find_all_ordered(Client)
  52100. d2 = find_all_ordered(Client, firm_types)
  52101. d1.each_index do |i|
  52102. assert_equal(d1[i], d2[i])
  52103. firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) }
  52104. end
  52105. end
  52106. def test_eager_with_valid_association_as_string_not_symbol
  52107. assert_nothing_raised { Post.find(:all, :include => 'comments') }
  52108. end
  52109. def test_preconfigured_includes_with_belongs_to
  52110. author = posts(:welcome).author_with_posts
  52111. assert_equal 5, author.posts.size
  52112. end
  52113. def test_preconfigured_includes_with_has_one
  52114. comment = posts(:sti_comments).very_special_comment_with_post
  52115. assert_equal posts(:sti_comments), comment.post
  52116. end
  52117. def test_preconfigured_includes_with_has_many
  52118. posts = authors(:david).posts_with_comments
  52119. one = posts.detect { |p| p.id == 1 }
  52120. assert_equal 5, posts.size
  52121. assert_equal 2, one.comments.size
  52122. end
  52123. def test_preconfigured_includes_with_habtm
  52124. posts = authors(:david).posts_with_categories
  52125. one = posts.detect { |p| p.id == 1 }
  52126. assert_equal 5, posts.size
  52127. assert_equal 2, one.categories.size
  52128. end
  52129. def test_preconfigured_includes_with_has_many_and_habtm
  52130. posts = authors(:david).posts_with_comments_and_categories
  52131. one = posts.detect { |p| p.id == 1 }
  52132. assert_equal 5, posts.size
  52133. assert_equal 2, one.comments.size
  52134. assert_equal 2, one.categories.size
  52135. end
  52136. end
  52137. require 'abstract_unit'
  52138. require 'fixtures/tag'
  52139. require 'fixtures/tagging'
  52140. require 'fixtures/post'
  52141. require 'fixtures/comment'
  52142. require 'fixtures/author'
  52143. require 'fixtures/category'
  52144. require 'fixtures/categorization'
  52145. class AssociationsJoinModelTest < Test::Unit::TestCase
  52146. self.use_transactional_fixtures = false
  52147. fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites
  52148. def test_has_many
  52149. assert_equal categories(:general), authors(:david).categories.first
  52150. end
  52151. def test_has_many_inherited
  52152. assert_equal categories(:sti_test), authors(:mary).categories.first
  52153. end
  52154. def test_inherited_has_many
  52155. assert_equal authors(:mary), categories(:sti_test).authors.first
  52156. end
  52157. def test_polymorphic_has_many
  52158. assert_equal taggings(:welcome_general), posts(:welcome).taggings.first
  52159. end
  52160. def test_polymorphic_has_one
  52161. assert_equal taggings(:welcome_general), posts(:welcome).tagging
  52162. end
  52163. def test_polymorphic_belongs_to
  52164. assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable
  52165. end
  52166. def test_polymorphic_has_many_going_through_join_model
  52167. assert_equal tags(:general), tag = posts(:welcome).tags.first
  52168. assert_no_queries do
  52169. tag.tagging
  52170. end
  52171. end
  52172. def test_count_polymorphic_has_many
  52173. assert_equal 1, posts(:welcome).taggings.count
  52174. assert_equal 1, posts(:welcome).tags.count
  52175. end
  52176. def test_polymorphic_has_many_going_through_join_model_with_find
  52177. assert_equal tags(:general), tag = posts(:welcome).tags.find(:first)
  52178. assert_no_queries do
  52179. tag.tagging
  52180. end
  52181. end
  52182. def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection
  52183. assert_equal tags(:general), tag = posts(:welcome).funky_tags.first
  52184. assert_no_queries do
  52185. tag.tagging
  52186. end
  52187. end
  52188. def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
  52189. assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
  52190. assert_no_queries do
  52191. tag.tagging
  52192. end
  52193. end
  52194. def test_polymorphic_has_many_going_through_join_model_with_disabled_include
  52195. assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
  52196. assert_queries 1 do
  52197. tag.tagging
  52198. end
  52199. end
  52200. def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
  52201. assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
  52202. tag.author_id
  52203. end
  52204. def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
  52205. assert_equal tags(:misc), taggings(:welcome_general).super_tag
  52206. assert_equal tags(:misc), posts(:welcome).super_tags.first
  52207. end
  52208. def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class
  52209. post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body'
  52210. assert_instance_of SubStiPost, post
  52211. tagging = tags(:misc).taggings.create(:taggable => post)
  52212. assert_equal "SubStiPost", tagging.taggable_type
  52213. end
  52214. def test_polymorphic_has_many_going_through_join_model_with_inheritance
  52215. assert_equal tags(:general), posts(:thinking).tags.first
  52216. end
  52217. def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name
  52218. assert_equal tags(:general), posts(:thinking).funky_tags.first
  52219. end
  52220. def test_polymorphic_has_many_create_model_with_inheritance
  52221. post = posts(:thinking)
  52222. assert_instance_of SpecialPost, post
  52223. tagging = tags(:misc).taggings.create(:taggable => post)
  52224. assert_equal "Post", tagging.taggable_type
  52225. end
  52226. def test_polymorphic_has_one_create_model_with_inheritance
  52227. tagging = tags(:misc).create_tagging(:taggable => posts(:thinking))
  52228. assert_equal "Post", tagging.taggable_type
  52229. end
  52230. def test_set_polymorphic_has_many
  52231. tagging = tags(:misc).taggings.create
  52232. posts(:thinking).taggings << tagging
  52233. assert_equal "Post", tagging.taggable_type
  52234. end
  52235. def test_set_polymorphic_has_one
  52236. tagging = tags(:misc).taggings.create
  52237. posts(:thinking).tagging = tagging
  52238. assert_equal "Post", tagging.taggable_type
  52239. end
  52240. def test_create_polymorphic_has_many_with_scope
  52241. old_count = posts(:welcome).taggings.count
  52242. tagging = posts(:welcome).taggings.create(:tag => tags(:misc))
  52243. assert_equal "Post", tagging.taggable_type
  52244. assert_equal old_count+1, posts(:welcome).taggings.count
  52245. end
  52246. def test_create_polymorphic_has_one_with_scope
  52247. old_count = Tagging.count
  52248. tagging = posts(:welcome).tagging.create(:tag => tags(:misc))
  52249. assert_equal "Post", tagging.taggable_type
  52250. assert_equal old_count+1, Tagging.count
  52251. end
  52252. def test_delete_polymorphic_has_many_with_delete_all
  52253. assert_equal 1, posts(:welcome).taggings.count
  52254. posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
  52255. post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)
  52256. old_count = Tagging.count
  52257. post.destroy
  52258. assert_equal old_count-1, Tagging.count
  52259. assert_equal 0, posts(:welcome).taggings.count
  52260. end
  52261. def test_delete_polymorphic_has_many_with_destroy
  52262. assert_equal 1, posts(:welcome).taggings.count
  52263. posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
  52264. post = find_post_with_dependency(1, :has_many, :taggings, :destroy)
  52265. old_count = Tagging.count
  52266. post.destroy
  52267. assert_equal old_count-1, Tagging.count
  52268. assert_equal 0, posts(:welcome).taggings.count
  52269. end
  52270. def test_delete_polymorphic_has_many_with_nullify
  52271. assert_equal 1, posts(:welcome).taggings.count
  52272. posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
  52273. post = find_post_with_dependency(1, :has_many, :taggings, :nullify)
  52274. old_count = Tagging.count
  52275. post.destroy
  52276. assert_equal old_count, Tagging.count
  52277. assert_equal 0, posts(:welcome).taggings.count
  52278. end
  52279. def test_delete_polymorphic_has_one_with_destroy
  52280. assert posts(:welcome).tagging
  52281. posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
  52282. post = find_post_with_dependency(1, :has_one, :tagging, :destroy)
  52283. old_count = Tagging.count
  52284. post.destroy
  52285. assert_equal old_count-1, Tagging.count
  52286. assert_nil posts(:welcome).tagging(true)
  52287. end
  52288. def test_delete_polymorphic_has_one_with_nullify
  52289. assert posts(:welcome).tagging
  52290. posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
  52291. post = find_post_with_dependency(1, :has_one, :tagging, :nullify)
  52292. old_count = Tagging.count
  52293. post.destroy
  52294. assert_equal old_count, Tagging.count
  52295. assert_nil posts(:welcome).tagging(true)
  52296. end
  52297. def test_has_many_with_piggyback
  52298. assert_equal "2", categories(:sti_test).authors.first.post_id.to_s
  52299. end
  52300. def test_include_has_many_through
  52301. posts = Post.find(:all, :order => 'posts.id')
  52302. posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id')
  52303. assert_equal posts.length, posts_with_authors.length
  52304. posts.length.times do |i|
  52305. assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length }
  52306. end
  52307. end
  52308. def test_include_polymorphic_has_one
  52309. post = Post.find_by_id(posts(:welcome).id, :include => :tagging)
  52310. tagging = taggings(:welcome_general)
  52311. assert_no_queries do
  52312. assert_equal tagging, post.tagging
  52313. end
  52314. end
  52315. def test_include_polymorphic_has_many_through
  52316. posts = Post.find(:all, :order => 'posts.id')
  52317. posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
  52318. assert_equal posts.length, posts_with_tags.length
  52319. posts.length.times do |i|
  52320. assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
  52321. end
  52322. end
  52323. def test_include_polymorphic_has_many
  52324. posts = Post.find(:all, :order => 'posts.id')
  52325. posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
  52326. assert_equal posts.length, posts_with_taggings.length
  52327. posts.length.times do |i|
  52328. assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
  52329. end
  52330. end
  52331. def test_has_many_find_all
  52332. assert_equal [categories(:general)], authors(:david).categories.find(:all)
  52333. end
  52334. def test_has_many_find_first
  52335. assert_equal categories(:general), authors(:david).categories.find(:first)
  52336. end
  52337. def test_has_many_find_conditions
  52338. assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
  52339. assert_equal nil, authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'")
  52340. end
  52341. def test_has_many_class_methods_called_by_method_missing
  52342. assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
  52343. # assert_equal nil, authors(:david).categories.find_by_name('Technology')
  52344. end
  52345. def test_has_many_going_through_join_model_with_custom_foreign_key
  52346. assert_equal [], posts(:thinking).authors
  52347. assert_equal [authors(:mary)], posts(:authorless).authors
  52348. end
  52349. def test_belongs_to_polymorphic_with_counter_cache
  52350. assert_equal 0, posts(:welcome)[:taggings_count]
  52351. tagging = posts(:welcome).taggings.create(:tag => tags(:general))
  52352. assert_equal 1, posts(:welcome, :reload)[:taggings_count]
  52353. tagging.destroy
  52354. assert posts(:welcome, :reload)[:taggings_count].zero?
  52355. end
  52356. def test_unavailable_through_reflection
  52357. assert_raises (ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings }
  52358. end
  52359. def test_has_many_through_join_model_with_conditions
  52360. assert_equal [], posts(:welcome).invalid_taggings
  52361. assert_equal [], posts(:welcome).invalid_tags
  52362. end
  52363. def test_has_many_polymorphic
  52364. assert_raises ActiveRecord::HasManyThroughAssociationPolymorphicError do
  52365. assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggables
  52366. end
  52367. assert_raises ActiveRecord::EagerLoadPolymorphicError do
  52368. assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggings.find(:all, :include => :taggable)
  52369. end
  52370. end
  52371. def test_has_many_through_has_many_find_all
  52372. assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
  52373. end
  52374. def test_has_many_through_has_many_find_all_with_custom_class
  52375. assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first
  52376. end
  52377. def test_has_many_through_has_many_find_first
  52378. assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id')
  52379. end
  52380. def test_has_many_through_has_many_find_conditions
  52381. assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, :conditions => "comments.type='SpecialComment'", :order => 'comments.id')
  52382. end
  52383. def test_has_many_through_has_many_find_by_id
  52384. assert_equal comments(:more_greetings), authors(:david).comments.find(2)
  52385. end
  52386. def test_has_many_through_polymorphic_has_one
  52387. assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
  52388. end
  52389. def test_has_many_through_polymorphic_has_many
  52390. assert_equal [taggings(:welcome_general), taggings(:thinking_general)], authors(:david).taggings.uniq.sort_by { |t| t.id }
  52391. end
  52392. def test_include_has_many_through_polymorphic_has_many
  52393. author = Author.find_by_id(authors(:david).id, :include => :taggings)
  52394. expected_taggings = [taggings(:welcome_general), taggings(:thinking_general)]
  52395. assert_no_queries do
  52396. assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
  52397. end
  52398. end
  52399. def test_has_many_through_has_many_through
  52400. assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
  52401. end
  52402. def test_has_many_through_habtm
  52403. assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
  52404. end
  52405. def test_eager_load_has_many_through_has_many
  52406. author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
  52407. SpecialComment.new; VerySpecialComment.new
  52408. assert_no_queries do
  52409. assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
  52410. end
  52411. end
  52412. def test_eager_belongs_to_and_has_one_not_singularized
  52413. assert_nothing_raised do
  52414. Author.find(:first, :include => :author_address)
  52415. AuthorAddress.find(:first, :include => :author)
  52416. end
  52417. end
  52418. def test_self_referential_has_many_through
  52419. assert_equal [authors(:mary)], authors(:david).favorite_authors
  52420. assert_equal [], authors(:mary).favorite_authors
  52421. end
  52422. def test_add_to_self_referential_has_many_through
  52423. new_author = Author.create(:name => "Bob")
  52424. authors(:david).author_favorites.create :favorite_author => new_author
  52425. assert_equal new_author, authors(:david).reload.favorite_authors.first
  52426. end
  52427. def test_has_many_through_uses_correct_attributes
  52428. assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"]
  52429. end
  52430. private
  52431. # create dynamic Post models to allow different dependency options
  52432. def find_post_with_dependency(post_id, association, association_name, dependency)
  52433. class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
  52434. Post.find(post_id).update_attribute :type, class_name
  52435. klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
  52436. klass.set_table_name 'posts'
  52437. klass.send(association, association_name, :as => :taggable, :dependent => dependency)
  52438. klass.find(post_id)
  52439. end
  52440. end
  52441. require 'abstract_unit'
  52442. require 'fixtures/developer'
  52443. require 'fixtures/project'
  52444. require 'fixtures/company'
  52445. require 'fixtures/topic'
  52446. require 'fixtures/reply'
  52447. require 'fixtures/computer'
  52448. require 'fixtures/customer'
  52449. require 'fixtures/order'
  52450. require 'fixtures/category'
  52451. require 'fixtures/post'
  52452. require 'fixtures/author'
  52453. # Can't declare new classes in test case methods, so tests before that
  52454. bad_collection_keys = false
  52455. begin
  52456. class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
  52457. rescue ArgumentError
  52458. bad_collection_keys = true
  52459. end
  52460. raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys
  52461. class AssociationsTest < Test::Unit::TestCase
  52462. fixtures :accounts, :companies, :developers, :projects, :developers_projects,
  52463. :computers
  52464. def test_force_reload
  52465. firm = Firm.new("name" => "A New Firm, Inc")
  52466. firm.save
  52467. firm.clients.each {|c|} # forcing to load all clients
  52468. assert firm.clients.empty?, "New firm shouldn't have client objects"
  52469. assert !firm.has_clients?, "New firm shouldn't have clients"
  52470. assert_equal 0, firm.clients.size, "New firm should have 0 clients"
  52471. client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
  52472. client.save
  52473. assert firm.clients.empty?, "New firm should have cached no client objects"
  52474. assert !firm.has_clients?, "New firm should have cached a no-clients response"
  52475. assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
  52476. assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
  52477. assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
  52478. end
  52479. def test_storing_in_pstore
  52480. require "tmpdir"
  52481. store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test")
  52482. File.delete(store_filename) if File.exists?(store_filename)
  52483. require "pstore"
  52484. apple = Firm.create("name" => "Apple")
  52485. natural = Client.new("name" => "Natural Company")
  52486. apple.clients << natural
  52487. db = PStore.new(store_filename)
  52488. db.transaction do
  52489. db["apple"] = apple
  52490. end
  52491. db = PStore.new(store_filename)
  52492. db.transaction do
  52493. assert_equal "Natural Company", db["apple"].clients.first.name
  52494. end
  52495. end
  52496. end
  52497. class HasOneAssociationsTest < Test::Unit::TestCase
  52498. fixtures :accounts, :companies, :developers, :projects, :developers_projects
  52499. def test_has_one
  52500. assert_equal companies(:first_firm).account, Account.find(1)
  52501. assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
  52502. end
  52503. def test_proxy_assignment
  52504. company = companies(:first_firm)
  52505. assert_nothing_raised { company.account = company.account }
  52506. end
  52507. def test_triple_equality
  52508. assert Account === companies(:first_firm).account
  52509. assert companies(:first_firm).account === Account
  52510. end
  52511. def test_type_mismatch
  52512. assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
  52513. assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
  52514. end
  52515. def test_natural_assignment
  52516. apple = Firm.create("name" => "Apple")
  52517. citibank = Account.create("credit_limit" => 10)
  52518. apple.account = citibank
  52519. assert_equal apple.id, citibank.firm_id
  52520. end
  52521. def test_natural_assignment_to_nil
  52522. old_account_id = companies(:first_firm).account.id
  52523. companies(:first_firm).account = nil
  52524. companies(:first_firm).save
  52525. assert_nil companies(:first_firm).account
  52526. # account is dependent, therefore is destroyed when reference to owner is lost
  52527. assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
  52528. end
  52529. def test_assignment_without_replacement
  52530. apple = Firm.create("name" => "Apple")
  52531. citibank = Account.create("credit_limit" => 10)
  52532. apple.account = citibank
  52533. assert_equal apple.id, citibank.firm_id
  52534. hsbc = apple.build_account({ :credit_limit => 20}, false)
  52535. assert_equal apple.id, hsbc.firm_id
  52536. hsbc.save
  52537. assert_equal apple.id, citibank.firm_id
  52538. nykredit = apple.create_account({ :credit_limit => 30}, false)
  52539. assert_equal apple.id, nykredit.firm_id
  52540. assert_equal apple.id, citibank.firm_id
  52541. assert_equal apple.id, hsbc.firm_id
  52542. end
  52543. def test_assignment_without_replacement_on_create
  52544. apple = Firm.create("name" => "Apple")
  52545. citibank = Account.create("credit_limit" => 10)
  52546. apple.account = citibank
  52547. assert_equal apple.id, citibank.firm_id
  52548. hsbc = apple.create_account({:credit_limit => 10}, false)
  52549. assert_equal apple.id, hsbc.firm_id
  52550. hsbc.save
  52551. assert_equal apple.id, citibank.firm_id
  52552. end
  52553. def test_dependence
  52554. num_accounts = Account.count
  52555. firm = Firm.find(1)
  52556. assert !firm.account.nil?
  52557. firm.destroy
  52558. assert_equal num_accounts - 1, Account.count
  52559. end
  52560. def test_succesful_build_association
  52561. firm = Firm.new("name" => "GlobalMegaCorp")
  52562. firm.save
  52563. account = firm.build_account("credit_limit" => 1000)
  52564. assert account.save
  52565. assert_equal account, firm.account
  52566. end
  52567. def test_failing_build_association
  52568. firm = Firm.new("name" => "GlobalMegaCorp")
  52569. firm.save
  52570. account = firm.build_account
  52571. assert !account.save
  52572. assert_equal "can't be empty", account.errors.on("credit_limit")
  52573. end
  52574. def test_build_association_twice_without_saving_affects_nothing
  52575. count_of_account = Account.count
  52576. firm = Firm.find(:first)
  52577. account1 = firm.build_account("credit_limit" => 1000)
  52578. account2 = firm.build_account("credit_limit" => 2000)
  52579. assert_equal count_of_account, Account.count
  52580. end
  52581. def test_create_association
  52582. firm = Firm.new("name" => "GlobalMegaCorp")
  52583. firm.save
  52584. assert_equal firm.create_account("credit_limit" => 1000), firm.account
  52585. end
  52586. def test_build
  52587. firm = Firm.new("name" => "GlobalMegaCorp")
  52588. firm.save
  52589. firm.account = account = Account.new("credit_limit" => 1000)
  52590. assert_equal account, firm.account
  52591. assert account.save
  52592. assert_equal account, firm.account
  52593. end
  52594. def test_build_before_child_saved
  52595. firm = Firm.find(1)
  52596. account = firm.account.build("credit_limit" => 1000)
  52597. assert_equal account, firm.account
  52598. assert account.new_record?
  52599. assert firm.save
  52600. assert_equal account, firm.account
  52601. assert !account.new_record?
  52602. end
  52603. def test_build_before_either_saved
  52604. firm = Firm.new("name" => "GlobalMegaCorp")
  52605. firm.account = account = Account.new("credit_limit" => 1000)
  52606. assert_equal account, firm.account
  52607. assert account.new_record?
  52608. assert firm.save
  52609. assert_equal account, firm.account
  52610. assert !account.new_record?
  52611. end
  52612. def test_failing_build_association
  52613. firm = Firm.new("name" => "GlobalMegaCorp")
  52614. firm.save
  52615. firm.account = account = Account.new
  52616. assert_equal account, firm.account
  52617. assert !account.save
  52618. assert_equal account, firm.account
  52619. assert_equal "can't be empty", account.errors.on("credit_limit")
  52620. end
  52621. def test_create
  52622. firm = Firm.new("name" => "GlobalMegaCorp")
  52623. firm.save
  52624. firm.account = account = Account.create("credit_limit" => 1000)
  52625. assert_equal account, firm.account
  52626. end
  52627. def test_create_before_save
  52628. firm = Firm.new("name" => "GlobalMegaCorp")
  52629. firm.account = account = Account.create("credit_limit" => 1000)
  52630. assert_equal account, firm.account
  52631. end
  52632. def test_dependence_with_missing_association
  52633. Account.destroy_all
  52634. firm = Firm.find(1)
  52635. assert firm.account.nil?
  52636. firm.destroy
  52637. end
  52638. def test_assignment_before_parent_saved
  52639. firm = Firm.new("name" => "GlobalMegaCorp")
  52640. firm.account = a = Account.find(1)
  52641. assert firm.new_record?
  52642. assert_equal a, firm.account
  52643. assert firm.save
  52644. assert_equal a, firm.account
  52645. assert_equal a, firm.account(true)
  52646. end
  52647. def test_assignment_before_child_saved
  52648. firm = Firm.find(1)
  52649. firm.account = a = Account.new("credit_limit" => 1000)
  52650. assert !a.new_record?
  52651. assert_equal a, firm.account
  52652. assert_equal a, firm.account
  52653. assert_equal a, firm.account(true)
  52654. end
  52655. def test_assignment_before_either_saved
  52656. firm = Firm.new("name" => "GlobalMegaCorp")
  52657. firm.account = a = Account.new("credit_limit" => 1000)
  52658. assert firm.new_record?
  52659. assert a.new_record?
  52660. assert_equal a, firm.account
  52661. assert firm.save
  52662. assert !firm.new_record?
  52663. assert !a.new_record?
  52664. assert_equal a, firm.account
  52665. assert_equal a, firm.account(true)
  52666. end
  52667. end
  52668. class HasManyAssociationsTest < Test::Unit::TestCase
  52669. fixtures :accounts, :companies, :developers, :projects,
  52670. :developers_projects, :topics
  52671. def setup
  52672. Client.destroyed_client_ids.clear
  52673. end
  52674. def force_signal37_to_load_all_clients_of_firm
  52675. companies(:first_firm).clients_of_firm.each {|f| }
  52676. end
  52677. def test_counting
  52678. assert_equal 2, Firm.find(:first).clients.count
  52679. end
  52680. def test_finding
  52681. assert_equal 2, Firm.find(:first).clients.length
  52682. end
  52683. def test_find_many_with_merged_options
  52684. assert_equal 1, companies(:first_firm).limited_clients.size
  52685. assert_equal 1, companies(:first_firm).limited_clients.find(:all).size
  52686. assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size
  52687. end
  52688. def test_triple_equality
  52689. assert !(Array === Firm.find(:first).clients)
  52690. assert Firm.find(:first).clients === Array
  52691. end
  52692. def test_finding_default_orders
  52693. assert_equal "Summit", Firm.find(:first).clients.first.name
  52694. end
  52695. def test_finding_with_different_class_name_and_order
  52696. assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name
  52697. end
  52698. def test_finding_with_foreign_key
  52699. assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name
  52700. end
  52701. def test_finding_with_condition
  52702. assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name
  52703. end
  52704. def test_finding_using_sql
  52705. firm = Firm.find(:first)
  52706. first_client = firm.clients_using_sql.first
  52707. assert_not_nil first_client
  52708. assert_equal "Microsoft", first_client.name
  52709. assert_equal 1, firm.clients_using_sql.size
  52710. assert_equal 1, Firm.find(:first).clients_using_sql.size
  52711. end
  52712. def test_counting_using_sql
  52713. assert_equal 1, Firm.find(:first).clients_using_counter_sql.size
  52714. assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size
  52715. end
  52716. def test_counting_non_existant_items_using_sql
  52717. assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size
  52718. end
  52719. def test_belongs_to_sanity
  52720. c = Client.new
  52721. assert_nil c.firm
  52722. if c.firm
  52723. assert false, "belongs_to failed if check"
  52724. end
  52725. unless c.firm
  52726. else
  52727. assert false, "belongs_to failed unless check"
  52728. end
  52729. end
  52730. def test_find_ids
  52731. firm = Firm.find(:first)
  52732. assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find }
  52733. client = firm.clients.find(2)
  52734. assert_kind_of Client, client
  52735. client_ary = firm.clients.find([2])
  52736. assert_kind_of Array, client_ary
  52737. assert_equal client, client_ary.first
  52738. client_ary = firm.clients.find(2, 3)
  52739. assert_kind_of Array, client_ary
  52740. assert_equal 2, client_ary.size
  52741. assert_equal client, client_ary.first
  52742. assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
  52743. end
  52744. def test_find_all
  52745. firm = Firm.find_first
  52746. assert_equal firm.clients, firm.clients.find_all
  52747. assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length
  52748. assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length
  52749. end
  52750. def test_find_all_sanitized
  52751. firm = Firm.find_first
  52752. assert_equal firm.clients.find_all("name = 'Summit'"), firm.clients.find_all(["name = '%s'", "Summit"])
  52753. summit = firm.clients.find(:all, :conditions => "name = 'Summit'")
  52754. assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"])
  52755. assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }])
  52756. end
  52757. def test_find_first
  52758. firm = Firm.find_first
  52759. client2 = Client.find(2)
  52760. assert_equal firm.clients.first, firm.clients.find_first
  52761. assert_equal client2, firm.clients.find_first("#{QUOTED_TYPE} = 'Client'")
  52762. assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'")
  52763. end
  52764. def test_find_first_sanitized
  52765. firm = Firm.find_first
  52766. client2 = Client.find(2)
  52767. assert_equal client2, firm.clients.find_first(["#{QUOTED_TYPE} = ?", "Client"])
  52768. assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'])
  52769. assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
  52770. end
  52771. def test_find_in_collection
  52772. assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
  52773. assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
  52774. end
  52775. def test_find_grouped
  52776. all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1")
  52777. grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count')
  52778. assert_equal 2, all_clients_of_firm1.size
  52779. assert_equal 1, grouped_clients_of_firm1.size
  52780. end
  52781. def test_adding
  52782. force_signal37_to_load_all_clients_of_firm
  52783. natural = Client.new("name" => "Natural Company")
  52784. companies(:first_firm).clients_of_firm << natural
  52785. assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection
  52786. assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db
  52787. assert_equal natural, companies(:first_firm).clients_of_firm.last
  52788. end
  52789. def test_adding_a_mismatch_class
  52790. assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil }
  52791. assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 }
  52792. assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) }
  52793. end
  52794. def test_adding_a_collection
  52795. force_signal37_to_load_all_clients_of_firm
  52796. companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
  52797. assert_equal 3, companies(:first_firm).clients_of_firm.size
  52798. assert_equal 3, companies(:first_firm).clients_of_firm(true).size
  52799. end
  52800. def test_adding_before_save
  52801. no_of_firms = Firm.count
  52802. no_of_clients = Client.count
  52803. new_firm = Firm.new("name" => "A New Firm, Inc")
  52804. new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
  52805. new_firm.clients_of_firm << (c = Client.new("name" => "Apple"))
  52806. assert new_firm.new_record?
  52807. assert c.new_record?
  52808. assert_equal 2, new_firm.clients_of_firm.size
  52809. assert_equal no_of_firms, Firm.count # Firm was not saved to database.
  52810. assert_equal no_of_clients, Client.count # Clients were not saved to database.
  52811. assert new_firm.save
  52812. assert !new_firm.new_record?
  52813. assert !c.new_record?
  52814. assert_equal new_firm, c.firm
  52815. assert_equal no_of_firms+1, Firm.count # Firm was saved to database.
  52816. assert_equal no_of_clients+2, Client.count # Clients were saved to database.
  52817. assert_equal 2, new_firm.clients_of_firm.size
  52818. assert_equal 2, new_firm.clients_of_firm(true).size
  52819. end
  52820. def test_invalid_adding
  52821. firm = Firm.find(1)
  52822. assert !(firm.clients_of_firm << c = Client.new)
  52823. assert c.new_record?
  52824. assert !firm.valid?
  52825. assert !firm.save
  52826. assert c.new_record?
  52827. end
  52828. def test_invalid_adding_before_save
  52829. no_of_firms = Firm.count
  52830. no_of_clients = Client.count
  52831. new_firm = Firm.new("name" => "A New Firm, Inc")
  52832. new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
  52833. assert c.new_record?
  52834. assert !c.valid?
  52835. assert !new_firm.valid?
  52836. assert !new_firm.save
  52837. assert c.new_record?
  52838. assert new_firm.new_record?
  52839. end
  52840. def test_build
  52841. new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client")
  52842. assert_equal "Another Client", new_client.name
  52843. assert new_client.new_record?
  52844. assert_equal new_client, companies(:first_firm).clients_of_firm.last
  52845. assert companies(:first_firm).save
  52846. assert !new_client.new_record?
  52847. assert_equal 2, companies(:first_firm).clients_of_firm(true).size
  52848. end
  52849. def test_build_many
  52850. new_clients = companies(:first_firm).clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}])
  52851. assert_equal 2, new_clients.size
  52852. assert companies(:first_firm).save
  52853. assert_equal 3, companies(:first_firm).clients_of_firm(true).size
  52854. end
  52855. def test_invalid_build
  52856. new_client = companies(:first_firm).clients_of_firm.build
  52857. assert new_client.new_record?
  52858. assert !new_client.valid?
  52859. assert_equal new_client, companies(:first_firm).clients_of_firm.last
  52860. assert !companies(:first_firm).save
  52861. assert new_client.new_record?
  52862. assert_equal 1, companies(:first_firm).clients_of_firm(true).size
  52863. end
  52864. def test_create
  52865. force_signal37_to_load_all_clients_of_firm
  52866. new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
  52867. assert !new_client.new_record?
  52868. assert_equal new_client, companies(:first_firm).clients_of_firm.last
  52869. assert_equal new_client, companies(:first_firm).clients_of_firm(true).last
  52870. end
  52871. def test_create_many
  52872. companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
  52873. assert_equal 3, companies(:first_firm).clients_of_firm(true).size
  52874. end
  52875. def test_find_or_create
  52876. number_of_clients = companies(:first_firm).clients.size
  52877. the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
  52878. assert_equal number_of_clients + 1, companies(:first_firm, :refresh).clients.size
  52879. assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
  52880. assert_equal number_of_clients + 1, companies(:first_firm, :refresh).clients.size
  52881. end
  52882. def test_deleting
  52883. force_signal37_to_load_all_clients_of_firm
  52884. companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
  52885. assert_equal 0, companies(:first_firm).clients_of_firm.size
  52886. assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  52887. end
  52888. def test_deleting_before_save
  52889. new_firm = Firm.new("name" => "A New Firm, Inc.")
  52890. new_client = new_firm.clients_of_firm.build("name" => "Another Client")
  52891. assert_equal 1, new_firm.clients_of_firm.size
  52892. new_firm.clients_of_firm.delete(new_client)
  52893. assert_equal 0, new_firm.clients_of_firm.size
  52894. end
  52895. def test_deleting_a_collection
  52896. force_signal37_to_load_all_clients_of_firm
  52897. companies(:first_firm).clients_of_firm.create("name" => "Another Client")
  52898. assert_equal 2, companies(:first_firm).clients_of_firm.size
  52899. companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]])
  52900. assert_equal 0, companies(:first_firm).clients_of_firm.size
  52901. assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  52902. end
  52903. def test_delete_all
  52904. force_signal37_to_load_all_clients_of_firm
  52905. companies(:first_firm).clients_of_firm.create("name" => "Another Client")
  52906. assert_equal 2, companies(:first_firm).clients_of_firm.size
  52907. companies(:first_firm).clients_of_firm.delete_all
  52908. assert_equal 0, companies(:first_firm).clients_of_firm.size
  52909. assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  52910. end
  52911. def test_delete_all_with_not_yet_loaded_association_collection
  52912. force_signal37_to_load_all_clients_of_firm
  52913. companies(:first_firm).clients_of_firm.create("name" => "Another Client")
  52914. assert_equal 2, companies(:first_firm).clients_of_firm.size
  52915. companies(:first_firm).clients_of_firm.reset
  52916. companies(:first_firm).clients_of_firm.delete_all
  52917. assert_equal 0, companies(:first_firm).clients_of_firm.size
  52918. assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  52919. end
  52920. def test_clearing_an_association_collection
  52921. firm = companies(:first_firm)
  52922. client_id = firm.clients_of_firm.first.id
  52923. assert_equal 1, firm.clients_of_firm.size
  52924. firm.clients_of_firm.clear
  52925. assert_equal 0, firm.clients_of_firm.size
  52926. assert_equal 0, firm.clients_of_firm(true).size
  52927. assert_equal [], Client.destroyed_client_ids[firm.id]
  52928. # Should not be destroyed since the association is not dependent.
  52929. assert_nothing_raised do
  52930. assert Client.find(client_id).firm.nil?
  52931. end
  52932. end
  52933. def test_clearing_a_dependent_association_collection
  52934. firm = companies(:first_firm)
  52935. client_id = firm.dependent_clients_of_firm.first.id
  52936. assert_equal 1, firm.dependent_clients_of_firm.size
  52937. # :dependent means destroy is called on each client
  52938. firm.dependent_clients_of_firm.clear
  52939. assert_equal 0, firm.dependent_clients_of_firm.size
  52940. assert_equal 0, firm.dependent_clients_of_firm(true).size
  52941. assert_equal [client_id], Client.destroyed_client_ids[firm.id]
  52942. # Should be destroyed since the association is dependent.
  52943. assert Client.find_by_id(client_id).nil?
  52944. end
  52945. def test_clearing_an_exclusively_dependent_association_collection
  52946. firm = companies(:first_firm)
  52947. client_id = firm.exclusively_dependent_clients_of_firm.first.id
  52948. assert_equal 1, firm.exclusively_dependent_clients_of_firm.size
  52949. assert_equal [], Client.destroyed_client_ids[firm.id]
  52950. # :exclusively_dependent means each client is deleted directly from
  52951. # the database without looping through them calling destroy.
  52952. firm.exclusively_dependent_clients_of_firm.clear
  52953. assert_equal 0, firm.exclusively_dependent_clients_of_firm.size
  52954. assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size
  52955. assert_equal [3], Client.destroyed_client_ids[firm.id]
  52956. # Should be destroyed since the association is exclusively dependent.
  52957. assert Client.find_by_id(client_id).nil?
  52958. end
  52959. def test_clearing_without_initial_access
  52960. firm = companies(:first_firm)
  52961. firm.clients_of_firm.clear
  52962. assert_equal 0, firm.clients_of_firm.size
  52963. assert_equal 0, firm.clients_of_firm(true).size
  52964. end
  52965. def test_deleting_a_item_which_is_not_in_the_collection
  52966. force_signal37_to_load_all_clients_of_firm
  52967. summit = Client.find_first("name = 'Summit'")
  52968. companies(:first_firm).clients_of_firm.delete(summit)
  52969. assert_equal 1, companies(:first_firm).clients_of_firm.size
  52970. assert_equal 1, companies(:first_firm).clients_of_firm(true).size
  52971. assert_equal 2, summit.client_of
  52972. end
  52973. def test_deleting_type_mismatch
  52974. david = Developer.find(1)
  52975. david.projects.reload
  52976. assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) }
  52977. end
  52978. def test_deleting_self_type_mismatch
  52979. david = Developer.find(1)
  52980. david.projects.reload
  52981. assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) }
  52982. end
  52983. def test_destroy_all
  52984. force_signal37_to_load_all_clients_of_firm
  52985. assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load"
  52986. companies(:first_firm).clients_of_firm.destroy_all
  52987. assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
  52988. assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
  52989. end
  52990. def test_dependence
  52991. firm = companies(:first_firm)
  52992. assert_equal 2, firm.clients.size
  52993. firm.destroy
  52994. assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty?
  52995. end
  52996. def test_destroy_dependent_when_deleted_from_association
  52997. firm = Firm.find(:first)
  52998. assert_equal 2, firm.clients.size
  52999. client = firm.clients.first
  53000. firm.clients.delete(client)
  53001. assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) }
  53002. assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) }
  53003. assert_equal 1, firm.clients.size
  53004. end
  53005. def test_three_levels_of_dependence
  53006. topic = Topic.create "title" => "neat and simple"
  53007. reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it"
  53008. silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining"
  53009. assert_nothing_raised { topic.destroy }
  53010. end
  53011. uses_transaction :test_dependence_with_transaction_support_on_failure
  53012. def test_dependence_with_transaction_support_on_failure
  53013. firm = companies(:first_firm)
  53014. clients = firm.clients
  53015. assert_equal 2, clients.length
  53016. clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }
  53017. firm.destroy rescue "do nothing"
  53018. assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size
  53019. end
  53020. def test_dependence_on_account
  53021. num_accounts = Account.count
  53022. companies(:first_firm).destroy
  53023. assert_equal num_accounts - 1, Account.count
  53024. end
  53025. def test_depends_and_nullify
  53026. num_accounts = Account.count
  53027. num_companies = Company.count
  53028. core = companies(:rails_core)
  53029. assert_equal accounts(:rails_core_account), core.account
  53030. assert_equal [companies(:leetsoft), companies(:jadedpixel)], core.companies
  53031. core.destroy
  53032. assert_nil accounts(:rails_core_account).reload.firm_id
  53033. assert_nil companies(:leetsoft).reload.client_of
  53034. assert_nil companies(:jadedpixel).reload.client_of
  53035. assert_equal num_accounts, Account.count
  53036. end
  53037. def test_included_in_collection
  53038. assert companies(:first_firm).clients.include?(Client.find(2))
  53039. end
  53040. def test_adding_array_and_collection
  53041. assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients }
  53042. end
  53043. def test_find_all_without_conditions
  53044. firm = companies(:first_firm)
  53045. assert_equal 2, firm.clients.find(:all).length
  53046. end
  53047. def test_replace_with_less
  53048. firm = Firm.find(:first)
  53049. firm.clients = [companies(:first_client)]
  53050. assert firm.save, "Could not save firm"
  53051. firm.reload
  53052. assert_equal 1, firm.clients.length
  53053. end
  53054. def test_replace_with_new
  53055. firm = Firm.find(:first)
  53056. new_client = Client.new("name" => "New Client")
  53057. firm.clients = [companies(:second_client),new_client]
  53058. firm.save
  53059. firm.reload
  53060. assert_equal 2, firm.clients.length
  53061. assert !firm.clients.include?(:first_client)
  53062. end
  53063. def test_replace_on_new_object
  53064. firm = Firm.new("name" => "New Firm")
  53065. firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
  53066. assert firm.save
  53067. firm.reload
  53068. assert_equal 2, firm.clients.length
  53069. assert firm.clients.include?(Client.find_by_name("New Client"))
  53070. end
  53071. def test_assign_ids
  53072. firm = Firm.new("name" => "Apple")
  53073. firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
  53074. firm.save
  53075. firm.reload
  53076. assert_equal 2, firm.clients.length
  53077. assert firm.clients.include?(companies(:second_client))
  53078. end
  53079. end
  53080. class BelongsToAssociationsTest < Test::Unit::TestCase
  53081. fixtures :accounts, :companies, :developers, :projects, :topics,
  53082. :developers_projects, :computers, :authors, :posts
  53083. def test_belongs_to
  53084. Client.find(3).firm.name
  53085. assert_equal companies(:first_firm).name, Client.find(3).firm.name
  53086. assert !Client.find(3).firm.nil?, "Microsoft should have a firm"
  53087. end
  53088. def test_proxy_assignment
  53089. account = Account.find(1)
  53090. assert_nothing_raised { account.firm = account.firm }
  53091. end
  53092. def test_triple_equality
  53093. assert Client.find(3).firm === Firm
  53094. assert Firm === Client.find(3).firm
  53095. end
  53096. def test_type_mismatch
  53097. assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
  53098. assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
  53099. end
  53100. def test_natural_assignment
  53101. apple = Firm.create("name" => "Apple")
  53102. citibank = Account.create("credit_limit" => 10)
  53103. citibank.firm = apple
  53104. assert_equal apple.id, citibank.firm_id
  53105. end
  53106. def test_creating_the_belonging_object
  53107. citibank = Account.create("credit_limit" => 10)
  53108. apple = citibank.create_firm("name" => "Apple")
  53109. assert_equal apple, citibank.firm
  53110. citibank.save
  53111. citibank.reload
  53112. assert_equal apple, citibank.firm
  53113. end
  53114. def test_building_the_belonging_object
  53115. citibank = Account.create("credit_limit" => 10)
  53116. apple = citibank.build_firm("name" => "Apple")
  53117. citibank.save
  53118. assert_equal apple.id, citibank.firm_id
  53119. end
  53120. def test_natural_assignment_to_nil
  53121. client = Client.find(3)
  53122. client.firm = nil
  53123. client.save
  53124. assert_nil client.firm(true)
  53125. assert_nil client.client_of
  53126. end
  53127. def test_with_different_class_name
  53128. assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
  53129. assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm"
  53130. end
  53131. def test_with_condition
  53132. assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
  53133. assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
  53134. end
  53135. def test_belongs_to_counter
  53136. debate = Topic.create("title" => "debate")
  53137. assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
  53138. trash = debate.replies.create("title" => "blah!", "content" => "world around!")
  53139. assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
  53140. trash.destroy
  53141. assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
  53142. end
  53143. def test_belongs_to_counter_with_reassigning
  53144. t1 = Topic.create("title" => "t1")
  53145. t2 = Topic.create("title" => "t2")
  53146. r1 = Reply.new("title" => "r1", "content" => "r1")
  53147. r1.topic = t1
  53148. assert r1.save
  53149. assert_equal 1, Topic.find(t1.id).replies.size
  53150. assert_equal 0, Topic.find(t2.id).replies.size
  53151. r1.topic = Topic.find(t2.id)
  53152. assert r1.save
  53153. assert_equal 0, Topic.find(t1.id).replies.size
  53154. assert_equal 1, Topic.find(t2.id).replies.size
  53155. r1.topic = nil
  53156. assert_equal 0, Topic.find(t1.id).replies.size
  53157. assert_equal 0, Topic.find(t2.id).replies.size
  53158. r1.topic = t1
  53159. assert_equal 1, Topic.find(t1.id).replies.size
  53160. assert_equal 0, Topic.find(t2.id).replies.size
  53161. r1.destroy
  53162. assert_equal 0, Topic.find(t1.id).replies.size
  53163. assert_equal 0, Topic.find(t2.id).replies.size
  53164. end
  53165. def test_assignment_before_parent_saved
  53166. client = Client.find(:first)
  53167. apple = Firm.new("name" => "Apple")
  53168. client.firm = apple
  53169. assert_equal apple, client.firm
  53170. assert apple.new_record?
  53171. assert client.save
  53172. assert apple.save
  53173. assert !apple.new_record?
  53174. assert_equal apple, client.firm
  53175. assert_equal apple, client.firm(true)
  53176. end
  53177. def test_assignment_before_child_saved
  53178. final_cut = Client.new("name" => "Final Cut")
  53179. firm = Firm.find(1)
  53180. final_cut.firm = firm
  53181. assert final_cut.new_record?
  53182. assert final_cut.save
  53183. assert !final_cut.new_record?
  53184. assert !firm.new_record?
  53185. assert_equal firm, final_cut.firm
  53186. assert_equal firm, final_cut.firm(true)
  53187. end
  53188. def test_assignment_before_either_saved
  53189. final_cut = Client.new("name" => "Final Cut")
  53190. apple = Firm.new("name" => "Apple")
  53191. final_cut.firm = apple
  53192. assert final_cut.new_record?
  53193. assert apple.new_record?
  53194. assert final_cut.save
  53195. assert !final_cut.new_record?
  53196. assert !apple.new_record?
  53197. assert_equal apple, final_cut.firm
  53198. assert_equal apple, final_cut.firm(true)
  53199. end
  53200. def test_new_record_with_foreign_key_but_no_object
  53201. c = Client.new("firm_id" => 1)
  53202. assert_equal Firm.find(:first), c.firm_with_basic_id
  53203. end
  53204. def test_forgetting_the_load_when_foreign_key_enters_late
  53205. c = Client.new
  53206. assert_nil c.firm_with_basic_id
  53207. c.firm_id = 1
  53208. assert_equal Firm.find(:first), c.firm_with_basic_id
  53209. end
  53210. def test_field_name_same_as_foreign_key
  53211. computer = Computer.find(1)
  53212. assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
  53213. end
  53214. def test_counter_cache
  53215. topic = Topic.create :title => "Zoom-zoom-zoom"
  53216. assert_equal 0, topic[:replies_count]
  53217. reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
  53218. reply.topic = topic
  53219. assert_equal 1, topic.reload[:replies_count]
  53220. assert_equal 1, topic.replies.size
  53221. topic[:replies_count] = 15
  53222. assert_equal 15, topic.replies.size
  53223. end
  53224. def test_custom_counter_cache
  53225. reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
  53226. assert_equal 0, reply[:replies_count]
  53227. silly = SillyReply.create(:title => "gaga", :content => "boo-boo")
  53228. silly.reply = reply
  53229. assert_equal 1, reply.reload[:replies_count]
  53230. assert_equal 1, reply.replies.size
  53231. reply[:replies_count] = 17
  53232. assert_equal 17, reply.replies.size
  53233. end
  53234. def test_store_two_association_with_one_save
  53235. num_orders = Order.count
  53236. num_customers = Customer.count
  53237. order = Order.new
  53238. customer1 = order.billing = Customer.new
  53239. customer2 = order.shipping = Customer.new
  53240. assert order.save
  53241. assert_equal customer1, order.billing
  53242. assert_equal customer2, order.shipping
  53243. order.reload
  53244. assert_equal customer1, order.billing
  53245. assert_equal customer2, order.shipping
  53246. assert_equal num_orders +1, Order.count
  53247. assert_equal num_customers +2, Customer.count
  53248. end
  53249. def test_store_association_in_two_relations_with_one_save
  53250. num_orders = Order.count
  53251. num_customers = Customer.count
  53252. order = Order.new
  53253. customer = order.billing = order.shipping = Customer.new
  53254. assert order.save
  53255. assert_equal customer, order.billing
  53256. assert_equal customer, order.shipping
  53257. order.reload
  53258. assert_equal customer, order.billing
  53259. assert_equal customer, order.shipping
  53260. assert_equal num_orders +1, Order.count
  53261. assert_equal num_customers +1, Customer.count
  53262. end
  53263. def test_store_association_in_two_relations_with_one_save_in_existing_object
  53264. num_orders = Order.count
  53265. num_customers = Customer.count
  53266. order = Order.create
  53267. customer = order.billing = order.shipping = Customer.new
  53268. assert order.save
  53269. assert_equal customer, order.billing
  53270. assert_equal customer, order.shipping
  53271. order.reload
  53272. assert_equal customer, order.billing
  53273. assert_equal customer, order.shipping
  53274. assert_equal num_orders +1, Order.count
  53275. assert_equal num_customers +1, Customer.count
  53276. end
  53277. def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
  53278. num_orders = Order.count
  53279. num_customers = Customer.count
  53280. order = Order.create
  53281. customer = order.billing = order.shipping = Customer.new
  53282. assert order.save
  53283. assert_equal customer, order.billing
  53284. assert_equal customer, order.shipping
  53285. order.reload
  53286. customer = order.billing = order.shipping = Customer.new
  53287. assert order.save
  53288. order.reload
  53289. assert_equal customer, order.billing
  53290. assert_equal customer, order.shipping
  53291. assert_equal num_orders +1, Order.count
  53292. assert_equal num_customers +2, Customer.count
  53293. end
  53294. def test_association_assignment_sticks
  53295. post = Post.find(:first)
  53296. author1, author2 = Author.find(:all, :limit => 2)
  53297. assert_not_nil author1
  53298. assert_not_nil author2
  53299. # make sure the association is loaded
  53300. post.author
  53301. # set the association by id, directly
  53302. post.author_id = author2.id
  53303. # save and reload
  53304. post.save!
  53305. post.reload
  53306. # the author id of the post should be the id we set
  53307. assert_equal post.author_id, author2.id
  53308. end
  53309. end
  53310. class ProjectWithAfterCreateHook < ActiveRecord::Base
  53311. set_table_name 'projects'
  53312. has_and_belongs_to_many :developers,
  53313. :class_name => "DeveloperForProjectWithAfterCreateHook",
  53314. :join_table => "developers_projects",
  53315. :foreign_key => "project_id",
  53316. :association_foreign_key => "developer_id"
  53317. after_create :add_david
  53318. def add_david
  53319. david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
  53320. david.projects << self
  53321. end
  53322. end
  53323. class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base
  53324. set_table_name 'developers'
  53325. has_and_belongs_to_many :projects,
  53326. :class_name => "ProjectWithAfterCreateHook",
  53327. :join_table => "developers_projects",
  53328. :association_foreign_key => "project_id",
  53329. :foreign_key => "developer_id"
  53330. end
  53331. class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
  53332. fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects
  53333. def test_has_and_belongs_to_many
  53334. david = Developer.find(1)
  53335. assert !david.projects.empty?
  53336. assert_equal 2, david.projects.size
  53337. active_record = Project.find(1)
  53338. assert !active_record.developers.empty?
  53339. assert_equal 3, active_record.developers.size
  53340. assert active_record.developers.include?(david)
  53341. end
  53342. def test_triple_equality
  53343. assert !(Array === Developer.find(1).projects)
  53344. assert Developer.find(1).projects === Array
  53345. end
  53346. def test_adding_single
  53347. jamis = Developer.find(2)
  53348. jamis.projects.reload # causing the collection to load
  53349. action_controller = Project.find(2)
  53350. assert_equal 1, jamis.projects.size
  53351. assert_equal 1, action_controller.developers.size
  53352. jamis.projects << action_controller
  53353. assert_equal 2, jamis.projects.size
  53354. assert_equal 2, jamis.projects(true).size
  53355. assert_equal 2, action_controller.developers(true).size
  53356. end
  53357. def test_adding_type_mismatch
  53358. jamis = Developer.find(2)
  53359. assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil }
  53360. assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 }
  53361. end
  53362. def test_adding_from_the_project
  53363. jamis = Developer.find(2)
  53364. action_controller = Project.find(2)
  53365. action_controller.developers.reload
  53366. assert_equal 1, jamis.projects.size
  53367. assert_equal 1, action_controller.developers.size
  53368. action_controller.developers << jamis
  53369. assert_equal 2, jamis.projects(true).size
  53370. assert_equal 2, action_controller.developers.size
  53371. assert_equal 2, action_controller.developers(true).size
  53372. end
  53373. def test_adding_from_the_project_fixed_timestamp
  53374. jamis = Developer.find(2)
  53375. action_controller = Project.find(2)
  53376. action_controller.developers.reload
  53377. assert_equal 1, jamis.projects.size
  53378. assert_equal 1, action_controller.developers.size
  53379. updated_at = jamis.updated_at
  53380. action_controller.developers << jamis
  53381. assert_equal updated_at, jamis.updated_at
  53382. assert_equal 2, jamis.projects(true).size
  53383. assert_equal 2, action_controller.developers.size
  53384. assert_equal 2, action_controller.developers(true).size
  53385. end
  53386. def test_adding_multiple
  53387. aredridel = Developer.new("name" => "Aredridel")
  53388. aredridel.save
  53389. aredridel.projects.reload
  53390. aredridel.projects.push(Project.find(1), Project.find(2))
  53391. assert_equal 2, aredridel.projects.size
  53392. assert_equal 2, aredridel.projects(true).size
  53393. end
  53394. def test_adding_a_collection
  53395. aredridel = Developer.new("name" => "Aredridel")
  53396. aredridel.save
  53397. aredridel.projects.reload
  53398. aredridel.projects.concat([Project.find(1), Project.find(2)])
  53399. assert_equal 2, aredridel.projects.size
  53400. assert_equal 2, aredridel.projects(true).size
  53401. end
  53402. def test_adding_uses_default_values_on_join_table
  53403. ac = projects(:action_controller)
  53404. assert !developers(:jamis).projects.include?(ac)
  53405. developers(:jamis).projects << ac
  53406. assert developers(:jamis, :reload).projects.include?(ac)
  53407. project = developers(:jamis).projects.detect { |p| p == ac }
  53408. assert_equal 1, project.access_level.to_i
  53409. end
  53410. def test_adding_uses_explicit_values_on_join_table
  53411. ac = projects(:action_controller)
  53412. assert !developers(:jamis).projects.include?(ac)
  53413. developers(:jamis).projects.push_with_attributes(ac, :access_level => 3)
  53414. assert developers(:jamis, :reload).projects.include?(ac)
  53415. project = developers(:jamis).projects.detect { |p| p == ac }
  53416. assert_equal 3, project.access_level.to_i
  53417. end
  53418. def test_hatbm_attribute_access_and_respond_to
  53419. project = developers(:jamis).projects[0]
  53420. assert project.has_attribute?("name")
  53421. assert project.has_attribute?("joined_on")
  53422. assert project.has_attribute?("access_level")
  53423. assert project.respond_to?("name")
  53424. assert project.respond_to?("name=")
  53425. assert project.respond_to?("name?")
  53426. assert project.respond_to?("joined_on")
  53427. assert project.respond_to?("joined_on=")
  53428. assert project.respond_to?("joined_on?")
  53429. assert project.respond_to?("access_level")
  53430. assert project.respond_to?("access_level=")
  53431. assert project.respond_to?("access_level?")
  53432. end
  53433. def test_habtm_adding_before_save
  53434. no_of_devels = Developer.count
  53435. no_of_projects = Project.count
  53436. aredridel = Developer.new("name" => "Aredridel")
  53437. aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")])
  53438. assert aredridel.new_record?
  53439. assert p.new_record?
  53440. assert aredridel.save
  53441. assert !aredridel.new_record?
  53442. assert_equal no_of_devels+1, Developer.count
  53443. assert_equal no_of_projects+1, Project.count
  53444. assert_equal 2, aredridel.projects.size
  53445. assert_equal 2, aredridel.projects(true).size
  53446. end
  53447. def test_habtm_adding_before_save_with_join_attributes
  53448. no_of_devels = Developer.count
  53449. no_of_projects = Project.count
  53450. now = Date.today
  53451. ken = Developer.new("name" => "Ken")
  53452. ken.projects.push_with_attributes( Project.find(1), :joined_on => now )
  53453. p = Project.new("name" => "Foomatic")
  53454. ken.projects.push_with_attributes( p, :joined_on => now )
  53455. assert ken.new_record?
  53456. assert p.new_record?
  53457. assert ken.save
  53458. assert !ken.new_record?
  53459. assert_equal no_of_devels+1, Developer.count
  53460. assert_equal no_of_projects+1, Project.count
  53461. assert_equal 2, ken.projects.size
  53462. assert_equal 2, ken.projects(true).size
  53463. kenReloaded = Developer.find_by_name 'Ken'
  53464. kenReloaded.projects.each {|prj| assert_date_from_db(now, prj.joined_on)}
  53465. end
  53466. def test_habtm_saving_multiple_relationships
  53467. new_project = Project.new("name" => "Grimetime")
  53468. amount_of_developers = 4
  53469. developers = (0..amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }
  53470. new_project.developer_ids = [developers[0].id, developers[1].id]
  53471. new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
  53472. assert new_project.save
  53473. new_project.reload
  53474. assert_equal amount_of_developers, new_project.developers.size
  53475. amount_of_developers.times do |i|
  53476. assert_equal developers[i].name, new_project.developers[i].name
  53477. end
  53478. end
  53479. def test_build
  53480. devel = Developer.find(1)
  53481. proj = devel.projects.build("name" => "Projekt")
  53482. assert_equal devel.projects.last, proj
  53483. assert proj.new_record?
  53484. devel.save
  53485. assert !proj.new_record?
  53486. assert_equal devel.projects.last, proj
  53487. end
  53488. def test_create
  53489. devel = Developer.find(1)
  53490. proj = devel.projects.create("name" => "Projekt")
  53491. assert_equal devel.projects.last, proj
  53492. assert !proj.new_record?
  53493. end
  53494. def test_uniq_after_the_fact
  53495. developers(:jamis).projects << projects(:active_record)
  53496. developers(:jamis).projects << projects(:active_record)
  53497. assert_equal 3, developers(:jamis).projects.size
  53498. assert_equal 1, developers(:jamis).projects.uniq.size
  53499. end
  53500. def test_uniq_before_the_fact
  53501. projects(:active_record).developers << developers(:jamis)
  53502. projects(:active_record).developers << developers(:david)
  53503. assert_equal 3, projects(:active_record, :reload).developers.size
  53504. end
  53505. def test_deleting
  53506. david = Developer.find(1)
  53507. active_record = Project.find(1)
  53508. david.projects.reload
  53509. assert_equal 2, david.projects.size
  53510. assert_equal 3, active_record.developers.size
  53511. david.projects.delete(active_record)
  53512. assert_equal 1, david.projects.size
  53513. assert_equal 1, david.projects(true).size
  53514. assert_equal 2, active_record.developers(true).size
  53515. end
  53516. def test_deleting_array
  53517. david = Developer.find(1)
  53518. david.projects.reload
  53519. david.projects.delete(Project.find(:all))
  53520. assert_equal 0, david.projects.size
  53521. assert_equal 0, david.projects(true).size
  53522. end
  53523. def test_deleting_with_sql
  53524. david = Developer.find(1)
  53525. active_record = Project.find(1)
  53526. active_record.developers.reload
  53527. assert_equal 3, active_record.developers_by_sql.size
  53528. active_record.developers_by_sql.delete(david)
  53529. assert_equal 2, active_record.developers_by_sql(true).size
  53530. end
  53531. def test_deleting_array_with_sql
  53532. active_record = Project.find(1)
  53533. active_record.developers.reload
  53534. assert_equal 3, active_record.developers_by_sql.size
  53535. active_record.developers_by_sql.delete(Developer.find(:all))
  53536. assert_equal 0, active_record.developers_by_sql(true).size
  53537. end
  53538. def test_deleting_all
  53539. david = Developer.find(1)
  53540. david.projects.reload
  53541. david.projects.clear
  53542. assert_equal 0, david.projects.size
  53543. assert_equal 0, david.projects(true).size
  53544. end
  53545. def test_removing_associations_on_destroy
  53546. david = DeveloperWithBeforeDestroyRaise.find(1)
  53547. assert !david.projects.empty?
  53548. assert_nothing_raised { david.destroy }
  53549. assert david.projects.empty?
  53550. assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
  53551. end
  53552. def test_additional_columns_from_join_table
  53553. assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on
  53554. end
  53555. def test_destroy_all
  53556. david = Developer.find(1)
  53557. david.projects.reload
  53558. assert !david.projects.empty?
  53559. david.projects.destroy_all
  53560. assert david.projects.empty?
  53561. assert david.projects(true).empty?
  53562. end
  53563. def test_rich_association
  53564. jamis = developers(:jamis)
  53565. jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today)
  53566. assert_date_from_db Date.today, jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on
  53567. assert_date_from_db Date.today, developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on
  53568. end
  53569. def test_associations_with_conditions
  53570. assert_equal 3, projects(:active_record).developers.size
  53571. assert_equal 1, projects(:active_record).developers_named_david.size
  53572. assert_equal developers(:david), projects(:active_record).developers_named_david.find(developers(:david).id)
  53573. assert_equal developers(:david), projects(:active_record).salaried_developers.find(developers(:david).id)
  53574. projects(:active_record).developers_named_david.clear
  53575. assert_equal 2, projects(:active_record, :reload).developers.size
  53576. end
  53577. def test_find_in_association
  53578. # Using sql
  53579. assert_equal developers(:david), projects(:active_record).developers.find(developers(:david).id), "SQL find"
  53580. # Using ruby
  53581. active_record = projects(:active_record)
  53582. active_record.developers.reload
  53583. assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find"
  53584. end
  53585. def test_find_in_association_with_custom_finder_sql
  53586. assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find"
  53587. active_record = projects(:active_record)
  53588. active_record.developers_with_finder_sql.reload
  53589. assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
  53590. end
  53591. def test_find_in_association_with_custom_finder_sql_and_string_id
  53592. assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
  53593. end
  53594. def test_find_with_merged_options
  53595. assert_equal 1, projects(:active_record).limited_developers.size
  53596. assert_equal 1, projects(:active_record).limited_developers.find(:all).size
  53597. assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size
  53598. end
  53599. def test_new_with_values_in_collection
  53600. jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis')
  53601. david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
  53602. project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie")
  53603. project.developers << jamis
  53604. project.save!
  53605. project.reload
  53606. assert project.developers.include?(jamis)
  53607. assert project.developers.include?(david)
  53608. end
  53609. def test_find_in_association_with_options
  53610. developers = projects(:active_record).developers.find(:all)
  53611. assert_equal 3, developers.size
  53612. assert_equal developers(:poor_jamis), projects(:active_record).developers.find(:first, :conditions => "salary < 10000")
  53613. assert_equal developers(:jamis), projects(:active_record).developers.find(:first, :order => "salary DESC")
  53614. end
  53615. def test_replace_with_less
  53616. david = developers(:david)
  53617. david.projects = [projects(:action_controller)]
  53618. assert david.save
  53619. assert_equal 1, david.projects.length
  53620. end
  53621. def test_replace_with_new
  53622. david = developers(:david)
  53623. david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
  53624. david.save
  53625. assert_equal 2, david.projects.length
  53626. assert !david.projects.include?(projects(:active_record))
  53627. end
  53628. def test_replace_on_new_object
  53629. new_developer = Developer.new("name" => "Matz")
  53630. new_developer.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
  53631. new_developer.save
  53632. assert_equal 2, new_developer.projects.length
  53633. end
  53634. def test_consider_type
  53635. developer = Developer.find(:first)
  53636. special_project = SpecialProject.create("name" => "Special Project")
  53637. other_project = developer.projects.first
  53638. developer.special_projects << special_project
  53639. developer.reload
  53640. assert developer.projects.include?(special_project)
  53641. assert developer.special_projects.include?(special_project)
  53642. assert !developer.special_projects.include?(other_project)
  53643. end
  53644. def test_update_attributes_after_push_without_duplicate_join_table_rows
  53645. developer = Developer.new("name" => "Kano")
  53646. project = SpecialProject.create("name" => "Special Project")
  53647. assert developer.save
  53648. developer.projects << project
  53649. developer.update_attribute("name", "Bruza")
  53650. assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
  53651. SELECT count(*) FROM developers_projects
  53652. WHERE project_id = #{project.id}
  53653. AND developer_id = #{developer.id}
  53654. end_sql
  53655. end
  53656. def test_updating_attributes_on_non_rich_associations
  53657. welcome = categories(:technology).posts.first
  53658. welcome.title = "Something else"
  53659. assert welcome.save!
  53660. end
  53661. def test_updating_attributes_on_rich_associations
  53662. david = projects(:action_controller).developers.first
  53663. david.name = "DHH"
  53664. assert_raises(ActiveRecord::ReadOnlyRecord) { david.save! }
  53665. end
  53666. def test_updating_attributes_on_rich_associations_with_limited_find
  53667. david = projects(:action_controller).developers.find(:all, :select => "developers.*").first
  53668. david.name = "DHH"
  53669. assert david.save!
  53670. end
  53671. def test_join_table_alias
  53672. assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size
  53673. end
  53674. end
  53675. require 'abstract_unit'
  53676. require 'fixtures/topic'
  53677. require 'fixtures/reply'
  53678. require 'fixtures/company'
  53679. require 'fixtures/customer'
  53680. require 'fixtures/developer'
  53681. require 'fixtures/project'
  53682. require 'fixtures/default'
  53683. require 'fixtures/auto_id'
  53684. require 'fixtures/column_name'
  53685. require 'fixtures/subscriber'
  53686. require 'fixtures/keyboard'
  53687. class Category < ActiveRecord::Base; end
  53688. class Smarts < ActiveRecord::Base; end
  53689. class CreditCard < ActiveRecord::Base; end
  53690. class MasterCreditCard < ActiveRecord::Base; end
  53691. class Post < ActiveRecord::Base; end
  53692. class Computer < ActiveRecord::Base; end
  53693. class NonExistentTable < ActiveRecord::Base; end
  53694. class TestOracleDefault < ActiveRecord::Base; end
  53695. class LoosePerson < ActiveRecord::Base
  53696. attr_protected :credit_rating, :administrator
  53697. self.abstract_class = true
  53698. end
  53699. class LooseDescendant < LoosePerson
  53700. attr_protected :phone_number
  53701. end
  53702. class TightPerson < ActiveRecord::Base
  53703. attr_accessible :name, :address
  53704. end
  53705. class TightDescendant < TightPerson
  53706. attr_accessible :phone_number
  53707. end
  53708. class Booleantest < ActiveRecord::Base; end
  53709. class Task < ActiveRecord::Base
  53710. attr_protected :starting
  53711. end
  53712. class BasicsTest < Test::Unit::TestCase
  53713. fixtures :topics, :companies, :developers, :projects, :computers
  53714. def test_table_exists
  53715. assert !NonExistentTable.table_exists?
  53716. assert Topic.table_exists?
  53717. end
  53718. def test_set_attributes
  53719. topic = Topic.find(1)
  53720. topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
  53721. topic.save
  53722. assert_equal("Budget", topic.title)
  53723. assert_equal("Jason", topic.author_name)
  53724. assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
  53725. end
  53726. def test_integers_as_nil
  53727. test = AutoId.create('value' => '')
  53728. assert_nil AutoId.find(test.id).value
  53729. end
  53730. def test_set_attributes_with_block
  53731. topic = Topic.new do |t|
  53732. t.title = "Budget"
  53733. t.author_name = "Jason"
  53734. end
  53735. assert_equal("Budget", topic.title)
  53736. assert_equal("Jason", topic.author_name)
  53737. end
  53738. def test_respond_to?
  53739. topic = Topic.find(1)
  53740. assert topic.respond_to?("title")
  53741. assert topic.respond_to?("title?")
  53742. assert topic.respond_to?("title=")
  53743. assert topic.respond_to?(:title)
  53744. assert topic.respond_to?(:title?)
  53745. assert topic.respond_to?(:title=)
  53746. assert topic.respond_to?("author_name")
  53747. assert topic.respond_to?("attribute_names")
  53748. assert !topic.respond_to?("nothingness")
  53749. assert !topic.respond_to?(:nothingness)
  53750. end
  53751. def test_array_content
  53752. topic = Topic.new
  53753. topic.content = %w( one two three )
  53754. topic.save
  53755. assert_equal(%w( one two three ), Topic.find(topic.id).content)
  53756. end
  53757. def test_hash_content
  53758. topic = Topic.new
  53759. topic.content = { "one" => 1, "two" => 2 }
  53760. topic.save
  53761. assert_equal 2, Topic.find(topic.id).content["two"]
  53762. topic.content["three"] = 3
  53763. topic.save
  53764. assert_equal 3, Topic.find(topic.id).content["three"]
  53765. end
  53766. def test_update_array_content
  53767. topic = Topic.new
  53768. topic.content = %w( one two three )
  53769. topic.content.push "four"
  53770. assert_equal(%w( one two three four ), topic.content)
  53771. topic.save
  53772. topic = Topic.find(topic.id)
  53773. topic.content << "five"
  53774. assert_equal(%w( one two three four five ), topic.content)
  53775. end
  53776. def test_case_sensitive_attributes_hash
  53777. # DB2 is not case-sensitive
  53778. return true if current_adapter?(:DB2Adapter)
  53779. assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
  53780. end
  53781. def test_create
  53782. topic = Topic.new
  53783. topic.title = "New Topic"
  53784. topic.save
  53785. topic_reloaded = Topic.find(topic.id)
  53786. assert_equal("New Topic", topic_reloaded.title)
  53787. end
  53788. def test_save!
  53789. topic = Topic.new(:title => "New Topic")
  53790. assert topic.save!
  53791. end
  53792. def test_hashes_not_mangled
  53793. new_topic = { :title => "New Topic" }
  53794. new_topic_values = { :title => "AnotherTopic" }
  53795. topic = Topic.new(new_topic)
  53796. assert_equal new_topic[:title], topic.title
  53797. topic.attributes= new_topic_values
  53798. assert_equal new_topic_values[:title], topic.title
  53799. end
  53800. def test_create_many
  53801. topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
  53802. assert_equal 2, topics.size
  53803. assert_equal "first", topics.first.title
  53804. end
  53805. def test_create_columns_not_equal_attributes
  53806. topic = Topic.new
  53807. topic.title = 'Another New Topic'
  53808. topic.send :write_attribute, 'does_not_exist', 'test'
  53809. assert_nothing_raised { topic.save }
  53810. end
  53811. def test_create_through_factory
  53812. topic = Topic.create("title" => "New Topic")
  53813. topicReloaded = Topic.find(topic.id)
  53814. assert_equal(topic, topicReloaded)
  53815. end
  53816. def test_update
  53817. topic = Topic.new
  53818. topic.title = "Another New Topic"
  53819. topic.written_on = "2003-12-12 23:23:00"
  53820. topic.save
  53821. topicReloaded = Topic.find(topic.id)
  53822. assert_equal("Another New Topic", topicReloaded.title)
  53823. topicReloaded.title = "Updated topic"
  53824. topicReloaded.save
  53825. topicReloadedAgain = Topic.find(topic.id)
  53826. assert_equal("Updated topic", topicReloadedAgain.title)
  53827. end
  53828. def test_update_columns_not_equal_attributes
  53829. topic = Topic.new
  53830. topic.title = "Still another topic"
  53831. topic.save
  53832. topicReloaded = Topic.find(topic.id)
  53833. topicReloaded.title = "A New Topic"
  53834. topicReloaded.send :write_attribute, 'does_not_exist', 'test'
  53835. assert_nothing_raised { topicReloaded.save }
  53836. end
  53837. def test_write_attribute
  53838. topic = Topic.new
  53839. topic.send(:write_attribute, :title, "Still another topic")
  53840. assert_equal "Still another topic", topic.title
  53841. topic.send(:write_attribute, "title", "Still another topic: part 2")
  53842. assert_equal "Still another topic: part 2", topic.title
  53843. end
  53844. def test_read_attribute
  53845. topic = Topic.new
  53846. topic.title = "Don't change the topic"
  53847. assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
  53848. assert_equal "Don't change the topic", topic["title"]
  53849. assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
  53850. assert_equal "Don't change the topic", topic[:title]
  53851. end
  53852. def test_read_attribute_when_false
  53853. topic = topics(:first)
  53854. topic.approved = false
  53855. assert !topic.approved?, "approved should be false"
  53856. topic.approved = "false"
  53857. assert !topic.approved?, "approved should be false"
  53858. end
  53859. def test_read_attribute_when_true
  53860. topic = topics(:first)
  53861. topic.approved = true
  53862. assert topic.approved?, "approved should be true"
  53863. topic.approved = "true"
  53864. assert topic.approved?, "approved should be true"
  53865. end
  53866. def test_read_write_boolean_attribute
  53867. topic = Topic.new
  53868. # puts ""
  53869. # puts "New Topic"
  53870. # puts topic.inspect
  53871. topic.approved = "false"
  53872. # puts "Expecting false"
  53873. # puts topic.inspect
  53874. assert !topic.approved?, "approved should be false"
  53875. topic.approved = "false"
  53876. # puts "Expecting false"
  53877. # puts topic.inspect
  53878. assert !topic.approved?, "approved should be false"
  53879. topic.approved = "true"
  53880. # puts "Expecting true"
  53881. # puts topic.inspect
  53882. assert topic.approved?, "approved should be true"
  53883. topic.approved = "true"
  53884. # puts "Expecting true"
  53885. # puts topic.inspect
  53886. assert topic.approved?, "approved should be true"
  53887. # puts ""
  53888. end
  53889. def test_reader_generation
  53890. Topic.find(:first).title
  53891. Firm.find(:first).name
  53892. Client.find(:first).name
  53893. if ActiveRecord::Base.generate_read_methods
  53894. assert_readers(Topic, %w(type replies_count))
  53895. assert_readers(Firm, %w(type))
  53896. assert_readers(Client, %w(type ruby_type rating?))
  53897. else
  53898. [Topic, Firm, Client].each {|klass| assert_equal klass.read_methods, {}}
  53899. end
  53900. end
  53901. def test_reader_for_invalid_column_names
  53902. # column names which aren't legal ruby ids
  53903. topic = Topic.find(:first)
  53904. topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
  53905. assert !Topic.read_methods.include?("mumub-jumbo")
  53906. end
  53907. def test_non_attribute_access_and_assignment
  53908. topic = Topic.new
  53909. assert !topic.respond_to?("mumbo")
  53910. assert_raises(NoMethodError) { topic.mumbo }
  53911. assert_raises(NoMethodError) { topic.mumbo = 5 }
  53912. end
  53913. def test_preserving_date_objects
  53914. # SQL Server doesn't have a separate column type just for dates, so all are returned as time
  53915. return true if current_adapter?(:SQLServerAdapter)
  53916. if current_adapter?(:SybaseAdapter)
  53917. # Sybase ctlib does not (yet?) support the date type; use datetime instead.
  53918. assert_kind_of(
  53919. Time, Topic.find(1).last_read,
  53920. "The last_read attribute should be of the Time class"
  53921. )
  53922. else
  53923. assert_kind_of(
  53924. Date, Topic.find(1).last_read,
  53925. "The last_read attribute should be of the Date class"
  53926. )
  53927. end
  53928. end
  53929. def test_preserving_time_objects
  53930. assert_kind_of(
  53931. Time, Topic.find(1).bonus_time,
  53932. "The bonus_time attribute should be of the Time class"
  53933. )
  53934. assert_kind_of(
  53935. Time, Topic.find(1).written_on,
  53936. "The written_on attribute should be of the Time class"
  53937. )
  53938. end
  53939. def test_destroy
  53940. topic = Topic.new
  53941. topic.title = "Yet Another New Topic"
  53942. topic.written_on = "2003-12-12 23:23:00"
  53943. topic.save
  53944. topic.destroy
  53945. assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
  53946. end
  53947. def test_destroy_returns_self
  53948. topic = Topic.new("title" => "Yet Another Title")
  53949. assert topic.save
  53950. assert_equal topic, topic.destroy, "destroy did not return destroyed object"
  53951. end
  53952. def test_record_not_found_exception
  53953. assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
  53954. end
  53955. def test_initialize_with_attributes
  53956. topic = Topic.new({
  53957. "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
  53958. })
  53959. assert_equal("initialized from attributes", topic.title)
  53960. end
  53961. def test_initialize_with_invalid_attribute
  53962. begin
  53963. topic = Topic.new({ "title" => "test",
  53964. "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
  53965. rescue ActiveRecord::MultiparameterAssignmentErrors => ex
  53966. assert_equal(1, ex.errors.size)
  53967. assert_equal("last_read", ex.errors[0].attribute)
  53968. end
  53969. end
  53970. def test_load
  53971. topics = Topic.find(:all, :order => 'id')
  53972. assert_equal(2, topics.size)
  53973. assert_equal(topics(:first).title, topics.first.title)
  53974. end
  53975. def test_load_with_condition
  53976. topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
  53977. assert_equal(1, topics.size)
  53978. assert_equal(topics(:second).title, topics.first.title)
  53979. end
  53980. def test_table_name_guesses
  53981. assert_equal "topics", Topic.table_name
  53982. assert_equal "categories", Category.table_name
  53983. assert_equal "smarts", Smarts.table_name
  53984. assert_equal "credit_cards", CreditCard.table_name
  53985. assert_equal "master_credit_cards", MasterCreditCard.table_name
  53986. ActiveRecord::Base.pluralize_table_names = false
  53987. [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
  53988. assert_equal "category", Category.table_name
  53989. assert_equal "smarts", Smarts.table_name
  53990. assert_equal "credit_card", CreditCard.table_name
  53991. assert_equal "master_credit_card", MasterCreditCard.table_name
  53992. ActiveRecord::Base.pluralize_table_names = true
  53993. [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
  53994. ActiveRecord::Base.table_name_prefix = "test_"
  53995. Category.reset_table_name
  53996. assert_equal "test_categories", Category.table_name
  53997. ActiveRecord::Base.table_name_suffix = "_test"
  53998. Category.reset_table_name
  53999. assert_equal "test_categories_test", Category.table_name
  54000. ActiveRecord::Base.table_name_prefix = ""
  54001. Category.reset_table_name
  54002. assert_equal "categories_test", Category.table_name
  54003. ActiveRecord::Base.table_name_suffix = ""
  54004. Category.reset_table_name
  54005. assert_equal "categories", Category.table_name
  54006. ActiveRecord::Base.pluralize_table_names = false
  54007. ActiveRecord::Base.table_name_prefix = "test_"
  54008. Category.reset_table_name
  54009. assert_equal "test_category", Category.table_name
  54010. ActiveRecord::Base.table_name_suffix = "_test"
  54011. Category.reset_table_name
  54012. assert_equal "test_category_test", Category.table_name
  54013. ActiveRecord::Base.table_name_prefix = ""
  54014. Category.reset_table_name
  54015. assert_equal "category_test", Category.table_name
  54016. ActiveRecord::Base.table_name_suffix = ""
  54017. Category.reset_table_name
  54018. assert_equal "category", Category.table_name
  54019. ActiveRecord::Base.pluralize_table_names = true
  54020. [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
  54021. end
  54022. def test_destroy_all
  54023. assert_equal 2, Topic.count
  54024. Topic.destroy_all "author_name = 'Mary'"
  54025. assert_equal 1, Topic.count
  54026. end
  54027. def test_destroy_many
  54028. assert_equal 3, Client.count
  54029. Client.destroy([2, 3])
  54030. assert_equal 1, Client.count
  54031. end
  54032. def test_delete_many
  54033. Topic.delete([1, 2])
  54034. assert_equal 0, Topic.count
  54035. end
  54036. def test_boolean_attributes
  54037. assert ! Topic.find(1).approved?
  54038. assert Topic.find(2).approved?
  54039. end
  54040. def test_increment_counter
  54041. Topic.increment_counter("replies_count", 1)
  54042. assert_equal 1, Topic.find(1).replies_count
  54043. Topic.increment_counter("replies_count", 1)
  54044. assert_equal 2, Topic.find(1).replies_count
  54045. end
  54046. def test_decrement_counter
  54047. Topic.decrement_counter("replies_count", 2)
  54048. assert_equal 1, Topic.find(2).replies_count
  54049. Topic.decrement_counter("replies_count", 2)
  54050. assert_equal 0, Topic.find(1).replies_count
  54051. end
  54052. def test_update_all
  54053. # The ADO library doesn't support the number of affected rows
  54054. return true if current_adapter?(:SQLServerAdapter)
  54055. assert_equal 2, Topic.update_all("content = 'bulk updated!'")
  54056. assert_equal "bulk updated!", Topic.find(1).content
  54057. assert_equal "bulk updated!", Topic.find(2).content
  54058. assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!'])
  54059. assert_equal "bulk updated again!", Topic.find(1).content
  54060. assert_equal "bulk updated again!", Topic.find(2).content
  54061. end
  54062. def test_update_many
  54063. topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
  54064. updated = Topic.update(topic_data.keys, topic_data.values)
  54065. assert_equal 2, updated.size
  54066. assert_equal "1 updated", Topic.find(1).content
  54067. assert_equal "2 updated", Topic.find(2).content
  54068. end
  54069. def test_delete_all
  54070. # The ADO library doesn't support the number of affected rows
  54071. return true if current_adapter?(:SQLServerAdapter)
  54072. assert_equal 2, Topic.delete_all
  54073. end
  54074. def test_update_by_condition
  54075. Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
  54076. assert_equal "Have a nice day", Topic.find(1).content
  54077. assert_equal "bulk updated!", Topic.find(2).content
  54078. end
  54079. def test_attribute_present
  54080. t = Topic.new
  54081. t.title = "hello there!"
  54082. t.written_on = Time.now
  54083. assert t.attribute_present?("title")
  54084. assert t.attribute_present?("written_on")
  54085. assert !t.attribute_present?("content")
  54086. end
  54087. def test_attribute_keys_on_new_instance
  54088. t = Topic.new
  54089. assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
  54090. assert_raise(NoMethodError) { t.title2 }
  54091. end
  54092. def test_class_name
  54093. assert_equal "Firm", ActiveRecord::Base.class_name("firms")
  54094. assert_equal "Category", ActiveRecord::Base.class_name("categories")
  54095. assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
  54096. ActiveRecord::Base.pluralize_table_names = false
  54097. assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
  54098. ActiveRecord::Base.pluralize_table_names = true
  54099. ActiveRecord::Base.table_name_prefix = "test_"
  54100. assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
  54101. ActiveRecord::Base.table_name_suffix = "_tests"
  54102. assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
  54103. ActiveRecord::Base.table_name_prefix = ""
  54104. assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
  54105. ActiveRecord::Base.table_name_suffix = ""
  54106. assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
  54107. end
  54108. def test_null_fields
  54109. assert_nil Topic.find(1).parent_id
  54110. assert_nil Topic.create("title" => "Hey you").parent_id
  54111. end
  54112. def test_default_values
  54113. topic = Topic.new
  54114. assert topic.approved?
  54115. assert_nil topic.written_on
  54116. assert_nil topic.bonus_time
  54117. assert_nil topic.last_read
  54118. topic.save
  54119. topic = Topic.find(topic.id)
  54120. assert topic.approved?
  54121. assert_nil topic.last_read
  54122. # Oracle has some funky default handling, so it requires a bit of
  54123. # extra testing. See ticket #2788.
  54124. if current_adapter?(:OracleAdapter)
  54125. test = TestOracleDefault.new
  54126. assert_equal "X", test.test_char
  54127. assert_equal "hello", test.test_string
  54128. assert_equal 3, test.test_int
  54129. end
  54130. end
  54131. def test_utc_as_time_zone
  54132. # Oracle and SQLServer do not have a TIME datatype.
  54133. return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)
  54134. Topic.default_timezone = :utc
  54135. attributes = { "bonus_time" => "5:42:00AM" }
  54136. topic = Topic.find(1)
  54137. topic.attributes = attributes
  54138. assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
  54139. Topic.default_timezone = :local
  54140. end
  54141. def test_default_values_on_empty_strings
  54142. topic = Topic.new
  54143. topic.approved = nil
  54144. topic.last_read = nil
  54145. topic.save
  54146. topic = Topic.find(topic.id)
  54147. assert_nil topic.last_read
  54148. # Sybase adapter does not allow nulls in boolean columns
  54149. if current_adapter?(:SybaseAdapter)
  54150. assert topic.approved == false
  54151. else
  54152. assert_nil topic.approved
  54153. end
  54154. end
  54155. def test_equality
  54156. assert_equal Topic.find(1), Topic.find(2).topic
  54157. end
  54158. def test_equality_of_new_records
  54159. assert_not_equal Topic.new, Topic.new
  54160. end
  54161. def test_hashing
  54162. assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
  54163. end
  54164. def test_destroy_new_record
  54165. client = Client.new
  54166. client.destroy
  54167. assert client.frozen?
  54168. end
  54169. def test_destroy_record_with_associations
  54170. client = Client.find(3)
  54171. client.destroy
  54172. assert client.frozen?
  54173. assert_kind_of Firm, client.firm
  54174. assert_raises(TypeError) { client.name = "something else" }
  54175. end
  54176. def test_update_attribute
  54177. assert !Topic.find(1).approved?
  54178. Topic.find(1).update_attribute("approved", true)
  54179. assert Topic.find(1).approved?
  54180. Topic.find(1).update_attribute(:approved, false)
  54181. assert !Topic.find(1).approved?
  54182. end
  54183. def test_mass_assignment_protection
  54184. firm = Firm.new
  54185. firm.attributes = { "name" => "Next Angle", "rating" => 5 }
  54186. assert_equal 1, firm.rating
  54187. end
  54188. def test_customized_primary_key_remains_protected
  54189. subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
  54190. assert_nil subscriber.id
  54191. keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
  54192. assert_nil keyboard.id
  54193. end
  54194. def test_customized_primary_key_remains_protected_when_refered_to_as_id
  54195. subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
  54196. assert_nil subscriber.id
  54197. keyboard = Keyboard.new(:id => 9, :name => 'nice try')
  54198. assert_nil keyboard.id
  54199. end
  54200. def test_mass_assignment_protection_on_defaults
  54201. firm = Firm.new
  54202. firm.attributes = { "id" => 5, "type" => "Client" }
  54203. assert_nil firm.id
  54204. assert_equal "Firm", firm[:type]
  54205. end
  54206. def test_mass_assignment_accessible
  54207. reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
  54208. reply.save
  54209. assert reply.approved?
  54210. reply.approved = false
  54211. reply.save
  54212. assert !reply.approved?
  54213. end
  54214. def test_mass_assignment_protection_inheritance
  54215. assert_nil LoosePerson.accessible_attributes
  54216. assert_equal [ :credit_rating, :administrator ], LoosePerson.protected_attributes
  54217. assert_nil LooseDescendant.accessible_attributes
  54218. assert_equal [ :credit_rating, :administrator, :phone_number ], LooseDescendant.protected_attributes
  54219. assert_nil TightPerson.protected_attributes
  54220. assert_equal [ :name, :address ], TightPerson.accessible_attributes
  54221. assert_nil TightDescendant.protected_attributes
  54222. assert_equal [ :name, :address, :phone_number ], TightDescendant.accessible_attributes
  54223. end
  54224. def test_multiparameter_attributes_on_date
  54225. attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
  54226. topic = Topic.find(1)
  54227. topic.attributes = attributes
  54228. # note that extra #to_date call allows test to pass for Oracle, which
  54229. # treats dates/times the same
  54230. assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
  54231. end
  54232. def test_multiparameter_attributes_on_date_with_empty_date
  54233. attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
  54234. topic = Topic.find(1)
  54235. topic.attributes = attributes
  54236. # note that extra #to_date call allows test to pass for Oracle, which
  54237. # treats dates/times the same
  54238. assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
  54239. end
  54240. def test_multiparameter_attributes_on_date_with_all_empty
  54241. attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
  54242. topic = Topic.find(1)
  54243. topic.attributes = attributes
  54244. assert_nil topic.last_read
  54245. end
  54246. def test_multiparameter_attributes_on_time
  54247. attributes = {
  54248. "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
  54249. "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
  54250. }
  54251. topic = Topic.find(1)
  54252. topic.attributes = attributes
  54253. assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
  54254. end
  54255. def test_multiparameter_attributes_on_time_with_empty_seconds
  54256. attributes = {
  54257. "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
  54258. "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
  54259. }
  54260. topic = Topic.find(1)
  54261. topic.attributes = attributes
  54262. assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
  54263. end
  54264. def test_multiparameter_mass_assignment_protector
  54265. task = Task.new
  54266. time = Time.mktime(2000, 1, 1, 1)
  54267. task.starting = time
  54268. attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
  54269. task.attributes = attributes
  54270. assert_equal time, task.starting
  54271. end
  54272. def test_multiparameter_assignment_of_aggregation
  54273. customer = Customer.new
  54274. address = Address.new("The Street", "The City", "The Country")
  54275. attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
  54276. customer.attributes = attributes
  54277. assert_equal address, customer.address
  54278. end
  54279. def test_attributes_on_dummy_time
  54280. # Oracle and SQL Server do not have a TIME datatype.
  54281. return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)
  54282. attributes = {
  54283. "bonus_time" => "5:42:00AM"
  54284. }
  54285. topic = Topic.find(1)
  54286. topic.attributes = attributes
  54287. assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
  54288. end
  54289. def test_boolean
  54290. b_false = Booleantest.create({ "value" => false })
  54291. false_id = b_false.id
  54292. b_true = Booleantest.create({ "value" => true })
  54293. true_id = b_true.id
  54294. b_false = Booleantest.find(false_id)
  54295. assert !b_false.value?
  54296. b_true = Booleantest.find(true_id)
  54297. assert b_true.value?
  54298. end
  54299. def test_boolean_cast_from_string
  54300. b_false = Booleantest.create({ "value" => "0" })
  54301. false_id = b_false.id
  54302. b_true = Booleantest.create({ "value" => "1" })
  54303. true_id = b_true.id
  54304. b_false = Booleantest.find(false_id)
  54305. assert !b_false.value?
  54306. b_true = Booleantest.find(true_id)
  54307. assert b_true.value?
  54308. end
  54309. def test_clone
  54310. topic = Topic.find(1)
  54311. cloned_topic = nil
  54312. assert_nothing_raised { cloned_topic = topic.clone }
  54313. assert_equal topic.title, cloned_topic.title
  54314. assert cloned_topic.new_record?
  54315. # test if the attributes have been cloned
  54316. topic.title = "a"
  54317. cloned_topic.title = "b"
  54318. assert_equal "a", topic.title
  54319. assert_equal "b", cloned_topic.title
  54320. # test if the attribute values have been cloned
  54321. topic.title = {"a" => "b"}
  54322. cloned_topic = topic.clone
  54323. cloned_topic.title["a"] = "c"
  54324. assert_equal "b", topic.title["a"]
  54325. cloned_topic.save
  54326. assert !cloned_topic.new_record?
  54327. assert cloned_topic.id != topic.id
  54328. end
  54329. def test_clone_with_aggregate_of_same_name_as_attribute
  54330. dev = DeveloperWithAggregate.find(1)
  54331. assert_kind_of DeveloperSalary, dev.salary
  54332. clone = nil
  54333. assert_nothing_raised { clone = dev.clone }
  54334. assert_kind_of DeveloperSalary, clone.salary
  54335. assert_equal dev.salary.amount, clone.salary.amount
  54336. assert clone.new_record?
  54337. # test if the attributes have been cloned
  54338. original_amount = clone.salary.amount
  54339. dev.salary.amount = 1
  54340. assert_equal original_amount, clone.salary.amount
  54341. assert clone.save
  54342. assert !clone.new_record?
  54343. assert clone.id != dev.id
  54344. end
  54345. def test_clone_preserves_subtype
  54346. clone = nil
  54347. assert_nothing_raised { clone = Company.find(3).clone }
  54348. assert_kind_of Client, clone
  54349. end
  54350. def test_bignum
  54351. company = Company.find(1)
  54352. company.rating = 2147483647
  54353. company.save
  54354. company = Company.find(1)
  54355. assert_equal 2147483647, company.rating
  54356. end
  54357. # TODO: extend defaults tests to other databases!
  54358. if current_adapter?(:PostgreSQLAdapter)
  54359. def test_default
  54360. default = Default.new
  54361. # fixed dates / times
  54362. assert_equal Date.new(2004, 1, 1), default.fixed_date
  54363. assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
  54364. # char types
  54365. assert_equal 'Y', default.char1
  54366. assert_equal 'a varchar field', default.char2
  54367. assert_equal 'a text field', default.char3
  54368. end
  54369. class Geometric < ActiveRecord::Base; end
  54370. def test_geometric_content
  54371. # accepted format notes:
  54372. # ()'s aren't required
  54373. # values can be a mix of float or integer
  54374. g = Geometric.new(
  54375. :a_point => '(5.0, 6.1)',
  54376. #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
  54377. :a_line_segment => '(2.0, 3), (5.5, 7.0)',
  54378. :a_box => '2.0, 3, 5.5, 7.0',
  54379. :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
  54380. :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
  54381. :a_circle => '<(5.3, 10.4), 2>'
  54382. )
  54383. assert g.save
  54384. # Reload and check that we have all the geometric attributes.
  54385. h = Geometric.find(g.id)
  54386. assert_equal '(5,6.1)', h.a_point
  54387. assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
  54388. assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
  54389. assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
  54390. assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
  54391. assert_equal '<(5.3,10.4),2>', h.a_circle
  54392. # use a geometric function to test for an open path
  54393. objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
  54394. assert_equal objs[0].isopen, 't'
  54395. # test alternate formats when defining the geometric types
  54396. g = Geometric.new(
  54397. :a_point => '5.0, 6.1',
  54398. #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
  54399. :a_line_segment => '((2.0, 3), (5.5, 7.0))',
  54400. :a_box => '(2.0, 3), (5.5, 7.0)',
  54401. :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
  54402. :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
  54403. :a_circle => '((5.3, 10.4), 2)'
  54404. )
  54405. assert g.save
  54406. # Reload and check that we have all the geometric attributes.
  54407. h = Geometric.find(g.id)
  54408. assert_equal '(5,6.1)', h.a_point
  54409. assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
  54410. assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
  54411. assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
  54412. assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
  54413. assert_equal '<(5.3,10.4),2>', h.a_circle
  54414. # use a geometric function to test for an closed path
  54415. objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
  54416. assert_equal objs[0].isclosed, 't'
  54417. end
  54418. end
  54419. def test_auto_id
  54420. auto = AutoId.new
  54421. auto.save
  54422. assert (auto.id > 0)
  54423. end
  54424. def quote_column_name(name)
  54425. "<#{name}>"
  54426. end
  54427. def test_quote_keys
  54428. ar = AutoId.new
  54429. source = {"foo" => "bar", "baz" => "quux"}
  54430. actual = ar.send(:quote_columns, self, source)
  54431. inverted = actual.invert
  54432. assert_equal("<foo>", inverted["bar"])
  54433. assert_equal("<baz>", inverted["quux"])
  54434. end
  54435. def test_sql_injection_via_find
  54436. assert_raises(ActiveRecord::RecordNotFound) do
  54437. Topic.find("123456 OR id > 0")
  54438. end
  54439. assert_raises(ActiveRecord::RecordNotFound) do
  54440. Topic.find(";;; this should raise an RecordNotFound error")
  54441. end
  54442. end
  54443. def test_column_name_properly_quoted
  54444. col_record = ColumnName.new
  54445. col_record.references = 40
  54446. assert col_record.save
  54447. col_record.references = 41
  54448. assert col_record.save
  54449. assert_not_nil c2 = ColumnName.find(col_record.id)
  54450. assert_equal(41, c2.references)
  54451. end
  54452. MyObject = Struct.new :attribute1, :attribute2
  54453. def test_serialized_attribute
  54454. myobj = MyObject.new('value1', 'value2')
  54455. topic = Topic.create("content" => myobj)
  54456. Topic.serialize("content", MyObject)
  54457. assert_equal(myobj, topic.content)
  54458. end
  54459. def test_serialized_attribute_with_class_constraint
  54460. myobj = MyObject.new('value1', 'value2')
  54461. topic = Topic.create("content" => myobj)
  54462. Topic.serialize(:content, Hash)
  54463. assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
  54464. settings = { "color" => "blue" }
  54465. Topic.find(topic.id).update_attribute("content", settings)
  54466. assert_equal(settings, Topic.find(topic.id).content)
  54467. Topic.serialize(:content)
  54468. end
  54469. def test_quote
  54470. author_name = "\\ \001 ' \n \\n \""
  54471. topic = Topic.create('author_name' => author_name)
  54472. assert_equal author_name, Topic.find(topic.id).author_name
  54473. end
  54474. def test_class_level_destroy
  54475. should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
  54476. Topic.find(1).replies << should_be_destroyed_reply
  54477. Topic.destroy(1)
  54478. assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
  54479. assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
  54480. end
  54481. def test_class_level_delete
  54482. should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
  54483. Topic.find(1).replies << should_be_destroyed_reply
  54484. Topic.delete(1)
  54485. assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
  54486. assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
  54487. end
  54488. def test_increment_attribute
  54489. assert_equal 0, topics(:first).replies_count
  54490. topics(:first).increment! :replies_count
  54491. assert_equal 1, topics(:first, :reload).replies_count
  54492. topics(:first).increment(:replies_count).increment!(:replies_count)
  54493. assert_equal 3, topics(:first, :reload).replies_count
  54494. end
  54495. def test_increment_nil_attribute
  54496. assert_nil topics(:first).parent_id
  54497. topics(:first).increment! :parent_id
  54498. assert_equal 1, topics(:first).parent_id
  54499. end
  54500. def test_decrement_attribute
  54501. topics(:first).increment(:replies_count).increment!(:replies_count)
  54502. assert_equal 2, topics(:first).replies_count
  54503. topics(:first).decrement!(:replies_count)
  54504. assert_equal 1, topics(:first, :reload).replies_count
  54505. topics(:first).decrement(:replies_count).decrement!(:replies_count)
  54506. assert_equal -1, topics(:first, :reload).replies_count
  54507. end
  54508. def test_toggle_attribute
  54509. assert !topics(:first).approved?
  54510. topics(:first).toggle!(:approved)
  54511. assert topics(:first).approved?
  54512. topic = topics(:first)
  54513. topic.toggle(:approved)
  54514. assert !topic.approved?
  54515. topic.reload
  54516. assert topic.approved?
  54517. end
  54518. def test_reload
  54519. t1 = Topic.find(1)
  54520. t2 = Topic.find(1)
  54521. t1.title = "something else"
  54522. t1.save
  54523. t2.reload
  54524. assert_equal t1.title, t2.title
  54525. end
  54526. def test_define_attr_method_with_value
  54527. k = Class.new( ActiveRecord::Base )
  54528. k.send(:define_attr_method, :table_name, "foo")
  54529. assert_equal "foo", k.table_name
  54530. end
  54531. def test_define_attr_method_with_block
  54532. k = Class.new( ActiveRecord::Base )
  54533. k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
  54534. assert_equal "sys_id", k.primary_key
  54535. end
  54536. def test_set_table_name_with_value
  54537. k = Class.new( ActiveRecord::Base )
  54538. k.table_name = "foo"
  54539. assert_equal "foo", k.table_name
  54540. k.set_table_name "bar"
  54541. assert_equal "bar", k.table_name
  54542. end
  54543. def test_set_table_name_with_block
  54544. k = Class.new( ActiveRecord::Base )
  54545. k.set_table_name { "ks" }
  54546. assert_equal "ks", k.table_name
  54547. end
  54548. def test_set_primary_key_with_value
  54549. k = Class.new( ActiveRecord::Base )
  54550. k.primary_key = "foo"
  54551. assert_equal "foo", k.primary_key
  54552. k.set_primary_key "bar"
  54553. assert_equal "bar", k.primary_key
  54554. end
  54555. def test_set_primary_key_with_block
  54556. k = Class.new( ActiveRecord::Base )
  54557. k.set_primary_key { "sys_" + original_primary_key }
  54558. assert_equal "sys_id", k.primary_key
  54559. end
  54560. def test_set_inheritance_column_with_value
  54561. k = Class.new( ActiveRecord::Base )
  54562. k.inheritance_column = "foo"
  54563. assert_equal "foo", k.inheritance_column
  54564. k.set_inheritance_column "bar"
  54565. assert_equal "bar", k.inheritance_column
  54566. end
  54567. def test_set_inheritance_column_with_block
  54568. k = Class.new( ActiveRecord::Base )
  54569. k.set_inheritance_column { original_inheritance_column + "_id" }
  54570. assert_equal "type_id", k.inheritance_column
  54571. end
  54572. def test_count_with_join
  54573. res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
  54574. res2 = nil
  54575. assert_nothing_raised do
  54576. res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'",
  54577. "LEFT JOIN comments ON posts.id=comments.post_id")
  54578. end
  54579. assert_equal res, res2
  54580. res3 = nil
  54581. assert_nothing_raised do
  54582. res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
  54583. :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
  54584. end
  54585. assert_equal res, res3
  54586. res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
  54587. res5 = nil
  54588. assert_nothing_raised do
  54589. res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
  54590. :joins => "p, comments c",
  54591. :select => "p.id")
  54592. end
  54593. assert_equal res4, res5
  54594. res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
  54595. res7 = nil
  54596. assert_nothing_raised do
  54597. res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
  54598. :joins => "p, comments c",
  54599. :select => "p.id",
  54600. :distinct => true)
  54601. end
  54602. assert_equal res6, res7
  54603. end
  54604. def test_clear_association_cache_stored
  54605. firm = Firm.find(1)
  54606. assert_kind_of Firm, firm
  54607. firm.clear_association_cache
  54608. assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
  54609. end
  54610. def test_clear_association_cache_new_record
  54611. firm = Firm.new
  54612. client_stored = Client.find(3)
  54613. client_new = Client.new
  54614. client_new.name = "The Joneses"
  54615. clients = [ client_stored, client_new ]
  54616. firm.clients << clients
  54617. firm.clear_association_cache
  54618. assert_equal firm.clients.collect{ |x| x.name }.sort, clients.collect{ |x| x.name }.sort
  54619. end
  54620. def test_interpolate_sql
  54621. assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
  54622. assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
  54623. assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
  54624. end
  54625. def test_scoped_find_conditions
  54626. scoped_developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
  54627. Developer.find(:all, :conditions => 'id < 5')
  54628. end
  54629. assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
  54630. assert_equal 3, scoped_developers.size
  54631. end
  54632. def test_scoped_find_limit_offset
  54633. scoped_developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
  54634. Developer.find(:all, :order => 'id')
  54635. end
  54636. assert !scoped_developers.include?(developers(:david))
  54637. assert !scoped_developers.include?(developers(:jamis))
  54638. assert_equal 3, scoped_developers.size
  54639. # Test without scoped find conditions to ensure we get the whole thing
  54640. developers = Developer.find(:all, :order => 'id')
  54641. assert_equal Developer.count, developers.size
  54642. end
  54643. def test_base_class
  54644. assert LoosePerson.abstract_class?
  54645. assert !LooseDescendant.abstract_class?
  54646. assert_equal LoosePerson, LoosePerson.base_class
  54647. assert_equal LooseDescendant, LooseDescendant.base_class
  54648. assert_equal TightPerson, TightPerson.base_class
  54649. assert_equal TightPerson, TightDescendant.base_class
  54650. end
  54651. def test_assert_queries
  54652. query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
  54653. assert_queries(2) { 2.times { query.call } }
  54654. assert_queries 1, &query
  54655. assert_no_queries { assert true }
  54656. end
  54657. def test_to_xml
  54658. xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true)
  54659. bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
  54660. written_on_in_current_timezone = topics(:first).written_on.xmlschema
  54661. last_read_in_current_timezone = topics(:first).last_read.xmlschema
  54662. assert_equal "<topic>", xml.first(7)
  54663. assert xml.include?(%(<title>The First Topic</title>))
  54664. assert xml.include?(%(<author-name>David</author-name>))
  54665. assert xml.include?(%(<id type="integer">1</id>))
  54666. assert xml.include?(%(<replies-count type="integer">0</replies-count>))
  54667. assert xml.include?(%(<written-on type="datetime">#{written_on_in_current_timezone}</written-on>))
  54668. assert xml.include?(%(<content>Have a nice day</content>))
  54669. assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>))
  54670. assert xml.include?(%(<parent-id></parent-id>))
  54671. if current_adapter?(:SybaseAdapter) or current_adapter?(:SQLServerAdapter)
  54672. assert xml.include?(%(<last-read type="datetime">#{last_read_in_current_timezone}</last-read>))
  54673. else
  54674. assert xml.include?(%(<last-read type="date">2004-04-15</last-read>))
  54675. end
  54676. # Oracle and DB2 don't have true boolean or time-only fields
  54677. unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
  54678. assert xml.include?(%(<approved type="boolean">false</approved>)), "Approved should be a boolean"
  54679. assert xml.include?(%(<bonus-time type="datetime">#{bonus_time_in_current_timezone}</bonus-time>))
  54680. end
  54681. end
  54682. def test_to_xml_skipping_attributes
  54683. xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => :title)
  54684. assert_equal "<topic>", xml.first(7)
  54685. assert !xml.include?(%(<title>The First Topic</title>))
  54686. assert xml.include?(%(<author-name>David</author-name>))
  54687. xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [ :title, :author_name ])
  54688. assert !xml.include?(%(<title>The First Topic</title>))
  54689. assert !xml.include?(%(<author-name>David</author-name>))
  54690. end
  54691. def test_to_xml_including_has_many_association
  54692. xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
  54693. assert_equal "<topic>", xml.first(7)
  54694. assert xml.include?(%(<replies><reply>))
  54695. assert xml.include?(%(<title>The Second Topic's of the day</title>))
  54696. end
  54697. def test_to_xml_including_belongs_to_association
  54698. xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
  54699. assert !xml.include?("<firm>")
  54700. xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
  54701. assert xml.include?("<firm>")
  54702. end
  54703. def test_to_xml_including_multiple_associations
  54704. xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
  54705. assert_equal "<firm>", xml.first(6)
  54706. assert xml.include?(%(<account>))
  54707. assert xml.include?(%(<clients><client>))
  54708. end
  54709. def test_to_xml_including_multiple_associations_with_options
  54710. xml = companies(:first_firm).to_xml(
  54711. :indent => 0, :skip_instruct => true,
  54712. :include => { :clients => { :only => :name } }
  54713. )
  54714. assert_equal "<firm>", xml.first(6)
  54715. assert xml.include?(%(<client><name>Summit</name></client>))
  54716. assert xml.include?(%(<clients><client>))
  54717. end
  54718. def test_except_attributes
  54719. assert_equal(
  54720. %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read),
  54721. topics(:first).attributes(:except => :title).keys
  54722. )
  54723. assert_equal(
  54724. %w( replies_count bonus_time written_on content author_email_address parent_id last_read),
  54725. topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys
  54726. )
  54727. end
  54728. def test_include_attributes
  54729. assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys)
  54730. assert_equal(%w( title author_name type id approved ), topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys)
  54731. end
  54732. def test_type_name_with_module_should_handle_beginning
  54733. assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
  54734. assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
  54735. end
  54736. # FIXME: this test ought to run, but it needs to run sandboxed so that it
  54737. # doesn't b0rk the current test environment by undefing everything.
  54738. #
  54739. #def test_dev_mode_memory_leak
  54740. # counts = []
  54741. # 2.times do
  54742. # require_dependency 'fixtures/company'
  54743. # Firm.find(:first)
  54744. # Dependencies.clear
  54745. # ActiveRecord::Base.reset_subclasses
  54746. # Dependencies.remove_subclasses_for(ActiveRecord::Base)
  54747. #
  54748. # GC.start
  54749. #
  54750. # count = 0
  54751. # ObjectSpace.each_object(Proc) { count += 1 }
  54752. # counts << count
  54753. # end
  54754. # assert counts.last <= counts.first,
  54755. # "expected last count (#{counts.last}) to be <= first count (#{counts.first})"
  54756. #end
  54757. private
  54758. def assert_readers(model, exceptions)
  54759. expected_readers = Set.new(model.column_names - (model.serialized_attributes.keys + ['id']))
  54760. expected_readers += expected_readers.map { |col| "#{col}?" }
  54761. expected_readers -= exceptions
  54762. assert_equal expected_readers, model.read_methods
  54763. end
  54764. end
  54765. require 'abstract_unit'
  54766. require 'fixtures/binary'
  54767. class BinaryTest < Test::Unit::TestCase
  54768. BINARY_FIXTURE_PATH = File.dirname(__FILE__) + '/fixtures/flowers.jpg'
  54769. def setup
  54770. Binary.connection.execute 'DELETE FROM binaries'
  54771. @data = File.read(BINARY_FIXTURE_PATH).freeze
  54772. end
  54773. def test_truth
  54774. assert true
  54775. end
  54776. # Without using prepared statements, it makes no sense to test
  54777. # BLOB data with SQL Server, because the length of a statement is
  54778. # limited to 8KB.
  54779. #
  54780. # Without using prepared statements, it makes no sense to test
  54781. # BLOB data with DB2 or Firebird, because the length of a statement
  54782. # is limited to 32KB.
  54783. unless %w(SQLServer Sybase DB2 Oracle Firebird).include? ActiveRecord::Base.connection.adapter_name
  54784. def test_load_save
  54785. bin = Binary.new
  54786. bin.data = @data
  54787. assert @data == bin.data, 'Newly assigned data differs from original'
  54788. bin.save
  54789. assert @data == bin.data, 'Data differs from original after save'
  54790. db_bin = Binary.find(bin.id)
  54791. assert @data == db_bin.data, 'Reloaded data differs from original'
  54792. end
  54793. end
  54794. end
  54795. require 'abstract_unit'
  54796. require 'fixtures/company'
  54797. require 'fixtures/topic'
  54798. Company.has_many :accounts
  54799. class CalculationsTest < Test::Unit::TestCase
  54800. fixtures :companies, :accounts, :topics
  54801. def test_should_sum_field
  54802. assert_equal 265, Account.sum(:credit_limit)
  54803. end
  54804. def test_should_average_field
  54805. value = Account.average(:credit_limit)
  54806. assert_equal 53, value
  54807. assert_kind_of Float, value
  54808. end
  54809. def test_should_get_maximum_of_field
  54810. assert_equal 60, Account.maximum(:credit_limit)
  54811. end
  54812. def test_should_get_minimum_of_field
  54813. assert_equal 50, Account.minimum(:credit_limit)
  54814. end
  54815. def test_should_group_by_field
  54816. c = Account.sum(:credit_limit, :group => :firm_id)
  54817. [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
  54818. end
  54819. def test_should_group_by_summed_field
  54820. c = Account.sum(:credit_limit, :group => :firm_id)
  54821. assert_equal 50, c[1]
  54822. assert_equal 105, c[6]
  54823. assert_equal 60, c[2]
  54824. end
  54825. def test_should_order_by_grouped_field
  54826. c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
  54827. assert_equal [1, 2, 6], c.keys.compact
  54828. end
  54829. def test_should_order_by_calculation
  54830. c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
  54831. assert_equal [105, 60, 50, 50], c.keys.collect { |k| c[k] }
  54832. assert_equal [6, 2, 1], c.keys.compact
  54833. end
  54834. def test_should_limit_calculation
  54835. c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
  54836. :group => :firm_id, :order => "firm_id", :limit => 2)
  54837. assert_equal [1, 2], c.keys.compact
  54838. end
  54839. def test_should_limit_calculation_with_offset
  54840. c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
  54841. :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1)
  54842. assert_equal [2, 6], c.keys.compact
  54843. end
  54844. def test_should_group_by_summed_field_having_condition
  54845. c = Account.sum(:credit_limit, :group => :firm_id,
  54846. :having => 'sum(credit_limit) > 50')
  54847. assert_nil c[1]
  54848. assert_equal 105, c[6]
  54849. assert_equal 60, c[2]
  54850. end
  54851. def test_should_group_by_summed_association
  54852. c = Account.sum(:credit_limit, :group => :firm)
  54853. assert_equal 50, c[companies(:first_firm)]
  54854. assert_equal 105, c[companies(:rails_core)]
  54855. assert_equal 60, c[companies(:first_client)]
  54856. end
  54857. def test_should_sum_field_with_conditions
  54858. assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
  54859. end
  54860. def test_should_group_by_summed_field_with_conditions
  54861. c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
  54862. :group => :firm_id)
  54863. assert_nil c[1]
  54864. assert_equal 105, c[6]
  54865. assert_equal 60, c[2]
  54866. end
  54867. def test_should_group_by_summed_field_with_conditions_and_having
  54868. c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
  54869. :group => :firm_id,
  54870. :having => 'sum(credit_limit) > 60')
  54871. assert_nil c[1]
  54872. assert_equal 105, c[6]
  54873. assert_nil c[2]
  54874. end
  54875. def test_should_group_by_fields_with_table_alias
  54876. c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
  54877. assert_equal 50, c[1]
  54878. assert_equal 105, c[6]
  54879. assert_equal 60, c[2]
  54880. end
  54881. def test_should_calculate_with_invalid_field
  54882. assert_equal 5, Account.calculate(:count, '*')
  54883. assert_equal 5, Account.calculate(:count, :all)
  54884. end
  54885. def test_should_calculate_grouped_with_invalid_field
  54886. c = Account.count(:all, :group => 'accounts.firm_id')
  54887. assert_equal 1, c[1]
  54888. assert_equal 2, c[6]
  54889. assert_equal 1, c[2]
  54890. end
  54891. def test_should_calculate_grouped_association_with_invalid_field
  54892. c = Account.count(:all, :group => :firm)
  54893. assert_equal 1, c[companies(:first_firm)]
  54894. assert_equal 2, c[companies(:rails_core)]
  54895. assert_equal 1, c[companies(:first_client)]
  54896. end
  54897. def test_should_calculate_grouped_by_function
  54898. c = Company.count(:all, :group => 'UPPER(type)')
  54899. assert_equal 2, c[nil]
  54900. assert_equal 1, c['DEPENDENTFIRM']
  54901. assert_equal 3, c['CLIENT']
  54902. assert_equal 2, c['FIRM']
  54903. end
  54904. def test_should_calculate_grouped_by_function_with_table_alias
  54905. c = Company.count(:all, :group => 'UPPER(companies.type)')
  54906. assert_equal 2, c[nil]
  54907. assert_equal 1, c['DEPENDENTFIRM']
  54908. assert_equal 3, c['CLIENT']
  54909. assert_equal 2, c['FIRM']
  54910. end
  54911. def test_should_sum_scoped_field
  54912. assert_equal 15, companies(:rails_core).companies.sum(:id)
  54913. end
  54914. def test_should_sum_scoped_field_with_conditions
  54915. assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
  54916. end
  54917. def test_should_group_by_scoped_field
  54918. c = companies(:rails_core).companies.sum(:id, :group => :name)
  54919. assert_equal 7, c['Leetsoft']
  54920. assert_equal 8, c['Jadedpixel']
  54921. end
  54922. def test_should_group_by_summed_field_with_conditions_and_having
  54923. c = companies(:rails_core).companies.sum(:id, :group => :name,
  54924. :having => 'sum(id) > 7')
  54925. assert_nil c['Leetsoft']
  54926. assert_equal 8, c['Jadedpixel']
  54927. end
  54928. def test_should_reject_invalid_options
  54929. assert_nothing_raised do
  54930. [:count, :sum].each do |func|
  54931. # empty options are valid
  54932. Company.send(:validate_calculation_options, func)
  54933. # these options are valid for all calculations
  54934. [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt|
  54935. Company.send(:validate_calculation_options, func, opt => true)
  54936. end
  54937. end
  54938. # :include is only valid on :count
  54939. Company.send(:validate_calculation_options, :count, :include => true)
  54940. end
  54941. assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :include => :posts) }
  54942. assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) }
  54943. assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) }
  54944. end
  54945. end
  54946. require 'abstract_unit'
  54947. class CallbackDeveloper < ActiveRecord::Base
  54948. set_table_name 'developers'
  54949. class << self
  54950. def callback_string(callback_method)
  54951. "history << [#{callback_method.to_sym.inspect}, :string]"
  54952. end
  54953. def callback_proc(callback_method)
  54954. Proc.new { |model| model.history << [callback_method, :proc] }
  54955. end
  54956. def define_callback_method(callback_method)
  54957. define_method("#{callback_method}_method") do |model|
  54958. model.history << [callback_method, :method]
  54959. end
  54960. end
  54961. def callback_object(callback_method)
  54962. klass = Class.new
  54963. klass.send(:define_method, callback_method) do |model|
  54964. model.history << [callback_method, :object]
  54965. end
  54966. klass.new
  54967. end
  54968. end
  54969. ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
  54970. callback_method_sym = callback_method.to_sym
  54971. define_callback_method(callback_method_sym)
  54972. send(callback_method, callback_method_sym)
  54973. send(callback_method, callback_string(callback_method_sym))
  54974. send(callback_method, callback_proc(callback_method_sym))
  54975. send(callback_method, callback_object(callback_method_sym))
  54976. send(callback_method) { |model| model.history << [callback_method_sym, :block] }
  54977. end
  54978. def history
  54979. @history ||= []
  54980. end
  54981. # after_initialize and after_find are invoked only if instance methods have been defined.
  54982. def after_initialize
  54983. end
  54984. def after_find
  54985. end
  54986. end
  54987. class RecursiveCallbackDeveloper < ActiveRecord::Base
  54988. set_table_name 'developers'
  54989. before_save :on_before_save
  54990. after_save :on_after_save
  54991. attr_reader :on_before_save_called, :on_after_save_called
  54992. def on_before_save
  54993. @on_before_save_called ||= 0
  54994. @on_before_save_called += 1
  54995. save unless @on_before_save_called > 1
  54996. end
  54997. def on_after_save
  54998. @on_after_save_called ||= 0
  54999. @on_after_save_called += 1
  55000. save unless @on_after_save_called > 1
  55001. end
  55002. end
  55003. class ImmutableDeveloper < ActiveRecord::Base
  55004. set_table_name 'developers'
  55005. validates_inclusion_of :salary, :in => 50000..200000
  55006. before_save :cancel
  55007. before_destroy :cancel
  55008. def cancelled?
  55009. @cancelled == true
  55010. end
  55011. private
  55012. def cancel
  55013. @cancelled = true
  55014. false
  55015. end
  55016. end
  55017. class ImmutableMethodDeveloper < ActiveRecord::Base
  55018. set_table_name 'developers'
  55019. validates_inclusion_of :salary, :in => 50000..200000
  55020. def cancelled?
  55021. @cancelled == true
  55022. end
  55023. def before_save
  55024. @cancelled = true
  55025. false
  55026. end
  55027. def before_destroy
  55028. @cancelled = true
  55029. false
  55030. end
  55031. end
  55032. class CallbacksTest < Test::Unit::TestCase
  55033. fixtures :developers
  55034. def test_initialize
  55035. david = CallbackDeveloper.new
  55036. assert_equal [
  55037. [ :after_initialize, :string ],
  55038. [ :after_initialize, :proc ],
  55039. [ :after_initialize, :object ],
  55040. [ :after_initialize, :block ],
  55041. ], david.history
  55042. end
  55043. def test_find
  55044. david = CallbackDeveloper.find(1)
  55045. assert_equal [
  55046. [ :after_find, :string ],
  55047. [ :after_find, :proc ],
  55048. [ :after_find, :object ],
  55049. [ :after_find, :block ],
  55050. [ :after_initialize, :string ],
  55051. [ :after_initialize, :proc ],
  55052. [ :after_initialize, :object ],
  55053. [ :after_initialize, :block ],
  55054. ], david.history
  55055. end
  55056. def test_new_valid?
  55057. david = CallbackDeveloper.new
  55058. david.valid?
  55059. assert_equal [
  55060. [ :after_initialize, :string ],
  55061. [ :after_initialize, :proc ],
  55062. [ :after_initialize, :object ],
  55063. [ :after_initialize, :block ],
  55064. [ :before_validation, :string ],
  55065. [ :before_validation, :proc ],
  55066. [ :before_validation, :object ],
  55067. [ :before_validation, :block ],
  55068. [ :before_validation_on_create, :string ],
  55069. [ :before_validation_on_create, :proc ],
  55070. [ :before_validation_on_create, :object ],
  55071. [ :before_validation_on_create, :block ],
  55072. [ :after_validation, :string ],
  55073. [ :after_validation, :proc ],
  55074. [ :after_validation, :object ],
  55075. [ :after_validation, :block ],
  55076. [ :after_validation_on_create, :string ],
  55077. [ :after_validation_on_create, :proc ],
  55078. [ :after_validation_on_create, :object ],
  55079. [ :after_validation_on_create, :block ]
  55080. ], david.history
  55081. end
  55082. def test_existing_valid?
  55083. david = CallbackDeveloper.find(1)
  55084. david.valid?
  55085. assert_equal [
  55086. [ :after_find, :string ],
  55087. [ :after_find, :proc ],
  55088. [ :after_find, :object ],
  55089. [ :after_find, :block ],
  55090. [ :after_initialize, :string ],
  55091. [ :after_initialize, :proc ],
  55092. [ :after_initialize, :object ],
  55093. [ :after_initialize, :block ],
  55094. [ :before_validation, :string ],
  55095. [ :before_validation, :proc ],
  55096. [ :before_validation, :object ],
  55097. [ :before_validation, :block ],
  55098. [ :before_validation_on_update, :string ],
  55099. [ :before_validation_on_update, :proc ],
  55100. [ :before_validation_on_update, :object ],
  55101. [ :before_validation_on_update, :block ],
  55102. [ :after_validation, :string ],
  55103. [ :after_validation, :proc ],
  55104. [ :after_validation, :object ],
  55105. [ :after_validation, :block ],
  55106. [ :after_validation_on_update, :string ],
  55107. [ :after_validation_on_update, :proc ],
  55108. [ :after_validation_on_update, :object ],
  55109. [ :after_validation_on_update, :block ]
  55110. ], david.history
  55111. end
  55112. def test_create
  55113. david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000)
  55114. assert_equal [
  55115. [ :after_initialize, :string ],
  55116. [ :after_initialize, :proc ],
  55117. [ :after_initialize, :object ],
  55118. [ :after_initialize, :block ],
  55119. [ :before_validation, :string ],
  55120. [ :before_validation, :proc ],
  55121. [ :before_validation, :object ],
  55122. [ :before_validation, :block ],
  55123. [ :before_validation_on_create, :string ],
  55124. [ :before_validation_on_create, :proc ],
  55125. [ :before_validation_on_create, :object ],
  55126. [ :before_validation_on_create, :block ],
  55127. [ :after_validation, :string ],
  55128. [ :after_validation, :proc ],
  55129. [ :after_validation, :object ],
  55130. [ :after_validation, :block ],
  55131. [ :after_validation_on_create, :string ],
  55132. [ :after_validation_on_create, :proc ],
  55133. [ :after_validation_on_create, :object ],
  55134. [ :after_validation_on_create, :block ],
  55135. [ :before_save, :string ],
  55136. [ :before_save, :proc ],
  55137. [ :before_save, :object ],
  55138. [ :before_save, :block ],
  55139. [ :before_create, :string ],
  55140. [ :before_create, :proc ],
  55141. [ :before_create, :object ],
  55142. [ :before_create, :block ],
  55143. [ :after_create, :string ],
  55144. [ :after_create, :proc ],
  55145. [ :after_create, :object ],
  55146. [ :after_create, :block ],
  55147. [ :after_save, :string ],
  55148. [ :after_save, :proc ],
  55149. [ :after_save, :object ],
  55150. [ :after_save, :block ]
  55151. ], david.history
  55152. end
  55153. def test_save
  55154. david = CallbackDeveloper.find(1)
  55155. david.save
  55156. assert_equal [
  55157. [ :after_find, :string ],
  55158. [ :after_find, :proc ],
  55159. [ :after_find, :object ],
  55160. [ :after_find, :block ],
  55161. [ :after_initialize, :string ],
  55162. [ :after_initialize, :proc ],
  55163. [ :after_initialize, :object ],
  55164. [ :after_initialize, :block ],
  55165. [ :before_validation, :string ],
  55166. [ :before_validation, :proc ],
  55167. [ :before_validation, :object ],
  55168. [ :before_validation, :block ],
  55169. [ :before_validation_on_update, :string ],
  55170. [ :before_validation_on_update, :proc ],
  55171. [ :before_validation_on_update, :object ],
  55172. [ :before_validation_on_update, :block ],
  55173. [ :after_validation, :string ],
  55174. [ :after_validation, :proc ],
  55175. [ :after_validation, :object ],
  55176. [ :after_validation, :block ],
  55177. [ :after_validation_on_update, :string ],
  55178. [ :after_validation_on_update, :proc ],
  55179. [ :after_validation_on_update, :object ],
  55180. [ :after_validation_on_update, :block ],
  55181. [ :before_save, :string ],
  55182. [ :before_save, :proc ],
  55183. [ :before_save, :object ],
  55184. [ :before_save, :block ],
  55185. [ :before_update, :string ],
  55186. [ :before_update, :proc ],
  55187. [ :before_update, :object ],
  55188. [ :before_update, :block ],
  55189. [ :after_update, :string ],
  55190. [ :after_update, :proc ],
  55191. [ :after_update, :object ],
  55192. [ :after_update, :block ],
  55193. [ :after_save, :string ],
  55194. [ :after_save, :proc ],
  55195. [ :after_save, :object ],
  55196. [ :after_save, :block ]
  55197. ], david.history
  55198. end
  55199. def test_destroy
  55200. david = CallbackDeveloper.find(1)
  55201. david.destroy
  55202. assert_equal [
  55203. [ :after_find, :string ],
  55204. [ :after_find, :proc ],
  55205. [ :after_find, :object ],
  55206. [ :after_find, :block ],
  55207. [ :after_initialize, :string ],
  55208. [ :after_initialize, :proc ],
  55209. [ :after_initialize, :object ],
  55210. [ :after_initialize, :block ],
  55211. [ :before_destroy, :string ],
  55212. [ :before_destroy, :proc ],
  55213. [ :before_destroy, :object ],
  55214. [ :before_destroy, :block ],
  55215. [ :after_destroy, :string ],
  55216. [ :after_destroy, :proc ],
  55217. [ :after_destroy, :object ],
  55218. [ :after_destroy, :block ]
  55219. ], david.history
  55220. end
  55221. def test_delete
  55222. david = CallbackDeveloper.find(1)
  55223. CallbackDeveloper.delete(david.id)
  55224. assert_equal [
  55225. [ :after_find, :string ],
  55226. [ :after_find, :proc ],
  55227. [ :after_find, :object ],
  55228. [ :after_find, :block ],
  55229. [ :after_initialize, :string ],
  55230. [ :after_initialize, :proc ],
  55231. [ :after_initialize, :object ],
  55232. [ :after_initialize, :block ],
  55233. ], david.history
  55234. end
  55235. def test_before_save_returning_false
  55236. david = ImmutableDeveloper.find(1)
  55237. assert david.valid?
  55238. assert !david.save
  55239. assert_raises(ActiveRecord::RecordNotSaved) { david.save! }
  55240. david = ImmutableDeveloper.find(1)
  55241. david.salary = 10_000_000
  55242. assert !david.valid?
  55243. assert !david.save
  55244. assert_raises(ActiveRecord::RecordInvalid) { david.save! }
  55245. end
  55246. def test_before_destroy_returning_false
  55247. david = ImmutableDeveloper.find(1)
  55248. assert !david.destroy
  55249. assert_not_nil ImmutableDeveloper.find_by_id(1)
  55250. end
  55251. def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper
  55252. david = CallbackDeveloper.find(1)
  55253. CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
  55254. CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
  55255. david.save
  55256. assert_equal [
  55257. [ :after_find, :string ],
  55258. [ :after_find, :proc ],
  55259. [ :after_find, :object ],
  55260. [ :after_find, :block ],
  55261. [ :after_initialize, :string ],
  55262. [ :after_initialize, :proc ],
  55263. [ :after_initialize, :object ],
  55264. [ :after_initialize, :block ],
  55265. [ :before_validation, :string ],
  55266. [ :before_validation, :proc ],
  55267. [ :before_validation, :object ],
  55268. [ :before_validation, :block ],
  55269. [ :before_validation, :returning_false ]
  55270. ], david.history
  55271. end
  55272. end
  55273. require 'test/unit'
  55274. require 'abstract_unit'
  55275. require 'active_support/core_ext/class/inheritable_attributes'
  55276. class A
  55277. include ClassInheritableAttributes
  55278. end
  55279. class B < A
  55280. write_inheritable_array "first", [ :one, :two ]
  55281. end
  55282. class C < A
  55283. write_inheritable_array "first", [ :three ]
  55284. end
  55285. class D < B
  55286. write_inheritable_array "first", [ :four ]
  55287. end
  55288. class ClassInheritableAttributesTest < Test::Unit::TestCase
  55289. def test_first_level
  55290. assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
  55291. assert_equal [ :three ], C.read_inheritable_attribute("first")
  55292. end
  55293. def test_second_level
  55294. assert_equal [ :one, :two, :four ], D.read_inheritable_attribute("first")
  55295. assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
  55296. end
  55297. end
  55298. require 'abstract_unit'
  55299. require 'fixtures/topic'
  55300. class TestColumnAlias < Test::Unit::TestCase
  55301. fixtures :topics
  55302. QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name
  55303. 'SELECT id AS pk FROM topics WHERE ROWNUM < 2'
  55304. else
  55305. 'SELECT id AS pk FROM topics'
  55306. end
  55307. def test_column_alias
  55308. records = Topic.connection.select_all(QUERY)
  55309. assert_equal 'pk', records[0].keys[0]
  55310. end
  55311. end
  55312. print "Using native DB2\n"
  55313. require_dependency 'fixtures/course'
  55314. require 'logger'
  55315. ActiveRecord::Base.logger = Logger.new("debug.log")
  55316. db1 = 'arunit'
  55317. db2 = 'arunit2'
  55318. ActiveRecord::Base.establish_connection(
  55319. :adapter => "db2",
  55320. :host => "localhost",
  55321. :username => "arunit",
  55322. :password => "arunit",
  55323. :database => db1
  55324. )
  55325. Course.establish_connection(
  55326. :adapter => "db2",
  55327. :host => "localhost",
  55328. :username => "arunit2",
  55329. :password => "arunit2",
  55330. :database => db2
  55331. )
  55332. print "Using native Firebird\n"
  55333. require_dependency 'fixtures/course'
  55334. require 'logger'
  55335. ActiveRecord::Base.logger = Logger.new("debug.log")
  55336. db1 = 'activerecord_unittest'
  55337. db2 = 'activerecord_unittest2'
  55338. ActiveRecord::Base.establish_connection(
  55339. :adapter => "firebird",
  55340. :host => "localhost",
  55341. :username => "rails",
  55342. :password => "rails",
  55343. :database => db1
  55344. )
  55345. Course.establish_connection(
  55346. :adapter => "firebird",
  55347. :host => "localhost",
  55348. :username => "rails",
  55349. :password => "rails",
  55350. :database => db2
  55351. )
  55352. print "Using native MySQL\n"
  55353. require_dependency 'fixtures/course'
  55354. require 'logger'
  55355. ActiveRecord::Base.logger = Logger.new("debug.log")
  55356. db1 = 'activerecord_unittest'
  55357. db2 = 'activerecord_unittest2'
  55358. ActiveRecord::Base.establish_connection(
  55359. :adapter => "mysql",
  55360. :username => "rails",
  55361. :encoding => "utf8",
  55362. :database => db1
  55363. )
  55364. Course.establish_connection(
  55365. :adapter => "mysql",
  55366. :username => "rails",
  55367. :database => db2
  55368. )
  55369. print "Using native OpenBase\n"
  55370. require_dependency 'fixtures/course'
  55371. require 'logger'
  55372. ActiveRecord::Base.logger = Logger.new("debug.log")
  55373. db1 = 'activerecord_unittest'
  55374. db2 = 'activerecord_unittest2'
  55375. ActiveRecord::Base.establish_connection(
  55376. :adapter => "openbase",
  55377. :username => "admin",
  55378. :password => "",
  55379. :database => db1
  55380. )
  55381. Course.establish_connection(
  55382. :adapter => "openbase",
  55383. :username => "admin",
  55384. :password => "",
  55385. :database => db2
  55386. )
  55387. print "Using Oracle\n"
  55388. require_dependency 'fixtures/course'
  55389. require 'logger'
  55390. ActiveRecord::Base.logger = Logger.new STDOUT
  55391. ActiveRecord::Base.logger.level = Logger::WARN
  55392. # Set these to your database connection strings
  55393. db = ENV['ARUNIT_DB'] || 'activerecord_unittest'
  55394. ActiveRecord::Base.establish_connection(
  55395. :adapter => 'oracle',
  55396. :username => 'arunit',
  55397. :password => 'arunit',
  55398. :database => db
  55399. )
  55400. Course.establish_connection(
  55401. :adapter => 'oracle',
  55402. :username => 'arunit2',
  55403. :password => 'arunit2',
  55404. :database => db
  55405. )
  55406. print "Using native PostgreSQL\n"
  55407. require_dependency 'fixtures/course'
  55408. require 'logger'
  55409. ActiveRecord::Base.logger = Logger.new("debug.log")
  55410. db1 = 'activerecord_unittest'
  55411. db2 = 'activerecord_unittest2'
  55412. ActiveRecord::Base.establish_connection(
  55413. :adapter => "postgresql",
  55414. :username => "postgres",
  55415. :password => "postgres",
  55416. :database => db1,
  55417. :min_messages => "warning"
  55418. )
  55419. Course.establish_connection(
  55420. :adapter => "postgresql",
  55421. :username => "postgres",
  55422. :password => "postgres",
  55423. :database => db2,
  55424. :min_messages => "warning"
  55425. )
  55426. print "Using native SQlite\n"
  55427. require_dependency 'fixtures/course'
  55428. require 'logger'
  55429. ActiveRecord::Base.logger = Logger.new("debug.log")
  55430. class SqliteError < StandardError
  55431. end
  55432. BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
  55433. sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite"
  55434. sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite"
  55435. def make_connection(clazz, db_file, db_definitions_file)
  55436. unless File.exist?(db_file)
  55437. puts "SQLite database not found at #{db_file}. Rebuilding it."
  55438. sqlite_command = %Q{sqlite #{db_file} "create table a (a integer); drop table a;"}
  55439. puts "Executing '#{sqlite_command}'"
  55440. raise SqliteError.new("Seems that there is no sqlite executable available") unless system(sqlite_command)
  55441. clazz.establish_connection(
  55442. :adapter => "sqlite",
  55443. :database => db_file)
  55444. script = File.read("#{BASE_DIR}/db_definitions/#{db_definitions_file}")
  55445. # SQLite-Ruby has problems with semi-colon separated commands, so split and execute one at a time
  55446. script.split(';').each do
  55447. |command|
  55448. clazz.connection.execute(command) unless command.strip.empty?
  55449. end
  55450. else
  55451. clazz.establish_connection(
  55452. :adapter => "sqlite",
  55453. :database => db_file)
  55454. end
  55455. end
  55456. make_connection(ActiveRecord::Base, sqlite_test_db, 'sqlite.sql')
  55457. make_connection(Course, sqlite_test_db2, 'sqlite2.sql')
  55458. load(File.join(BASE_DIR, 'db_definitions', 'schema.rb'))
  55459. print "Using native SQLite3\n"
  55460. require_dependency 'fixtures/course'
  55461. require 'logger'
  55462. ActiveRecord::Base.logger = Logger.new("debug.log")
  55463. class SqliteError < StandardError
  55464. end
  55465. BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
  55466. sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3"
  55467. sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"
  55468. def make_connection(clazz, db_file, db_definitions_file)
  55469. unless File.exist?(db_file)
  55470. puts "SQLite3 database not found at #{db_file}. Rebuilding it."
  55471. sqlite_command = %Q{sqlite3 #{db_file} "create table a (a integer); drop table a;"}
  55472. puts "Executing '#{sqlite_command}'"
  55473. raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command)
  55474. clazz.establish_connection(
  55475. :adapter => "sqlite3",
  55476. :database => db_file)
  55477. script = File.read("#{BASE_DIR}/db_definitions/#{db_definitions_file}")
  55478. # SQLite-Ruby has problems with semi-colon separated commands, so split and execute one at a time
  55479. script.split(';').each do
  55480. |command|
  55481. clazz.connection.execute(command) unless command.strip.empty?
  55482. end
  55483. else
  55484. clazz.establish_connection(
  55485. :adapter => "sqlite3",
  55486. :database => db_file)
  55487. end
  55488. end
  55489. make_connection(ActiveRecord::Base, sqlite_test_db, 'sqlite.sql')
  55490. make_connection(Course, sqlite_test_db2, 'sqlite2.sql')
  55491. load(File.join(BASE_DIR, 'db_definitions', 'schema.rb'))
  55492. print "Using native SQLite3\n"
  55493. require_dependency 'fixtures/course'
  55494. require 'logger'
  55495. ActiveRecord::Base.logger = Logger.new("debug.log")
  55496. class SqliteError < StandardError
  55497. end
  55498. def make_connection(clazz, db_definitions_file)
  55499. clazz.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
  55500. File.read("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/#{db_definitions_file}").split(';').each do |command|
  55501. clazz.connection.execute(command) unless command.strip.empty?
  55502. end
  55503. end
  55504. make_connection(ActiveRecord::Base, 'sqlite.sql')
  55505. make_connection(Course, 'sqlite2.sql')
  55506. load("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/schema.rb"))
  55507. print "Using native SQLServer\n"
  55508. require_dependency 'fixtures/course'
  55509. require 'logger'
  55510. ActiveRecord::Base.logger = Logger.new("debug.log")
  55511. db1 = 'activerecord_unittest'
  55512. db2 = 'activerecord_unittest2'
  55513. ActiveRecord::Base.establish_connection(
  55514. :adapter => "sqlserver",
  55515. :host => "localhost",
  55516. :username => "sa",
  55517. :password => "",
  55518. :database => db1
  55519. )
  55520. Course.establish_connection(
  55521. :adapter => "sqlserver",
  55522. :host => "localhost",
  55523. :username => "sa",
  55524. :password => "",
  55525. :database => db2
  55526. )
  55527. print "Using native SQLServer via ODBC\n"
  55528. require_dependency 'fixtures/course'
  55529. require 'logger'
  55530. ActiveRecord::Base.logger = Logger.new("debug.log")
  55531. dsn1 = 'activerecord_unittest'
  55532. dsn2 = 'activerecord_unittest2'
  55533. ActiveRecord::Base.establish_connection(
  55534. :adapter => "sqlserver",
  55535. :mode => "ODBC",
  55536. :host => "localhost",
  55537. :username => "sa",
  55538. :password => "",
  55539. :dsn => dsn1
  55540. )
  55541. Course.establish_connection(
  55542. :adapter => "sqlserver",
  55543. :mode => "ODBC",
  55544. :host => "localhost",
  55545. :username => "sa",
  55546. :password => "",
  55547. :dsn => dsn2
  55548. )
  55549. print "Using native Sybase Open Client\n"
  55550. require_dependency 'fixtures/course'
  55551. require 'logger'
  55552. ActiveRecord::Base.logger = Logger.new("debug.log")
  55553. db1 = 'activerecord_unittest'
  55554. db2 = 'activerecord_unittest2'
  55555. ActiveRecord::Base.establish_connection(
  55556. :adapter => "sybase",
  55557. :host => "database_ASE",
  55558. :username => "sa",
  55559. :password => "",
  55560. :database => db1
  55561. )
  55562. Course.establish_connection(
  55563. :adapter => "sybase",
  55564. :host => "database_ASE",
  55565. :username => "sa",
  55566. :password => "",
  55567. :database => db2
  55568. )
  55569. require 'abstract_unit'
  55570. class CopyTableTest < Test::Unit::TestCase
  55571. fixtures :companies, :comments
  55572. def setup
  55573. @connection = ActiveRecord::Base.connection
  55574. class << @connection
  55575. public :copy_table, :table_structure, :indexes
  55576. end
  55577. end
  55578. def test_copy_table(from = 'companies', to = 'companies2', options = {})
  55579. assert_nothing_raised {copy_table(from, to, options)}
  55580. assert_equal row_count(from), row_count(to)
  55581. if block_given?
  55582. yield from, to, options
  55583. else
  55584. assert_equal column_names(from), column_names(to)
  55585. end
  55586. @connection.drop_table(to) rescue nil
  55587. end
  55588. def test_copy_table_renaming_column
  55589. test_copy_table('companies', 'companies2',
  55590. :rename => {'client_of' => 'fan_of'}) do |from, to, options|
  55591. assert_equal column_values(from, 'client_of').compact.sort,
  55592. column_values(to, 'fan_of').compact.sort
  55593. end
  55594. end
  55595. def test_copy_table_with_index
  55596. test_copy_table('comments', 'comments_with_index') do
  55597. @connection.add_index('comments_with_index', ['post_id', 'type'])
  55598. test_copy_table('comments_with_index', 'comments_with_index2') do
  55599. assert_equal table_indexes_without_name('comments_with_index'),
  55600. table_indexes_without_name('comments_with_index2')
  55601. end
  55602. end
  55603. end
  55604. protected
  55605. def copy_table(from, to, options = {})
  55606. @connection.copy_table(from, to, {:temporary => true}.merge(options))
  55607. end
  55608. def column_names(table)
  55609. @connection.table_structure(table).map {|column| column['name']}
  55610. end
  55611. def column_values(table, column)
  55612. @connection.select_all("SELECT #{column} FROM #{table}").map {|row| row[column]}
  55613. end
  55614. def table_indexes_without_name(table)
  55615. @connection.indexes('comments_with_index').delete(:name)
  55616. end
  55617. def row_count(table)
  55618. @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count']
  55619. end
  55620. end
  55621. require 'abstract_unit'
  55622. require 'fixtures/default'
  55623. class DefaultTest < Test::Unit::TestCase
  55624. def test_default_timestamp
  55625. default = Default.new
  55626. assert_instance_of(Time, default.default_timestamp)
  55627. assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type)
  55628. # Variance should be small; increase if required -- e.g., if test db is on
  55629. # remote host and clocks aren't synchronized.
  55630. t1 = Time.new
  55631. accepted_variance = 1.0
  55632. assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance)
  55633. end
  55634. end
  55635. require 'abstract_unit'
  55636. require 'fixtures/default'
  55637. class DefaultsTest < Test::Unit::TestCase
  55638. if %w(PostgreSQL).include? ActiveRecord::Base.connection.adapter_name
  55639. def test_default_integers
  55640. default = Default.new
  55641. assert_instance_of(Fixnum, default.positive_integer)
  55642. assert_equal(default.positive_integer, 1)
  55643. assert_instance_of(Fixnum, default.negative_integer)
  55644. assert_equal(default.negative_integer, -1)
  55645. end
  55646. else
  55647. def test_dummy
  55648. assert true
  55649. end
  55650. end
  55651. end
  55652. require 'abstract_unit'
  55653. require 'fixtures/developer'
  55654. require 'fixtures/project'
  55655. require 'fixtures/company'
  55656. require 'fixtures/topic'
  55657. require 'fixtures/reply'
  55658. # Can't declare new classes in test case methods, so tests before that
  55659. bad_collection_keys = false
  55660. begin
  55661. class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
  55662. rescue ArgumentError
  55663. bad_collection_keys = true
  55664. end
  55665. raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys
  55666. class DeprecatedAssociationsTest < Test::Unit::TestCase
  55667. fixtures :accounts, :companies, :developers, :projects, :topics,
  55668. :developers_projects
  55669. def test_has_many_find
  55670. assert_equal 2, Firm.find_first.clients.length
  55671. end
  55672. def test_has_many_orders
  55673. assert_equal "Summit", Firm.find_first.clients.first.name
  55674. end
  55675. def test_has_many_class_name
  55676. assert_equal "Microsoft", Firm.find_first.clients_sorted_desc.first.name
  55677. end
  55678. def test_has_many_foreign_key
  55679. assert_equal "Microsoft", Firm.find_first.clients_of_firm.first.name
  55680. end
  55681. def test_has_many_conditions
  55682. assert_equal "Microsoft", Firm.find_first.clients_like_ms.first.name
  55683. end
  55684. def test_has_many_sql
  55685. firm = Firm.find_first
  55686. assert_equal "Microsoft", firm.clients_using_sql.first.name
  55687. assert_equal 1, firm.clients_using_sql_count
  55688. assert_equal 1, Firm.find_first.clients_using_sql_count
  55689. end
  55690. def test_has_many_counter_sql
  55691. assert_equal 1, Firm.find_first.clients_using_counter_sql_count
  55692. end
  55693. def test_has_many_queries
  55694. assert Firm.find_first.has_clients?
  55695. firm = Firm.find_first
  55696. assert_equal 2, firm.clients_count # tests using class count
  55697. firm.clients
  55698. assert firm.has_clients?
  55699. assert_equal 2, firm.clients_count # tests using collection length
  55700. end
  55701. def test_has_many_dependence
  55702. assert_equal 3, Client.find_all.length
  55703. Firm.find_first.destroy
  55704. assert_equal 1, Client.find_all.length
  55705. end
  55706. uses_transaction :test_has_many_dependence_with_transaction_support_on_failure
  55707. def test_has_many_dependence_with_transaction_support_on_failure
  55708. assert_equal 3, Client.find_all.length
  55709. firm = Firm.find_first
  55710. clients = firm.clients
  55711. clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }
  55712. firm.destroy rescue "do nothing"
  55713. assert_equal 3, Client.find_all.length
  55714. end
  55715. def test_has_one_dependence
  55716. num_accounts = Account.count
  55717. firm = Firm.find(1)
  55718. assert firm.has_account?
  55719. firm.destroy
  55720. assert_equal num_accounts - 1, Account.count
  55721. end
  55722. def test_has_one_dependence_with_missing_association
  55723. Account.destroy_all
  55724. firm = Firm.find(1)
  55725. assert !firm.has_account?
  55726. firm.destroy
  55727. end
  55728. def test_belongs_to
  55729. assert_equal companies(:first_firm).name, Client.find(3).firm.name
  55730. assert Client.find(3).has_firm?, "Microsoft should have a firm"
  55731. # assert !Company.find(1).has_firm?, "37signals shouldn't have a firm"
  55732. end
  55733. def test_belongs_to_with_different_class_name
  55734. assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
  55735. assert Company.find(3).has_firm_with_other_name?, "Microsoft should have a firm"
  55736. end
  55737. def test_belongs_to_with_condition
  55738. assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
  55739. assert Company.find(3).has_firm_with_condition?, "Microsoft should have a firm"
  55740. end
  55741. def test_belongs_to_equality
  55742. assert Company.find(3).firm?(Company.find(1)), "Microsoft should have 37signals as firm"
  55743. assert_raises(RuntimeError) { !Company.find(3).firm?(Company.find(3)) } # "Summit shouldn't have itself as firm"
  55744. end
  55745. def test_has_one
  55746. assert companies(:first_firm).account?(Account.find(1))
  55747. assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
  55748. assert companies(:first_firm).has_account?, "37signals should have an account"
  55749. assert Account.find(1).firm?(companies(:first_firm)), "37signals account should be able to backtrack"
  55750. assert Account.find(1).has_firm?, "37signals account should be able to backtrack"
  55751. assert !Account.find(2).has_firm?, "Unknown isn't linked"
  55752. assert !Account.find(2).firm?(companies(:first_firm)), "Unknown isn't linked"
  55753. end
  55754. def test_has_many_dependence_on_account
  55755. num_accounts = Account.count
  55756. companies(:first_firm).destroy
  55757. assert_equal num_accounts - 1, Account.count
  55758. end
  55759. def test_find_in
  55760. assert_equal Client.find(2).name, companies(:first_firm).find_in_clients(2).name
  55761. assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).find_in_clients(6) }
  55762. end
  55763. def test_force_reload
  55764. firm = Firm.new("name" => "A New Firm, Inc")
  55765. firm.save
  55766. firm.clients.each {|c|} # forcing to load all clients
  55767. assert firm.clients.empty?, "New firm shouldn't have client objects"
  55768. assert !firm.has_clients?, "New firm shouldn't have clients"
  55769. assert_equal 0, firm.clients_count, "New firm should have 0 clients"
  55770. client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
  55771. client.save
  55772. assert firm.clients.empty?, "New firm should have cached no client objects"
  55773. assert !firm.has_clients?, "New firm should have cached a no-clients response"
  55774. assert_equal 0, firm.clients_count, "New firm should have cached 0 clients count"
  55775. assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
  55776. assert firm.has_clients?(true), "New firm should have reloaded with a have-clients response"
  55777. assert_equal 1, firm.clients_count(true), "New firm should have reloaded clients count"
  55778. end
  55779. def test_included_in_collection
  55780. assert companies(:first_firm).clients.include?(Client.find(2))
  55781. end
  55782. def test_build_to_collection
  55783. assert_equal 1, companies(:first_firm).clients_of_firm_count
  55784. new_client = companies(:first_firm).build_to_clients_of_firm("name" => "Another Client")
  55785. assert_equal "Another Client", new_client.name
  55786. assert new_client.save
  55787. assert new_client.firm?(companies(:first_firm))
  55788. assert_equal 2, companies(:first_firm).clients_of_firm_count(true)
  55789. end
  55790. def test_create_in_collection
  55791. assert_equal companies(:first_firm).create_in_clients_of_firm("name" => "Another Client"), companies(:first_firm).clients_of_firm(true).last
  55792. end
  55793. def test_has_and_belongs_to_many
  55794. david = Developer.find(1)
  55795. assert david.has_projects?
  55796. assert_equal 2, david.projects_count
  55797. active_record = Project.find(1)
  55798. assert active_record.has_developers?
  55799. assert_equal 3, active_record.developers_count
  55800. assert active_record.developers.include?(david)
  55801. end
  55802. def test_has_and_belongs_to_many_removing
  55803. david = Developer.find(1)
  55804. active_record = Project.find(1)
  55805. david.remove_projects(active_record)
  55806. assert_equal 1, david.projects_count
  55807. assert_equal 2, active_record.developers_count
  55808. end
  55809. def test_has_and_belongs_to_many_zero
  55810. david = Developer.find(1)
  55811. david.remove_projects(Project.find_all)
  55812. assert_equal 0, david.projects_count
  55813. assert !david.has_projects?
  55814. end
  55815. def test_has_and_belongs_to_many_adding
  55816. jamis = Developer.find(2)
  55817. action_controller = Project.find(2)
  55818. jamis.add_projects(action_controller)
  55819. assert_equal 2, jamis.projects_count
  55820. assert_equal 2, action_controller.developers_count
  55821. end
  55822. def test_has_and_belongs_to_many_adding_from_the_project
  55823. jamis = Developer.find(2)
  55824. action_controller = Project.find(2)
  55825. action_controller.add_developers(jamis)
  55826. assert_equal 2, jamis.projects_count
  55827. assert_equal 2, action_controller.developers_count
  55828. end
  55829. def test_has_and_belongs_to_many_adding_a_collection
  55830. aredridel = Developer.new("name" => "Aredridel")
  55831. aredridel.save
  55832. aredridel.add_projects([ Project.find(1), Project.find(2) ])
  55833. assert_equal 2, aredridel.projects_count
  55834. end
  55835. def test_belongs_to_counter
  55836. topic = Topic.create("title" => "Apple", "content" => "hello world")
  55837. assert_equal 0, topic.send(:read_attribute, "replies_count"), "No replies yet"
  55838. reply = topic.create_in_replies("title" => "I'm saying no!", "content" => "over here")
  55839. assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count"), "First reply created"
  55840. reply.destroy
  55841. assert_equal 0, Topic.find(topic.id).send(:read_attribute, "replies_count"), "First reply deleted"
  55842. end
  55843. def test_natural_assignment_of_has_one
  55844. apple = Firm.create("name" => "Apple")
  55845. citibank = Account.create("credit_limit" => 10)
  55846. apple.account = citibank
  55847. assert_equal apple.id, citibank.firm_id
  55848. end
  55849. def test_natural_assignment_of_belongs_to
  55850. apple = Firm.create("name" => "Apple")
  55851. citibank = Account.create("credit_limit" => 10)
  55852. citibank.firm = apple
  55853. assert_equal apple.id, citibank.firm_id
  55854. end
  55855. def test_natural_assignment_of_has_many
  55856. apple = Firm.create("name" => "Apple")
  55857. natural = Client.create("name" => "Natural Company")
  55858. apple.clients << natural
  55859. assert_equal apple.id, natural.firm_id
  55860. assert_equal Client.find(natural.id), Firm.find(apple.id).clients.find(natural.id)
  55861. apple.clients.delete natural
  55862. assert_raises(ActiveRecord::RecordNotFound) {
  55863. Firm.find(apple.id).clients.find(natural.id)
  55864. }
  55865. end
  55866. def test_natural_adding_of_has_and_belongs_to_many
  55867. rails = Project.create("name" => "Rails")
  55868. ap = Project.create("name" => "Action Pack")
  55869. john = Developer.create("name" => "John")
  55870. mike = Developer.create("name" => "Mike")
  55871. rails.developers << john
  55872. rails.developers << mike
  55873. assert_equal Developer.find(john.id), Project.find(rails.id).developers.find(john.id)
  55874. assert_equal Developer.find(mike.id), Project.find(rails.id).developers.find(mike.id)
  55875. assert_equal Project.find(rails.id), Developer.find(mike.id).projects.find(rails.id)
  55876. assert_equal Project.find(rails.id), Developer.find(john.id).projects.find(rails.id)
  55877. ap.developers << john
  55878. assert_equal Developer.find(john.id), Project.find(ap.id).developers.find(john.id)
  55879. assert_equal Project.find(ap.id), Developer.find(john.id).projects.find(ap.id)
  55880. ap.developers.delete john
  55881. assert_raises(ActiveRecord::RecordNotFound) {
  55882. Project.find(ap.id).developers.find(john.id)
  55883. }
  55884. assert_raises(ActiveRecord::RecordNotFound) {
  55885. Developer.find(john.id).projects.find(ap.id)
  55886. }
  55887. end
  55888. def test_storing_in_pstore
  55889. require "pstore"
  55890. require "tmpdir"
  55891. apple = Firm.create("name" => "Apple")
  55892. natural = Client.new("name" => "Natural Company")
  55893. apple.clients << natural
  55894. db = PStore.new(File.join(Dir.tmpdir, "ar-pstore-association-test"))
  55895. db.transaction do
  55896. db["apple"] = apple
  55897. end
  55898. db = PStore.new(File.join(Dir.tmpdir, "ar-pstore-association-test"))
  55899. db.transaction do
  55900. assert_equal "Natural Company", db["apple"].clients.first.name
  55901. end
  55902. end
  55903. def test_has_many_find_all
  55904. assert_equal 2, Firm.find_first.find_all_in_clients("#{QUOTED_TYPE} = 'Client'").length
  55905. assert_equal 1, Firm.find_first.find_all_in_clients("name = 'Summit'").length
  55906. end
  55907. def test_has_one
  55908. assert companies(:first_firm).account?(Account.find(1))
  55909. assert companies(:first_firm).has_account?, "37signals should have an account"
  55910. assert Account.find(1).firm?(companies(:first_firm)), "37signals account should be able to backtrack"
  55911. assert Account.find(1).has_firm?, "37signals account should be able to backtrack"
  55912. assert !Account.find(2).has_firm?, "Unknown isn't linked"
  55913. assert !Account.find(2).firm?(companies(:first_firm)), "Unknown isn't linked"
  55914. end
  55915. def test_has_one_build
  55916. firm = Firm.new("name" => "GlobalMegaCorp")
  55917. assert firm.save
  55918. account = firm.build_account("credit_limit" => 1000)
  55919. assert account.save
  55920. assert_equal account, firm.account
  55921. end
  55922. def test_has_one_failing_build_association
  55923. firm = Firm.new("name" => "GlobalMegaCorp")
  55924. firm.save
  55925. account = firm.build_account
  55926. assert !account.save
  55927. assert_equal "can't be empty", account.errors.on("credit_limit")
  55928. end
  55929. def test_has_one_create
  55930. firm = Firm.new("name" => "GlobalMegaCorp")
  55931. firm.save
  55932. assert_equal firm.create_account("credit_limit" => 1000), firm.account
  55933. end
  55934. end
  55935. require 'abstract_unit'
  55936. require 'fixtures/company'
  55937. require 'fixtures/topic'
  55938. require 'fixtures/entrant'
  55939. require 'fixtures/developer'
  55940. class DeprecatedFinderTest < Test::Unit::TestCase
  55941. fixtures :companies, :topics, :entrants, :developers
  55942. def test_find_all_with_limit
  55943. entrants = Entrant.find_all nil, "id ASC", 2
  55944. assert_equal(2, entrants.size)
  55945. assert_equal(entrants(:first).name, entrants.first.name)
  55946. end
  55947. def test_find_all_with_prepared_limit_and_offset
  55948. entrants = Entrant.find_all nil, "id ASC", [2, 1]
  55949. assert_equal(2, entrants.size)
  55950. assert_equal(entrants(:second).name, entrants.first.name)
  55951. end
  55952. def test_find_first
  55953. first = Topic.find_first "title = 'The First Topic'"
  55954. assert_equal(topics(:first).title, first.title)
  55955. end
  55956. def test_find_first_failing
  55957. first = Topic.find_first "title = 'The First Topic!'"
  55958. assert_nil(first)
  55959. end
  55960. def test_deprecated_find_on_conditions
  55961. assert Topic.find_on_conditions(1, ["approved = ?", false])
  55962. assert_raises(ActiveRecord::RecordNotFound) { Topic.find_on_conditions(1, ["approved = ?", true]) }
  55963. end
  55964. def test_condition_interpolation
  55965. assert_kind_of Firm, Company.find_first(["name = '%s'", "37signals"])
  55966. assert_nil Company.find_first(["name = '%s'", "37signals!"])
  55967. assert_nil Company.find_first(["name = '%s'", "37signals!' OR 1=1"])
  55968. assert_kind_of Time, Topic.find_first(["id = %d", 1]).written_on
  55969. end
  55970. def test_bind_variables
  55971. assert_kind_of Firm, Company.find_first(["name = ?", "37signals"])
  55972. assert_nil Company.find_first(["name = ?", "37signals!"])
  55973. assert_nil Company.find_first(["name = ?", "37signals!' OR 1=1"])
  55974. assert_kind_of Time, Topic.find_first(["id = ?", 1]).written_on
  55975. assert_raises(ActiveRecord::PreparedStatementInvalid) {
  55976. Company.find_first(["id=? AND name = ?", 2])
  55977. }
  55978. assert_raises(ActiveRecord::PreparedStatementInvalid) {
  55979. Company.find_first(["id=?", 2, 3, 4])
  55980. }
  55981. end
  55982. def test_bind_variables_with_quotes
  55983. Company.create("name" => "37signals' go'es agains")
  55984. assert Company.find_first(["name = ?", "37signals' go'es agains"])
  55985. end
  55986. def test_named_bind_variables_with_quotes
  55987. Company.create("name" => "37signals' go'es agains")
  55988. assert Company.find_first(["name = :name", {:name => "37signals' go'es agains"}])
  55989. end
  55990. def test_named_bind_variables
  55991. assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
  55992. assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
  55993. assert_kind_of Firm, Company.find_first(["name = :name", { :name => "37signals" }])
  55994. assert_nil Company.find_first(["name = :name", { :name => "37signals!" }])
  55995. assert_nil Company.find_first(["name = :name", { :name => "37signals!' OR 1=1" }])
  55996. assert_kind_of Time, Topic.find_first(["id = :id", { :id => 1 }]).written_on
  55997. end
  55998. def test_count
  55999. assert_equal(0, Entrant.count("id > 3"))
  56000. assert_equal(1, Entrant.count(["id > ?", 2]))
  56001. assert_equal(2, Entrant.count(["id > ?", 1]))
  56002. end
  56003. def test_count_by_sql
  56004. assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
  56005. assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
  56006. assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
  56007. end
  56008. def test_find_all_with_limit
  56009. first_five_developers = Developer.find_all nil, 'id ASC', 5
  56010. assert_equal 5, first_five_developers.length
  56011. assert_equal 'David', first_five_developers.first.name
  56012. assert_equal 'fixture_5', first_five_developers.last.name
  56013. no_developers = Developer.find_all nil, 'id ASC', 0
  56014. assert_equal 0, no_developers.length
  56015. assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5])
  56016. assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0])
  56017. end
  56018. def test_find_all_with_limit_and_offset
  56019. first_three_developers = Developer.find_all nil, 'id ASC', [3, 0]
  56020. second_three_developers = Developer.find_all nil, 'id ASC', [3, 3]
  56021. last_two_developers = Developer.find_all nil, 'id ASC', [2, 8]
  56022. assert_equal 3, first_three_developers.length
  56023. assert_equal 3, second_three_developers.length
  56024. assert_equal 2, last_two_developers.length
  56025. assert_equal 'David', first_three_developers.first.name
  56026. assert_equal 'fixture_4', second_three_developers.first.name
  56027. assert_equal 'fixture_9', last_two_developers.first.name
  56028. end
  56029. def test_find_all_by_one_attribute_with_options
  56030. topics = Topic.find_all_by_content("Have a nice day", "id DESC")
  56031. assert topics(:first), topics.last
  56032. topics = Topic.find_all_by_content("Have a nice day", "id DESC")
  56033. assert topics(:first), topics.first
  56034. end
  56035. protected
  56036. def bind(statement, *vars)
  56037. if vars.first.is_a?(Hash)
  56038. ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
  56039. else
  56040. ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
  56041. end
  56042. end
  56043. end
  56044. require 'abstract_unit'
  56045. require 'fixtures/company'
  56046. require 'fixtures/topic'
  56047. require 'fixtures/reply'
  56048. require 'fixtures/entrant'
  56049. require 'fixtures/developer'
  56050. require 'fixtures/post'
  56051. class FinderTest < Test::Unit::TestCase
  56052. fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :accounts
  56053. def test_find
  56054. assert_equal(topics(:first).title, Topic.find(1).title)
  56055. end
  56056. def test_exists
  56057. assert (Topic.exists?(1))
  56058. assert !(Topic.exists?(45))
  56059. assert !(Topic.exists?("foo"))
  56060. assert !(Topic.exists?([1,2]))
  56061. end
  56062. def test_find_by_array_of_one_id
  56063. assert_kind_of(Array, Topic.find([ 1 ]))
  56064. assert_equal(1, Topic.find([ 1 ]).length)
  56065. end
  56066. def test_find_by_ids
  56067. assert_equal(2, Topic.find(1, 2).length)
  56068. assert_equal(topics(:second).title, Topic.find([ 2 ]).first.title)
  56069. end
  56070. def test_find_an_empty_array
  56071. assert_equal [], Topic.find([])
  56072. end
  56073. def test_find_by_ids_missing_one
  56074. assert_raises(ActiveRecord::RecordNotFound) {
  56075. Topic.find(1, 2, 45)
  56076. }
  56077. end
  56078. def test_find_all_with_limit
  56079. entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
  56080. assert_equal(2, entrants.size)
  56081. assert_equal(entrants(:first).name, entrants.first.name)
  56082. end
  56083. def test_find_all_with_prepared_limit_and_offset
  56084. entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
  56085. assert_equal(2, entrants.size)
  56086. assert_equal(entrants(:second).name, entrants.first.name)
  56087. entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
  56088. assert_equal(1, entrants.size)
  56089. assert_equal(entrants(:third).name, entrants.first.name)
  56090. end
  56091. def test_find_all_with_limit_and_offset_and_multiple_orderings
  56092. developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
  56093. assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
  56094. end
  56095. def test_find_with_limit_and_condition
  56096. developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
  56097. assert_equal(1, developers.size)
  56098. assert_equal("fixture_3", developers.first.name)
  56099. end
  56100. def test_find_with_entire_select_statement
  56101. topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
  56102. assert_equal(1, topics.size)
  56103. assert_equal(topics(:second).title, topics.first.title)
  56104. end
  56105. def test_find_with_prepared_select_statement
  56106. topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
  56107. assert_equal(1, topics.size)
  56108. assert_equal(topics(:second).title, topics.first.title)
  56109. end
  56110. def test_find_first
  56111. first = Topic.find(:first, :conditions => "title = 'The First Topic'")
  56112. assert_equal(topics(:first).title, first.title)
  56113. end
  56114. def test_find_first_failing
  56115. first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
  56116. assert_nil(first)
  56117. end
  56118. def test_unexisting_record_exception_handling
  56119. assert_raises(ActiveRecord::RecordNotFound) {
  56120. Topic.find(1).parent
  56121. }
  56122. Topic.find(2).topic
  56123. end
  56124. def test_find_only_some_columns
  56125. topic = Topic.find(1, :select => "author_name")
  56126. assert_raises(NoMethodError) { topic.title }
  56127. assert_equal "David", topic.author_name
  56128. assert !topic.attribute_present?("title")
  56129. assert !topic.respond_to?("title")
  56130. assert topic.attribute_present?("author_name")
  56131. assert topic.respond_to?("author_name")
  56132. end
  56133. def test_find_on_conditions
  56134. assert Topic.find(1, :conditions => ["approved = ?", false])
  56135. assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
  56136. end
  56137. def test_condition_interpolation
  56138. assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
  56139. assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
  56140. assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
  56141. assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
  56142. end
  56143. def test_bind_variables
  56144. assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
  56145. assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
  56146. assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
  56147. assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
  56148. assert_raises(ActiveRecord::PreparedStatementInvalid) {
  56149. Company.find(:first, :conditions => ["id=? AND name = ?", 2])
  56150. }
  56151. assert_raises(ActiveRecord::PreparedStatementInvalid) {
  56152. Company.find(:first, :conditions => ["id=?", 2, 3, 4])
  56153. }
  56154. end
  56155. def test_bind_variables_with_quotes
  56156. Company.create("name" => "37signals' go'es agains")
  56157. assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
  56158. end
  56159. def test_named_bind_variables_with_quotes
  56160. Company.create("name" => "37signals' go'es agains")
  56161. assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
  56162. end
  56163. def test_bind_arity
  56164. assert_nothing_raised { bind '' }
  56165. assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
  56166. assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
  56167. assert_nothing_raised { bind '?', 1 }
  56168. assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
  56169. end
  56170. def test_named_bind_variables
  56171. assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
  56172. assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
  56173. assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
  56174. assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
  56175. assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
  56176. assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
  56177. end
  56178. def test_bind_enumerable
  56179. assert_equal '1,2,3', bind('?', [1, 2, 3])
  56180. assert_equal %('a','b','c'), bind('?', %w(a b c))
  56181. assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
  56182. assert_equal %('a','b','c'), bind(':a', :a => %w(a b c)) # '
  56183. require 'set'
  56184. assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
  56185. assert_equal %('a','b','c'), bind('?', Set.new(%w(a b c)))
  56186. assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
  56187. assert_equal %('a','b','c'), bind(':a', :a => Set.new(%w(a b c))) # '
  56188. end
  56189. def test_bind_string
  56190. assert_equal "''", bind('?', '')
  56191. end
  56192. def test_string_sanitation
  56193. assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
  56194. assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
  56195. end
  56196. def test_count
  56197. assert_equal(0, Entrant.count("id > 3"))
  56198. assert_equal(1, Entrant.count(["id > ?", 2]))
  56199. assert_equal(2, Entrant.count(["id > ?", 1]))
  56200. end
  56201. def test_count_by_sql
  56202. assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
  56203. assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
  56204. assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
  56205. end
  56206. def test_find_by_one_attribute
  56207. assert_equal topics(:first), Topic.find_by_title("The First Topic")
  56208. assert_nil Topic.find_by_title("The First Topic!")
  56209. end
  56210. def test_find_by_one_attribute_with_order_option
  56211. assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
  56212. assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
  56213. end
  56214. def test_find_by_one_attribute_with_conditions
  56215. assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
  56216. end
  56217. def test_find_by_one_attribute_with_several_options
  56218. assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
  56219. end
  56220. def test_find_by_one_missing_attribute
  56221. assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
  56222. end
  56223. def test_find_by_two_attributes
  56224. assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
  56225. assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  56226. end
  56227. def test_find_all_by_one_attribute
  56228. topics = Topic.find_all_by_content("Have a nice day")
  56229. assert_equal 2, topics.size
  56230. assert topics.include?(topics(:first))
  56231. assert_equal [], Topic.find_all_by_title("The First Topic!!")
  56232. end
  56233. def test_find_all_by_one_attribute_with_options
  56234. topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
  56235. assert topics(:first), topics.last
  56236. topics = Topic.find_all_by_content("Have a nice day", :order => "id")
  56237. assert topics(:first), topics.first
  56238. end
  56239. def test_find_all_by_array_attribute
  56240. assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic's of the day"]).size
  56241. end
  56242. def test_find_all_by_boolean_attribute
  56243. topics = Topic.find_all_by_approved(false)
  56244. assert_equal 1, topics.size
  56245. assert topics.include?(topics(:first))
  56246. topics = Topic.find_all_by_approved(true)
  56247. assert_equal 1, topics.size
  56248. assert topics.include?(topics(:second))
  56249. end
  56250. def test_find_by_nil_attribute
  56251. topic = Topic.find_by_last_read nil
  56252. assert_not_nil topic
  56253. assert_nil topic.last_read
  56254. end
  56255. def test_find_all_by_nil_attribute
  56256. topics = Topic.find_all_by_last_read nil
  56257. assert_equal 1, topics.size
  56258. assert_nil topics[0].last_read
  56259. end
  56260. def test_find_by_nil_and_not_nil_attributes
  56261. topic = Topic.find_by_last_read_and_author_name nil, "Mary"
  56262. assert_equal "Mary", topic.author_name
  56263. end
  56264. def test_find_all_by_nil_and_not_nil_attributes
  56265. topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
  56266. assert_equal 1, topics.size
  56267. assert_equal "Mary", topics[0].author_name
  56268. end
  56269. def test_find_or_create_from_one_attribute
  56270. number_of_companies = Company.count
  56271. sig38 = Company.find_or_create_by_name("38signals")
  56272. assert_equal number_of_companies + 1, Company.count
  56273. assert_equal sig38, Company.find_or_create_by_name("38signals")
  56274. end
  56275. def test_find_or_create_from_two_attributes
  56276. number_of_topics = Topic.count
  56277. another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
  56278. assert_equal number_of_topics + 1, Topic.count
  56279. assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
  56280. end
  56281. def test_find_with_bad_sql
  56282. assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
  56283. end
  56284. def test_find_with_invalid_params
  56285. assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
  56286. assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
  56287. end
  56288. def test_find_all_with_limit
  56289. first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5
  56290. assert_equal 5, first_five_developers.length
  56291. assert_equal 'David', first_five_developers.first.name
  56292. assert_equal 'fixture_5', first_five_developers.last.name
  56293. no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
  56294. assert_equal 0, no_developers.length
  56295. end
  56296. def test_find_all_with_limit_and_offset
  56297. first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
  56298. second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
  56299. last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
  56300. assert_equal 3, first_three_developers.length
  56301. assert_equal 3, second_three_developers.length
  56302. assert_equal 2, last_two_developers.length
  56303. assert_equal 'David', first_three_developers.first.name
  56304. assert_equal 'fixture_4', second_three_developers.first.name
  56305. assert_equal 'fixture_9', last_two_developers.first.name
  56306. end
  56307. def test_find_all_with_limit_and_offset_and_multiple_order_clauses
  56308. first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
  56309. second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
  56310. last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
  56311. assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
  56312. assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
  56313. assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
  56314. end
  56315. def test_find_all_with_join
  56316. developers_on_project_one = Developer.find(
  56317. :all,
  56318. :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
  56319. :conditions => 'project_id=1'
  56320. )
  56321. assert_equal 3, developers_on_project_one.length
  56322. developer_names = developers_on_project_one.map { |d| d.name }
  56323. assert developer_names.include?('David')
  56324. assert developer_names.include?('Jamis')
  56325. end
  56326. def test_find_by_id_with_conditions_with_or
  56327. assert_nothing_raised do
  56328. Post.find([1,2,3],
  56329. :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
  56330. end
  56331. end
  56332. def test_select_value
  56333. assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1")
  56334. assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1")
  56335. # make sure we didn't break count...
  56336. assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'")
  56337. assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'")
  56338. end
  56339. def test_select_values
  56340. assert_equal ["1","2","3","4","5","6","7","8"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
  56341. assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
  56342. end
  56343. protected
  56344. def bind(statement, *vars)
  56345. if vars.first.is_a?(Hash)
  56346. ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
  56347. else
  56348. ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
  56349. end
  56350. end
  56351. end
  56352. class Author < ActiveRecord::Base
  56353. has_many :posts
  56354. has_many :posts_with_comments, :include => :comments, :class_name => "Post"
  56355. has_many :posts_with_categories, :include => :categories, :class_name => "Post"
  56356. has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
  56357. has_many :comments, :through => :posts
  56358. has_many :funky_comments, :through => :posts, :source => :comments
  56359. has_many :special_posts, :class_name => "Post"
  56360. has_many :hello_posts, :class_name => "Post", :conditions=>"\#{aliased_table_name}.body = 'hello'"
  56361. has_many :nonexistent_posts, :class_name => "Post", :conditions=>"\#{aliased_table_name}.body = 'nonexistent'"
  56362. has_many :posts_with_callbacks, :class_name => "Post", :before_add => :log_before_adding,
  56363. :after_add => :log_after_adding,
  56364. :before_remove => :log_before_removing,
  56365. :after_remove => :log_after_removing
  56366. has_many :posts_with_proc_callbacks, :class_name => "Post",
  56367. :before_add => Proc.new {|o, r| o.post_log << "before_adding#{r.id}"},
  56368. :after_add => Proc.new {|o, r| o.post_log << "after_adding#{r.id}"},
  56369. :before_remove => Proc.new {|o, r| o.post_log << "before_removing#{r.id}"},
  56370. :after_remove => Proc.new {|o, r| o.post_log << "after_removing#{r.id}"}
  56371. has_many :posts_with_multiple_callbacks, :class_name => "Post",
  56372. :before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id}"}],
  56373. :after_add => [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id}"}]
  56374. has_many :unchangable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding
  56375. has_many :categorizations
  56376. has_many :categories, :through => :categorizations
  56377. has_many :nothings, :through => :kateggorisatons, :class_name => 'Category'
  56378. has_many :author_favorites
  56379. has_many :favorite_authors, :through => :author_favorites, :order => 'name'
  56380. has_many :tagging, :through => :posts # through polymorphic has_one
  56381. has_many :taggings, :through => :posts, :source => :taggings # through polymorphic has_many
  56382. has_many :tags, :through => :posts # through has_many :through
  56383. has_many :post_categories, :through => :posts, :source => :categories
  56384. belongs_to :author_address
  56385. attr_accessor :post_log
  56386. def after_initialize
  56387. @post_log = []
  56388. end
  56389. private
  56390. def log_before_adding(object)
  56391. @post_log << "before_adding#{object.id}"
  56392. end
  56393. def log_after_adding(object)
  56394. @post_log << "after_adding#{object.id}"
  56395. end
  56396. def log_before_removing(object)
  56397. @post_log << "before_removing#{object.id}"
  56398. end
  56399. def log_after_removing(object)
  56400. @post_log << "after_removing#{object.id}"
  56401. end
  56402. def raise_exception(object)
  56403. raise Exception.new("You can't add a post")
  56404. end
  56405. end
  56406. class AuthorAddress < ActiveRecord::Base
  56407. has_one :author
  56408. end
  56409. class AuthorFavorite < ActiveRecord::Base
  56410. belongs_to :author
  56411. belongs_to :favorite_author, :class_name => "Author", :foreign_key => 'favorite_author_id'
  56412. endclass AutoId < ActiveRecord::Base
  56413. def self.table_name () "auto_id_tests" end
  56414. def self.primary_key () "auto_id" end
  56415. end
  56416. class Binary < ActiveRecord::Base
  56417. endclass Categorization < ActiveRecord::Base
  56418. belongs_to :post
  56419. belongs_to :category
  56420. belongs_to :author
  56421. endclass Category < ActiveRecord::Base
  56422. has_and_belongs_to_many :posts
  56423. has_and_belongs_to_many :special_posts, :class_name => "Post"
  56424. has_and_belongs_to_many :hello_posts, :class_name => "Post", :conditions => "\#{aliased_table_name}.body = 'hello'"
  56425. has_and_belongs_to_many :nonexistent_posts, :class_name => "Post", :conditions=>"\#{aliased_table_name}.body = 'nonexistent'"
  56426. def self.what_are_you
  56427. 'a category...'
  56428. end
  56429. has_many :categorizations
  56430. has_many :authors, :through => :categorizations, :select => 'authors.*, categorizations.post_id'
  56431. end
  56432. class SpecialCategory < Category
  56433. def self.what_are_you
  56434. 'a special category...'
  56435. end
  56436. end
  56437. class ColumnName < ActiveRecord::Base
  56438. def self.table_name () "colnametests" end
  56439. endclass Comment < ActiveRecord::Base
  56440. belongs_to :post
  56441. def self.what_are_you
  56442. 'a comment...'
  56443. end
  56444. def self.search_by_type(q)
  56445. self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q])
  56446. end
  56447. end
  56448. class SpecialComment < Comment
  56449. def self.what_are_you
  56450. 'a special comment...'
  56451. end
  56452. end
  56453. class VerySpecialComment < Comment
  56454. def self.what_are_you
  56455. 'a very special comment...'
  56456. end
  56457. endclass Company < ActiveRecord::Base
  56458. attr_protected :rating
  56459. set_sequence_name :companies_nonstd_seq
  56460. validates_presence_of :name
  56461. has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account"
  56462. end
  56463. class Firm < Company
  56464. has_many :clients, :order => "id", :dependent => :destroy, :counter_sql =>
  56465. "SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
  56466. "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
  56467. has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
  56468. has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
  56469. has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
  56470. has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
  56471. has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1
  56472. has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
  56473. has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'
  56474. has_many :clients_using_counter_sql, :class_name => "Client",
  56475. :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}',
  56476. :counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = #{id}'
  56477. has_many :clients_using_zero_counter_sql, :class_name => "Client",
  56478. :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}',
  56479. :counter_sql => 'SELECT 0 FROM companies WHERE client_of = #{id}'
  56480. has_many :no_clients_using_counter_sql, :class_name => "Client",
  56481. :finder_sql => 'SELECT * FROM companies WHERE client_of = 1000',
  56482. :counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = 1000'
  56483. has_one :account, :foreign_key => "firm_id", :dependent => :destroy
  56484. end
  56485. class DependentFirm < Company
  56486. has_one :account, :foreign_key => "firm_id", :dependent => :nullify
  56487. has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify
  56488. end
  56489. class Client < Company
  56490. belongs_to :firm, :foreign_key => "client_of"
  56491. belongs_to :firm_with_basic_id, :class_name => "Firm", :foreign_key => "firm_id"
  56492. belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
  56493. belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => ["1 = ?", 1]
  56494. # Record destruction so we can test whether firm.clients.clear has
  56495. # is calling client.destroy, deleting from the database, or setting
  56496. # foreign keys to NULL.
  56497. def self.destroyed_client_ids
  56498. @destroyed_client_ids ||= Hash.new { |h,k| h[k] = [] }
  56499. end
  56500. before_destroy do |client|
  56501. if client.firm
  56502. Client.destroyed_client_ids[client.firm.id] << client.id
  56503. end
  56504. true
  56505. end
  56506. # Used to test that read and question methods are not generated for these attributes
  56507. def ruby_type
  56508. read_attribute :ruby_type
  56509. end
  56510. def rating?
  56511. query_attribute :rating
  56512. end
  56513. end
  56514. class SpecialClient < Client
  56515. end
  56516. class VerySpecialClient < SpecialClient
  56517. end
  56518. class Account < ActiveRecord::Base
  56519. belongs_to :firm
  56520. protected
  56521. def validate
  56522. errors.add_on_empty "credit_limit"
  56523. end
  56524. endmodule MyApplication
  56525. module Business
  56526. class Company < ActiveRecord::Base
  56527. attr_protected :rating
  56528. end
  56529. class Firm < Company
  56530. has_many :clients, :order => "id", :dependent => :destroy
  56531. has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
  56532. has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
  56533. has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
  56534. has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'
  56535. has_one :account, :dependent => :destroy
  56536. end
  56537. class Client < Company
  56538. belongs_to :firm, :foreign_key => "client_of"
  56539. belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
  56540. end
  56541. class Developer < ActiveRecord::Base
  56542. has_and_belongs_to_many :projects
  56543. protected
  56544. def validate
  56545. errors.add_on_boundary_breaking("name", 3..20)
  56546. end
  56547. end
  56548. class Project < ActiveRecord::Base
  56549. has_and_belongs_to_many :developers
  56550. end
  56551. end
  56552. module Billing
  56553. class Firm < ActiveRecord::Base
  56554. self.table_name = 'companies'
  56555. end
  56556. module Nested
  56557. class Firm < ActiveRecord::Base
  56558. self.table_name = 'companies'
  56559. end
  56560. end
  56561. class Account < ActiveRecord::Base
  56562. belongs_to :firm, :class_name => 'MyApplication::Business::Firm'
  56563. belongs_to :qualified_billing_firm, :class_name => 'MyApplication::Billing::Firm'
  56564. belongs_to :unqualified_billing_firm, :class_name => 'Firm'
  56565. belongs_to :nested_qualified_billing_firm, :class_name => 'MyApplication::Billing::Nested::Firm'
  56566. belongs_to :nested_unqualified_billing_firm, :class_name => 'Nested::Firm'
  56567. protected
  56568. def validate
  56569. errors.add_on_empty "credit_limit"
  56570. end
  56571. end
  56572. end
  56573. end
  56574. class Computer < ActiveRecord::Base
  56575. belongs_to :developer, :foreign_key=>'developer'
  56576. end
  56577. class Course < ActiveRecord::Base
  56578. has_many :entrants
  56579. end
  56580. class Customer < ActiveRecord::Base
  56581. composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ]
  56582. composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
  56583. composed_of :gps_location
  56584. end
  56585. class Address
  56586. attr_reader :street, :city, :country
  56587. def initialize(street, city, country)
  56588. @street, @city, @country = street, city, country
  56589. end
  56590. def close_to?(other_address)
  56591. city == other_address.city && country == other_address.country
  56592. end
  56593. def ==(other)
  56594. other.is_a?(self.class) && other.street == street && other.city == city && other.country == country
  56595. end
  56596. end
  56597. class Money
  56598. attr_reader :amount, :currency
  56599. EXCHANGE_RATES = { "USD_TO_DKK" => 6, "DKK_TO_USD" => 0.6 }
  56600. def initialize(amount, currency = "USD")
  56601. @amount, @currency = amount, currency
  56602. end
  56603. def exchange_to(other_currency)
  56604. Money.new((amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor, other_currency)
  56605. end
  56606. end
  56607. class GpsLocation
  56608. attr_reader :gps_location
  56609. def initialize(gps_location)
  56610. @gps_location = gps_location
  56611. end
  56612. def latitude
  56613. gps_location.split("x").first
  56614. end
  56615. def longitude
  56616. gps_location.split("x").last
  56617. end
  56618. def ==(other)
  56619. self.latitude == other.latitude && self.longitude == other.longitude
  56620. end
  56621. endActiveRecord::Schema.define do
  56622. create_table :taggings, :force => true do |t|
  56623. t.column :tag_id, :integer
  56624. t.column :super_tag_id, :integer
  56625. t.column :taggable_type, :string
  56626. t.column :taggable_id, :integer
  56627. end
  56628. create_table :tags, :force => true do |t|
  56629. t.column :name, :string
  56630. t.column :taggings_count, :integer, :default => 0
  56631. end
  56632. create_table :categorizations, :force => true do |t|
  56633. t.column :category_id, :integer
  56634. t.column :post_id, :integer
  56635. t.column :author_id, :integer
  56636. end
  56637. add_column :posts, :taggings_count, :integer, :default => 0
  56638. add_column :authors, :author_address_id, :integer
  56639. create_table :author_addresses, :force => true do |t|
  56640. t.column :author_address_id, :integer
  56641. end
  56642. create_table :author_favorites, :force => true do |t|
  56643. t.column :author_id, :integer
  56644. t.column :favorite_author_id, :integer
  56645. end
  56646. endclass Default < ActiveRecord::Base
  56647. end
  56648. module DeveloperProjectsAssociationExtension
  56649. def find_most_recent
  56650. find(:first, :order => "id DESC")
  56651. end
  56652. end
  56653. class Developer < ActiveRecord::Base
  56654. has_and_belongs_to_many :projects do
  56655. def find_most_recent
  56656. find(:first, :order => "id DESC")
  56657. end
  56658. end
  56659. has_and_belongs_to_many :projects_extended_by_name,
  56660. :class_name => "Project",
  56661. :join_table => "developers_projects",
  56662. :association_foreign_key => "project_id",
  56663. :extend => DeveloperProjectsAssociationExtension
  56664. has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'
  56665. validates_inclusion_of :salary, :in => 50000..200000
  56666. validates_length_of :name, :within => 3..20
  56667. end
  56668. DeveloperSalary = Struct.new(:amount)
  56669. class DeveloperWithAggregate < ActiveRecord::Base
  56670. self.table_name = 'developers'
  56671. composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)]
  56672. end
  56673. class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
  56674. self.table_name = 'developers'
  56675. has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id'
  56676. before_destroy :raise_if_projects_empty!
  56677. def raise_if_projects_empty!
  56678. raise if projects.empty?
  56679. end
  56680. end
  56681. class Entrant < ActiveRecord::Base
  56682. belongs_to :course
  56683. end
  56684. class Joke < ActiveRecord::Base
  56685. set_table_name 'funny_jokes'
  56686. end
  56687. class Joke < ActiveRecord::Base
  56688. set_table_name 'funny_jokes'
  56689. endclass Keyboard < ActiveRecord::Base
  56690. set_primary_key 'key_number'
  56691. end
  56692. class LegacyThing < ActiveRecord::Base
  56693. set_locking_column :version
  56694. end
  56695. class PeopleHaveLastNames < ActiveRecord::Migration
  56696. def self.up
  56697. add_column "people", "last_name", :string
  56698. end
  56699. def self.down
  56700. remove_column "people", "last_name"
  56701. end
  56702. endclass WeNeedReminders < ActiveRecord::Migration
  56703. def self.up
  56704. create_table("reminders") do |t|
  56705. t.column :content, :text
  56706. t.column :remind_at, :datetime
  56707. end
  56708. end
  56709. def self.down
  56710. drop_table "reminders"
  56711. end
  56712. endclass InnocentJointable < ActiveRecord::Migration
  56713. def self.up
  56714. create_table("people_reminders", :id => false) do |t|
  56715. t.column :reminder_id, :integer
  56716. t.column :person_id, :integer
  56717. end
  56718. end
  56719. def self.down
  56720. drop_table "people_reminders"
  56721. end
  56722. endclass PeopleHaveLastNames < ActiveRecord::Migration
  56723. def self.up
  56724. add_column "people", "last_name", :string
  56725. end
  56726. def self.down
  56727. remove_column "people", "last_name"
  56728. end
  56729. endclass WeNeedReminders < ActiveRecord::Migration
  56730. def self.up
  56731. create_table("reminders") do |t|
  56732. t.column :content, :text
  56733. t.column :remind_at, :datetime
  56734. end
  56735. end
  56736. def self.down
  56737. drop_table "reminders"
  56738. end
  56739. endclass Foo < ActiveRecord::Migration
  56740. def self.up
  56741. end
  56742. def self.down
  56743. end
  56744. endclass InnocentJointable < ActiveRecord::Migration
  56745. def self.up
  56746. create_table("people_reminders", :id => false) do |t|
  56747. t.column :reminder_id, :integer
  56748. t.column :person_id, :integer
  56749. end
  56750. end
  56751. def self.down
  56752. drop_table "people_reminders"
  56753. end
  56754. endclass Mixin < ActiveRecord::Base
  56755. end
  56756. class TreeMixin < Mixin
  56757. acts_as_tree :foreign_key => "parent_id", :order => "id"
  56758. end
  56759. class TreeMixinWithoutOrder < Mixin
  56760. acts_as_tree :foreign_key => "parent_id"
  56761. end
  56762. class ListMixin < Mixin
  56763. acts_as_list :column => "pos", :scope => :parent
  56764. def self.table_name() "mixins" end
  56765. end
  56766. class ListMixinSub1 < ListMixin
  56767. end
  56768. class ListMixinSub2 < ListMixin
  56769. end
  56770. class ListWithStringScopeMixin < ActiveRecord::Base
  56771. acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
  56772. def self.table_name() "mixins" end
  56773. end
  56774. class NestedSet < Mixin
  56775. acts_as_nested_set :scope => "root_id IS NULL"
  56776. def self.table_name() "mixins" end
  56777. end
  56778. class NestedSetWithStringScope < Mixin
  56779. acts_as_nested_set :scope => 'root_id = #{root_id}'
  56780. def self.table_name() "mixins" end
  56781. end
  56782. class NestedSetWithSymbolScope < Mixin
  56783. acts_as_nested_set :scope => :root
  56784. def self.table_name() "mixins" end
  56785. end
  56786. class Movie < ActiveRecord::Base
  56787. def self.primary_key
  56788. "movieid"
  56789. end
  56790. end
  56791. class Order < ActiveRecord::Base
  56792. belongs_to :billing, :class_name => 'Customer', :foreign_key => 'billing_customer_id'
  56793. belongs_to :shipping, :class_name => 'Customer', :foreign_key => 'shipping_customer_id'
  56794. end
  56795. class Person < ActiveRecord::Base
  56796. has_many :readers
  56797. has_many :posts, :through => :readers
  56798. end
  56799. class Post < ActiveRecord::Base
  56800. belongs_to :author do
  56801. def greeting
  56802. "hello"
  56803. end
  56804. end
  56805. belongs_to :author_with_posts, :class_name => "Author", :include => :posts
  56806. has_many :comments, :order => "body" do
  56807. def find_most_recent
  56808. find(:first, :order => "id DESC")
  56809. end
  56810. end
  56811. has_one :very_special_comment
  56812. has_one :very_special_comment_with_post, :class_name => "VerySpecialComment", :include => :post
  56813. has_many :special_comments
  56814. has_and_belongs_to_many :categories
  56815. has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id'
  56816. has_many :taggings, :as => :taggable
  56817. has_many :tags, :through => :taggings, :include => :tagging do
  56818. def add_joins_and_select
  56819. find :all, :select => 'tags.*, authors.id as author_id', :include => false,
  56820. :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
  56821. end
  56822. end
  56823. has_many :funky_tags, :through => :taggings, :source => :tag
  56824. has_many :super_tags, :through => :taggings
  56825. has_one :tagging, :as => :taggable
  56826. has_many :invalid_taggings, :as => :taggable, :class_name => "Tagging", :conditions => 'taggings.id < 0'
  56827. has_many :invalid_tags, :through => :invalid_taggings, :source => :tag
  56828. has_many :categorizations, :foreign_key => :category_id
  56829. has_many :authors, :through => :categorizations
  56830. has_many :readers
  56831. has_many :people, :through => :readers
  56832. def self.what_are_you
  56833. 'a post...'
  56834. end
  56835. end
  56836. class SpecialPost < Post; end;
  56837. class StiPost < Post
  56838. self.abstract_class = true
  56839. has_one :special_comment, :class_name => "SpecialComment"
  56840. end
  56841. class SubStiPost < StiPost
  56842. end
  56843. class Project < ActiveRecord::Base
  56844. has_and_belongs_to_many :developers, :uniq => true
  56845. has_and_belongs_to_many :limited_developers, :class_name => "Developer", :limit => 1
  56846. has_and_belongs_to_many :developers_named_david, :class_name => "Developer", :conditions => "name = 'David'", :uniq => true
  56847. has_and_belongs_to_many :salaried_developers, :class_name => "Developer", :conditions => "salary > 0"
  56848. has_and_belongs_to_many :developers_with_finder_sql, :class_name => "Developer", :finder_sql => 'SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id}'
  56849. has_and_belongs_to_many :developers_by_sql, :class_name => "Developer", :delete_sql => "DELETE FROM developers_projects WHERE project_id = \#{id} AND developer_id = \#{record.id}"
  56850. has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => Proc.new {|o, r| o.developers_log << "before_adding#{r.id}"},
  56851. :after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id}"},
  56852. :before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
  56853. :after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}
  56854. attr_accessor :developers_log
  56855. def after_initialize
  56856. @developers_log = []
  56857. end
  56858. end
  56859. class SpecialProject < Project
  56860. def hello_world
  56861. "hello there!"
  56862. end
  56863. end
  56864. class Reader < ActiveRecord::Base
  56865. belongs_to :post
  56866. belongs_to :person
  56867. end
  56868. require 'fixtures/topic'
  56869. class Reply < Topic
  56870. belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
  56871. has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"
  56872. validate :errors_on_empty_content
  56873. validate_on_create :title_is_wrong_create
  56874. attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
  56875. def validate
  56876. errors.add("title", "Empty") unless attribute_present? "title"
  56877. end
  56878. def errors_on_empty_content
  56879. errors.add("content", "Empty") unless attribute_present? "content"
  56880. end
  56881. def validate_on_create
  56882. if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
  56883. errors.add("title", "is Content Mismatch")
  56884. end
  56885. end
  56886. def title_is_wrong_create
  56887. errors.add("title", "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
  56888. end
  56889. def validate_on_update
  56890. errors.add("title", "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
  56891. end
  56892. end
  56893. class SillyReply < Reply
  56894. belongs_to :reply, :foreign_key => "parent_id", :counter_cache => :replies_count
  56895. end
  56896. # used for OracleSynonymTest, see test/synonym_test_oci.rb
  56897. #
  56898. class Subject < ActiveRecord::Base
  56899. end
  56900. class Subscriber < ActiveRecord::Base
  56901. set_primary_key 'nick'
  56902. end
  56903. class SpecialSubscriber < Subscriber
  56904. end
  56905. class Tag < ActiveRecord::Base
  56906. has_many :taggings
  56907. has_many :taggables, :through => :taggings
  56908. has_one :tagging
  56909. endclass Tagging < ActiveRecord::Base
  56910. belongs_to :tag, :include => :tagging
  56911. belongs_to :super_tag, :class_name => 'Tag', :foreign_key => 'super_tag_id'
  56912. belongs_to :invalid_tag, :class_name => 'Tag', :foreign_key => 'tag_id'
  56913. belongs_to :taggable, :polymorphic => true, :counter_cache => true
  56914. endclass Task < ActiveRecord::Base
  56915. end
  56916. class Topic < ActiveRecord::Base
  56917. has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
  56918. serialize :content
  56919. before_create :default_written_on
  56920. before_destroy :destroy_children
  56921. def parent
  56922. Topic.find(parent_id)
  56923. end
  56924. protected
  56925. def default_written_on
  56926. self.written_on = Time.now unless attribute_present?("written_on")
  56927. end
  56928. def destroy_children
  56929. self.class.delete_all "parent_id = #{id}"
  56930. end
  56931. endrequire 'abstract_unit'
  56932. require 'fixtures/topic'
  56933. require 'fixtures/developer'
  56934. require 'fixtures/company'
  56935. require 'fixtures/task'
  56936. require 'fixtures/reply'
  56937. require 'fixtures/joke'
  56938. require 'fixtures/category'
  56939. class FixturesTest < Test::Unit::TestCase
  56940. self.use_instantiated_fixtures = true
  56941. self.use_transactional_fixtures = false
  56942. fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes
  56943. FIXTURES = %w( accounts companies customers
  56944. developers developers_projects entrants
  56945. movies projects subscribers topics tasks )
  56946. MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-_\w]*/
  56947. def test_clean_fixtures
  56948. FIXTURES.each do |name|
  56949. fixtures = nil
  56950. assert_nothing_raised { fixtures = create_fixtures(name) }
  56951. assert_kind_of(Fixtures, fixtures)
  56952. fixtures.each { |name, fixture|
  56953. fixture.each { |key, value|
  56954. assert_match(MATCH_ATTRIBUTE_NAME, key)
  56955. }
  56956. }
  56957. end
  56958. end
  56959. def test_multiple_clean_fixtures
  56960. fixtures_array = nil
  56961. assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
  56962. assert_kind_of(Array, fixtures_array)
  56963. fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) }
  56964. end
  56965. def test_attributes
  56966. topics = create_fixtures("topics")
  56967. assert_equal("The First Topic", topics["first"]["title"])
  56968. assert_nil(topics["second"]["author_email_address"])
  56969. end
  56970. def test_inserts
  56971. topics = create_fixtures("topics")
  56972. firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
  56973. assert_equal("The First Topic", firstRow["title"])
  56974. secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'Mary'")
  56975. assert_nil(secondRow["author_email_address"])
  56976. end
  56977. if ActiveRecord::Base.connection.supports_migrations?
  56978. def test_inserts_with_pre_and_suffix
  56979. ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
  56980. t.column :title, :string
  56981. t.column :author_name, :string
  56982. t.column :author_email_address, :string
  56983. t.column :written_on, :datetime
  56984. t.column :bonus_time, :time
  56985. t.column :last_read, :date
  56986. t.column :content, :string
  56987. t.column :approved, :boolean, :default => true
  56988. t.column :replies_count, :integer, :default => 0
  56989. t.column :parent_id, :integer
  56990. t.column :type, :string, :limit => 50
  56991. end
  56992. # Store existing prefix/suffix
  56993. old_prefix = ActiveRecord::Base.table_name_prefix
  56994. old_suffix = ActiveRecord::Base.table_name_suffix
  56995. # Set a prefix/suffix we can test against
  56996. ActiveRecord::Base.table_name_prefix = 'prefix_'
  56997. ActiveRecord::Base.table_name_suffix = '_suffix'
  56998. topics = create_fixtures("topics")
  56999. firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'")
  57000. assert_equal("The First Topic", firstRow["title"])
  57001. secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'")
  57002. assert_nil(secondRow["author_email_address"])
  57003. ensure
  57004. # Restore prefix/suffix to its previous values
  57005. ActiveRecord::Base.table_name_prefix = old_prefix
  57006. ActiveRecord::Base.table_name_suffix = old_suffix
  57007. ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil
  57008. end
  57009. end
  57010. def test_insert_with_datetime
  57011. topics = create_fixtures("tasks")
  57012. first = Task.find(1)
  57013. assert first
  57014. end
  57015. def test_bad_format
  57016. path = File.join(File.dirname(__FILE__), 'fixtures', 'bad_fixtures')
  57017. Dir.entries(path).each do |file|
  57018. next unless File.file?(file) and file !~ Fixtures::DEFAULT_FILTER_RE
  57019. assert_raise(Fixture::FormatError) {
  57020. Fixture.new(bad_fixtures_path, file)
  57021. }
  57022. end
  57023. end
  57024. def test_deprecated_yaml_extension
  57025. assert_raise(Fixture::FormatError) {
  57026. Fixtures.new(nil, 'bad_extension', 'BadExtension', File.join(File.dirname(__FILE__), 'fixtures'))
  57027. }
  57028. end
  57029. def test_logger_level_invariant
  57030. level = ActiveRecord::Base.logger.level
  57031. create_fixtures('topics')
  57032. assert_equal level, ActiveRecord::Base.logger.level
  57033. end
  57034. def test_instantiation
  57035. topics = create_fixtures("topics")
  57036. assert_kind_of Topic, topics["first"].find
  57037. end
  57038. def test_complete_instantiation
  57039. assert_equal 2, @topics.size
  57040. assert_equal "The First Topic", @first.title
  57041. end
  57042. def test_fixtures_from_root_yml_with_instantiation
  57043. # assert_equal 2, @accounts.size
  57044. assert_equal 50, @unknown.credit_limit
  57045. end
  57046. def test_erb_in_fixtures
  57047. assert_equal 11, @developers.size
  57048. assert_equal "fixture_5", @dev_5.name
  57049. end
  57050. def test_empty_yaml_fixture
  57051. assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', File.dirname(__FILE__) + "/fixtures/naked/yml/accounts")
  57052. end
  57053. def test_empty_yaml_fixture_with_a_comment_in_it
  57054. assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', File.dirname(__FILE__) + "/fixtures/naked/yml/companies")
  57055. end
  57056. def test_dirty_dirty_yaml_file
  57057. assert_raises(Fixture::FormatError) do
  57058. Fixtures.new( Account.connection, "courses", 'Course', File.dirname(__FILE__) + "/fixtures/naked/yml/courses")
  57059. end
  57060. end
  57061. def test_empty_csv_fixtures
  57062. assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', File.dirname(__FILE__) + "/fixtures/naked/csv/accounts")
  57063. end
  57064. def test_omap_fixtures
  57065. assert_nothing_raised do
  57066. fixtures = Fixtures.new(Account.connection, 'categories', 'Category', File.dirname(__FILE__) + '/fixtures/categories_ordered')
  57067. i = 0
  57068. fixtures.each do |name, fixture|
  57069. assert_equal "fixture_no_#{i}", name
  57070. assert_equal "Category #{i}", fixture['name']
  57071. i += 1
  57072. end
  57073. end
  57074. end
  57075. def test_yml_file_in_subdirectory
  57076. assert_equal(categories(:sub_special_1).name, "A special category in a subdir file")
  57077. assert_equal(categories(:sub_special_1).class, SpecialCategory)
  57078. end
  57079. def test_subsubdir_file_with_arbitrary_name
  57080. assert_equal(categories(:sub_special_3).name, "A special category in an arbitrarily named subsubdir file")
  57081. assert_equal(categories(:sub_special_3).class, SpecialCategory)
  57082. end
  57083. end
  57084. if Account.connection.respond_to?(:reset_pk_sequence!)
  57085. class FixturesResetPkSequenceTest < Test::Unit::TestCase
  57086. fixtures :accounts
  57087. fixtures :companies
  57088. def setup
  57089. @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
  57090. end
  57091. def test_resets_to_min_pk_with_specified_pk_and_sequence
  57092. @instances.each do |instance|
  57093. model = instance.class
  57094. model.delete_all
  57095. model.connection.reset_pk_sequence!(model.table_name, model.primary_key, model.sequence_name)
  57096. instance.save!
  57097. assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed."
  57098. end
  57099. end
  57100. def test_resets_to_min_pk_with_default_pk_and_sequence
  57101. @instances.each do |instance|
  57102. model = instance.class
  57103. model.delete_all
  57104. model.connection.reset_pk_sequence!(model.table_name)
  57105. instance.save!
  57106. assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed."
  57107. end
  57108. end
  57109. def test_create_fixtures_resets_sequences
  57110. @instances.each do |instance|
  57111. max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)|
  57112. fixture_id = fixture['id'].to_i
  57113. fixture_id > max_id ? fixture_id : max_id
  57114. end
  57115. # Clone the last fixture to check that it gets the next greatest id.
  57116. instance.save!
  57117. assert_equal max_id + 1, instance.id, "Sequence reset for #{instance.class.table_name} failed."
  57118. end
  57119. end
  57120. end
  57121. end
  57122. class FixturesWithoutInstantiationTest < Test::Unit::TestCase
  57123. self.use_instantiated_fixtures = false
  57124. fixtures :topics, :developers, :accounts
  57125. def test_without_complete_instantiation
  57126. assert_nil @first
  57127. assert_nil @topics
  57128. assert_nil @developers
  57129. assert_nil @accounts
  57130. end
  57131. def test_fixtures_from_root_yml_without_instantiation
  57132. assert_nil @unknown
  57133. end
  57134. def test_accessor_methods
  57135. assert_equal "The First Topic", topics(:first).title
  57136. assert_equal "Jamis", developers(:jamis).name
  57137. assert_equal 50, accounts(:signals37).credit_limit
  57138. end
  57139. end
  57140. class FixturesWithoutInstanceInstantiationTest < Test::Unit::TestCase
  57141. self.use_instantiated_fixtures = true
  57142. self.use_instantiated_fixtures = :no_instances
  57143. fixtures :topics, :developers, :accounts
  57144. def test_without_instance_instantiation
  57145. assert_nil @first
  57146. assert_not_nil @topics
  57147. assert_not_nil @developers
  57148. assert_not_nil @accounts
  57149. end
  57150. end
  57151. class TransactionalFixturesTest < Test::Unit::TestCase
  57152. self.use_instantiated_fixtures = true
  57153. self.use_transactional_fixtures = true
  57154. fixtures :topics
  57155. def test_destroy
  57156. assert_not_nil @first
  57157. @first.destroy
  57158. end
  57159. def test_destroy_just_kidding
  57160. assert_not_nil @first
  57161. end
  57162. end
  57163. class MultipleFixturesTest < Test::Unit::TestCase
  57164. fixtures :topics
  57165. fixtures :developers, :accounts
  57166. def test_fixture_table_names
  57167. assert_equal %w(topics developers accounts), fixture_table_names
  57168. end
  57169. end
  57170. class OverlappingFixturesTest < Test::Unit::TestCase
  57171. fixtures :topics, :developers
  57172. fixtures :developers, :accounts
  57173. def test_fixture_table_names
  57174. assert_equal %w(topics developers accounts), fixture_table_names
  57175. end
  57176. end
  57177. class ForeignKeyFixturesTest < Test::Unit::TestCase
  57178. fixtures :fk_test_has_pk, :fk_test_has_fk
  57179. # if foreign keys are implemented and fixtures
  57180. # are not deleted in reverse order then this test
  57181. # case will raise StatementInvalid
  57182. def test_number1
  57183. assert true
  57184. end
  57185. def test_number2
  57186. assert true
  57187. end
  57188. end
  57189. class SetTableNameFixturesTest < Test::Unit::TestCase
  57190. set_fixture_class :funny_jokes => 'Joke'
  57191. fixtures :funny_jokes
  57192. def test_table_method
  57193. assert_kind_of Joke, funny_jokes(:a_joke)
  57194. end
  57195. end
  57196. class InvalidTableNameFixturesTest < Test::Unit::TestCase
  57197. fixtures :funny_jokes
  57198. def test_raises_error
  57199. assert_raises FixtureClassNotFound do
  57200. funny_jokes(:a_joke)
  57201. end
  57202. end
  57203. end
  57204. require 'abstract_unit'
  57205. require 'fixtures/company'
  57206. require 'fixtures/project'
  57207. require 'fixtures/subscriber'
  57208. class InheritanceTest < Test::Unit::TestCase
  57209. fixtures :companies, :projects, :subscribers
  57210. def test_a_bad_type_column
  57211. #SQLServer need to turn Identity Insert On before manually inserting into the Identity column
  57212. if current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter)
  57213. Company.connection.execute "SET IDENTITY_INSERT companies ON"
  57214. end
  57215. Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
  57216. #We then need to turn it back Off before continuing.
  57217. if current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter)
  57218. Company.connection.execute "SET IDENTITY_INSERT companies OFF"
  57219. end
  57220. assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) }
  57221. end
  57222. def test_inheritance_find
  57223. assert Company.find(1).kind_of?(Firm), "37signals should be a firm"
  57224. assert Firm.find(1).kind_of?(Firm), "37signals should be a firm"
  57225. assert Company.find(2).kind_of?(Client), "Summit should be a client"
  57226. assert Client.find(2).kind_of?(Client), "Summit should be a client"
  57227. end
  57228. def test_alt_inheritance_find
  57229. switch_to_alt_inheritance_column
  57230. test_inheritance_find
  57231. end
  57232. def test_inheritance_find_all
  57233. companies = Company.find(:all, :order => 'id')
  57234. assert companies[0].kind_of?(Firm), "37signals should be a firm"
  57235. assert companies[1].kind_of?(Client), "Summit should be a client"
  57236. end
  57237. def test_alt_inheritance_find_all
  57238. switch_to_alt_inheritance_column
  57239. test_inheritance_find_all
  57240. end
  57241. def test_inheritance_save
  57242. firm = Firm.new
  57243. firm.name = "Next Angle"
  57244. firm.save
  57245. next_angle = Company.find(firm.id)
  57246. assert next_angle.kind_of?(Firm), "Next Angle should be a firm"
  57247. end
  57248. def test_alt_inheritance_save
  57249. switch_to_alt_inheritance_column
  57250. test_inheritance_save
  57251. end
  57252. def test_inheritance_condition
  57253. assert_equal 8, Company.count
  57254. assert_equal 2, Firm.count
  57255. assert_equal 3, Client.count
  57256. end
  57257. def test_alt_inheritance_condition
  57258. switch_to_alt_inheritance_column
  57259. test_inheritance_condition
  57260. end
  57261. def test_finding_incorrect_type_data
  57262. assert_raises(ActiveRecord::RecordNotFound) { Firm.find(2) }
  57263. assert_nothing_raised { Firm.find(1) }
  57264. end
  57265. def test_alt_finding_incorrect_type_data
  57266. switch_to_alt_inheritance_column
  57267. test_finding_incorrect_type_data
  57268. end
  57269. def test_update_all_within_inheritance
  57270. Client.update_all "name = 'I am a client'"
  57271. assert_equal "I am a client", Client.find(:all).first.name
  57272. assert_equal "37signals", Firm.find(:all).first.name
  57273. end
  57274. def test_alt_update_all_within_inheritance
  57275. switch_to_alt_inheritance_column
  57276. test_update_all_within_inheritance
  57277. end
  57278. def test_destroy_all_within_inheritance
  57279. Client.destroy_all
  57280. assert_equal 0, Client.count
  57281. assert_equal 2, Firm.count
  57282. end
  57283. def test_alt_destroy_all_within_inheritance
  57284. switch_to_alt_inheritance_column
  57285. test_destroy_all_within_inheritance
  57286. end
  57287. def test_find_first_within_inheritance
  57288. assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'")
  57289. assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'")
  57290. assert_nil Client.find(:first, :conditions => "name = '37signals'")
  57291. end
  57292. def test_alt_find_first_within_inheritance
  57293. switch_to_alt_inheritance_column
  57294. test_find_first_within_inheritance
  57295. end
  57296. def test_complex_inheritance
  57297. very_special_client = VerySpecialClient.create("name" => "veryspecial")
  57298. assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'")
  57299. assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
  57300. assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
  57301. assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
  57302. assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size
  57303. assert_equal very_special_client, Client.find(very_special_client.id)
  57304. end
  57305. def test_alt_complex_inheritance
  57306. switch_to_alt_inheritance_column
  57307. test_complex_inheritance
  57308. end
  57309. def test_inheritance_without_mapping
  57310. assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132")
  57311. assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save }
  57312. end
  57313. private
  57314. def switch_to_alt_inheritance_column
  57315. # we don't want misleading test results, so get rid of the values in the type column
  57316. Company.find(:all, :order => 'id').each do |c|
  57317. c['type'] = nil
  57318. c.save
  57319. end
  57320. def Company.inheritance_column() "ruby_type" end
  57321. end
  57322. end
  57323. require 'abstract_unit'
  57324. require 'fixtures/topic'
  57325. require 'fixtures/developer'
  57326. require 'fixtures/reply'
  57327. class Topic; def after_find() end end
  57328. class Developer; def after_find() end end
  57329. class SpecialDeveloper < Developer; end
  57330. class TopicManualObserver
  57331. include Singleton
  57332. attr_reader :action, :object, :callbacks
  57333. def initialize
  57334. Topic.add_observer(self)
  57335. @callbacks = []
  57336. end
  57337. def update(callback_method, object)
  57338. @callbacks << { "callback_method" => callback_method, "object" => object }
  57339. end
  57340. def has_been_notified?
  57341. !@callbacks.empty?
  57342. end
  57343. end
  57344. class TopicaObserver < ActiveRecord::Observer
  57345. def self.observed_class() Topic end
  57346. attr_reader :topic
  57347. def after_find(topic)
  57348. @topic = topic
  57349. end
  57350. end
  57351. class TopicObserver < ActiveRecord::Observer
  57352. attr_reader :topic
  57353. def after_find(topic)
  57354. @topic = topic
  57355. end
  57356. end
  57357. class MultiObserver < ActiveRecord::Observer
  57358. attr_reader :record
  57359. def self.observed_class() [ Topic, Developer ] end
  57360. def after_find(record)
  57361. @record = record
  57362. end
  57363. end
  57364. class LifecycleTest < Test::Unit::TestCase
  57365. fixtures :topics, :developers
  57366. def test_before_destroy
  57367. assert_equal 2, Topic.count
  57368. Topic.find(1).destroy
  57369. assert_equal 0, Topic.count
  57370. end
  57371. def test_after_save
  57372. ActiveRecord::Base.observers = :topic_manual_observer
  57373. topic = Topic.find(1)
  57374. topic.title = "hello"
  57375. topic.save
  57376. assert TopicManualObserver.instance.has_been_notified?
  57377. assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"]
  57378. end
  57379. def test_observer_update_on_save
  57380. ActiveRecord::Base.observers = TopicManualObserver
  57381. topic = Topic.find(1)
  57382. assert TopicManualObserver.instance.has_been_notified?
  57383. assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"]
  57384. end
  57385. def test_auto_observer
  57386. topic_observer = TopicaObserver.instance
  57387. topic = Topic.find(1)
  57388. assert_equal topic_observer.topic.title, topic.title
  57389. end
  57390. def test_infered_auto_observer
  57391. topic_observer = TopicObserver.instance
  57392. topic = Topic.find(1)
  57393. assert_equal topic_observer.topic.title, topic.title
  57394. end
  57395. def test_observing_two_classes
  57396. multi_observer = MultiObserver.instance
  57397. topic = Topic.find(1)
  57398. assert_equal multi_observer.record.title, topic.title
  57399. developer = Developer.find(1)
  57400. assert_equal multi_observer.record.name, developer.name
  57401. end
  57402. def test_observing_subclasses
  57403. multi_observer = MultiObserver.instance
  57404. developer = SpecialDeveloper.find(1)
  57405. assert_equal multi_observer.record.name, developer.name
  57406. end
  57407. end
  57408. require 'abstract_unit'
  57409. require 'fixtures/person'
  57410. require 'fixtures/legacy_thing'
  57411. class LockingTest < Test::Unit::TestCase
  57412. fixtures :people, :legacy_things
  57413. def test_lock_existing
  57414. p1 = Person.find(1)
  57415. p2 = Person.find(1)
  57416. p1.first_name = "Michael"
  57417. p1.save
  57418. assert_raises(ActiveRecord::StaleObjectError) {
  57419. p2.first_name = "should fail"
  57420. p2.save
  57421. }
  57422. end
  57423. def test_lock_new
  57424. p1 = Person.create({ "first_name"=>"anika"})
  57425. p2 = Person.find(p1.id)
  57426. assert_equal p1.id, p2.id
  57427. p1.first_name = "Anika"
  57428. p1.save
  57429. assert_raises(ActiveRecord::StaleObjectError) {
  57430. p2.first_name = "should fail"
  57431. p2.save
  57432. }
  57433. end
  57434. def test_lock_column_name_existing
  57435. t1 = LegacyThing.find(1)
  57436. t2 = LegacyThing.find(1)
  57437. t1.tps_report_number = 400
  57438. t1.save
  57439. assert_raises(ActiveRecord::StaleObjectError) {
  57440. t2.tps_report_number = 300
  57441. t2.save
  57442. }
  57443. end
  57444. end
  57445. require 'abstract_unit'
  57446. require 'fixtures/developer'
  57447. require 'fixtures/project'
  57448. require 'fixtures/comment'
  57449. require 'fixtures/post'
  57450. require 'fixtures/category'
  57451. class MethodScopingTest < Test::Unit::TestCase
  57452. fixtures :developers, :projects, :comments, :posts
  57453. def test_set_conditions
  57454. Developer.with_scope(:find => { :conditions => 'just a test...' }) do
  57455. assert_equal 'just a test...', Developer.send(:current_scoped_methods)[:find][:conditions]
  57456. end
  57457. end
  57458. def test_scoped_find
  57459. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57460. assert_nothing_raised { Developer.find(1) }
  57461. end
  57462. end
  57463. def test_scoped_find_first
  57464. Developer.with_scope(:find => { :conditions => "salary = 100000" }) do
  57465. assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
  57466. end
  57467. end
  57468. def test_scoped_find_combines_conditions
  57469. Developer.with_scope(:find => { :conditions => "salary = 9000" }) do
  57470. assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
  57471. end
  57472. end
  57473. def test_scoped_find_sanitizes_conditions
  57474. Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
  57475. assert_equal developers(:poor_jamis), Developer.find(:first)
  57476. end
  57477. end
  57478. def test_scoped_find_combines_and_sanitizes_conditions
  57479. Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
  57480. assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
  57481. end
  57482. end
  57483. def test_scoped_find_all
  57484. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57485. assert_equal [developers(:david)], Developer.find(:all)
  57486. end
  57487. end
  57488. def test_scoped_count
  57489. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57490. assert_equal 1, Developer.count
  57491. end
  57492. Developer.with_scope(:find => { :conditions => 'salary = 100000' }) do
  57493. assert_equal 8, Developer.count
  57494. assert_equal 1, Developer.count("name LIKE 'fixture_1%'")
  57495. end
  57496. end
  57497. def test_scoped_find_include
  57498. # with the include, will retrieve only developers for the given project
  57499. scoped_developers = Developer.with_scope(:find => { :include => :projects }) do
  57500. Developer.find(:all, :conditions => 'projects.id = 2')
  57501. end
  57502. assert scoped_developers.include?(developers(:david))
  57503. assert !scoped_developers.include?(developers(:jamis))
  57504. assert_equal 1, scoped_developers.size
  57505. end
  57506. def test_scoped_count_include
  57507. # with the include, will retrieve only developers for the given project
  57508. Developer.with_scope(:find => { :include => :projects }) do
  57509. assert_equal 1, Developer.count('projects.id = 2')
  57510. end
  57511. end
  57512. def test_scoped_create
  57513. new_comment = nil
  57514. VerySpecialComment.with_scope(:create => { :post_id => 1 }) do
  57515. assert_equal({ :post_id => 1 }, VerySpecialComment.send(:current_scoped_methods)[:create])
  57516. new_comment = VerySpecialComment.create :body => "Wonderful world"
  57517. end
  57518. assert Post.find(1).comments.include?(new_comment)
  57519. end
  57520. def test_immutable_scope
  57521. options = { :conditions => "name = 'David'" }
  57522. Developer.with_scope(:find => options) do
  57523. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57524. options[:conditions] = "name != 'David'"
  57525. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57526. end
  57527. scope = { :find => { :conditions => "name = 'David'" }}
  57528. Developer.with_scope(scope) do
  57529. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57530. scope[:find][:conditions] = "name != 'David'"
  57531. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57532. end
  57533. end
  57534. def test_scoped_with_duck_typing
  57535. scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] })
  57536. Developer.with_scope(scoping) do
  57537. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57538. end
  57539. end
  57540. def test_ensure_that_method_scoping_is_correctly_restored
  57541. scoped_methods = Developer.instance_eval('current_scoped_methods')
  57542. begin
  57543. Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
  57544. raise "an exception"
  57545. end
  57546. rescue
  57547. end
  57548. assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
  57549. end
  57550. end
  57551. class NestedScopingTest < Test::Unit::TestCase
  57552. fixtures :developers, :projects, :comments, :posts
  57553. def test_merge_options
  57554. Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
  57555. Developer.with_scope(:find => { :limit => 10 }) do
  57556. merged_option = Developer.instance_eval('current_scoped_methods')[:find]
  57557. assert_equal({ :conditions => 'salary = 80000', :limit => 10 }, merged_option)
  57558. end
  57559. end
  57560. end
  57561. def test_replace_options
  57562. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57563. Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
  57564. assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.instance_eval('current_scoped_methods'))
  57565. assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.send(:scoped_methods)[-1])
  57566. end
  57567. end
  57568. end
  57569. def test_append_conditions
  57570. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57571. Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
  57572. appended_condition = Developer.instance_eval('current_scoped_methods')[:find][:conditions]
  57573. assert_equal("( name = 'David' ) AND ( salary = 80000 )", appended_condition)
  57574. assert_equal(1, Developer.count)
  57575. end
  57576. Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
  57577. assert_equal(0, Developer.count)
  57578. end
  57579. end
  57580. end
  57581. def test_merge_and_append_options
  57582. Developer.with_scope(:find => { :conditions => 'salary = 80000', :limit => 10 }) do
  57583. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57584. merged_option = Developer.instance_eval('current_scoped_methods')[:find]
  57585. assert_equal({ :conditions => "( salary = 80000 ) AND ( name = 'David' )", :limit => 10 }, merged_option)
  57586. end
  57587. end
  57588. end
  57589. def test_nested_scoped_find
  57590. Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
  57591. Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
  57592. assert_nothing_raised { Developer.find(1) }
  57593. assert_equal('David', Developer.find(:first).name)
  57594. end
  57595. assert_equal('Jamis', Developer.find(:first).name)
  57596. end
  57597. end
  57598. def test_nested_scoped_find_include
  57599. Developer.with_scope(:find => { :include => :projects }) do
  57600. Developer.with_scope(:find => { :conditions => "projects.id = 2" }) do
  57601. assert_nothing_raised { Developer.find(1) }
  57602. assert_equal('David', Developer.find(:first).name)
  57603. end
  57604. end
  57605. end
  57606. def test_nested_scoped_find_merged_include
  57607. # :include's remain unique and don't "double up" when merging
  57608. Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
  57609. Developer.with_scope(:find => { :include => :projects }) do
  57610. assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
  57611. assert_equal('David', Developer.find(:first).name)
  57612. end
  57613. end
  57614. # the nested scope doesn't remove the first :include
  57615. Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
  57616. Developer.with_scope(:find => { :include => [] }) do
  57617. assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
  57618. assert_equal('David', Developer.find(:first).name)
  57619. end
  57620. end
  57621. # mixing array and symbol include's will merge correctly
  57622. Developer.with_scope(:find => { :include => [:projects], :conditions => "projects.id = 2" }) do
  57623. Developer.with_scope(:find => { :include => :projects }) do
  57624. assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
  57625. assert_equal('David', Developer.find(:first).name)
  57626. end
  57627. end
  57628. end
  57629. def test_nested_scoped_find_replace_include
  57630. Developer.with_scope(:find => { :include => :projects }) do
  57631. Developer.with_exclusive_scope(:find => { :include => [] }) do
  57632. assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length
  57633. end
  57634. end
  57635. end
  57636. def test_three_level_nested_exclusive_scoped_find
  57637. Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
  57638. assert_equal('Jamis', Developer.find(:first).name)
  57639. Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
  57640. assert_equal('David', Developer.find(:first).name)
  57641. Developer.with_exclusive_scope(:find => { :conditions => "name = 'Maiha'" }) do
  57642. assert_equal(nil, Developer.find(:first))
  57643. end
  57644. # ensure that scoping is restored
  57645. assert_equal('David', Developer.find(:first).name)
  57646. end
  57647. # ensure that scoping is restored
  57648. assert_equal('Jamis', Developer.find(:first).name)
  57649. end
  57650. end
  57651. def test_merged_scoped_find
  57652. poor_jamis = developers(:poor_jamis)
  57653. Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
  57654. Developer.with_scope(:find => { :offset => 1 }) do
  57655. assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
  57656. end
  57657. end
  57658. end
  57659. def test_merged_scoped_find_sanitizes_conditions
  57660. Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
  57661. Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
  57662. assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) }
  57663. end
  57664. end
  57665. end
  57666. def test_nested_scoped_find_combines_and_sanitizes_conditions
  57667. Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
  57668. Developer.with_exclusive_scope(:find => { :conditions => ['salary = ?', 9000] }) do
  57669. assert_equal developers(:poor_jamis), Developer.find(:first)
  57670. assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
  57671. end
  57672. end
  57673. end
  57674. def test_merged_scoped_find_combines_and_sanitizes_conditions
  57675. Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
  57676. Developer.with_scope(:find => { :conditions => ['salary > ?', 9000] }) do
  57677. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57678. end
  57679. end
  57680. end
  57681. def test_immutable_nested_scope
  57682. options1 = { :conditions => "name = 'Jamis'" }
  57683. options2 = { :conditions => "name = 'David'" }
  57684. Developer.with_scope(:find => options1) do
  57685. Developer.with_exclusive_scope(:find => options2) do
  57686. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57687. options1[:conditions] = options2[:conditions] = nil
  57688. assert_equal %w(David), Developer.find(:all).map { |d| d.name }
  57689. end
  57690. end
  57691. end
  57692. def test_immutable_merged_scope
  57693. options1 = { :conditions => "name = 'Jamis'" }
  57694. options2 = { :conditions => "salary > 10000" }
  57695. Developer.with_scope(:find => options1) do
  57696. Developer.with_scope(:find => options2) do
  57697. assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
  57698. options1[:conditions] = options2[:conditions] = nil
  57699. assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
  57700. end
  57701. end
  57702. end
  57703. def test_ensure_that_method_scoping_is_correctly_restored
  57704. Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
  57705. scoped_methods = Developer.instance_eval('current_scoped_methods')
  57706. begin
  57707. Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
  57708. raise "an exception"
  57709. end
  57710. rescue
  57711. end
  57712. assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
  57713. end
  57714. end
  57715. end
  57716. class HasManyScopingTest< Test::Unit::TestCase
  57717. fixtures :comments, :posts
  57718. def setup
  57719. @welcome = Post.find(1)
  57720. end
  57721. def test_forwarding_of_static_methods
  57722. assert_equal 'a comment...', Comment.what_are_you
  57723. assert_equal 'a comment...', @welcome.comments.what_are_you
  57724. end
  57725. def test_forwarding_to_scoped
  57726. assert_equal 4, Comment.search_by_type('Comment').size
  57727. assert_equal 2, @welcome.comments.search_by_type('Comment').size
  57728. end
  57729. def test_forwarding_to_dynamic_finders
  57730. assert_equal 4, Comment.find_all_by_type('Comment').size
  57731. assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
  57732. end
  57733. def test_nested_scope
  57734. Comment.with_scope(:find => { :conditions => '1=1' }) do
  57735. assert_equal 'a comment...', @welcome.comments.what_are_you
  57736. end
  57737. end
  57738. end
  57739. class HasAndBelongsToManyScopingTest< Test::Unit::TestCase
  57740. fixtures :posts, :categories, :categories_posts
  57741. def setup
  57742. @welcome = Post.find(1)
  57743. end
  57744. def test_forwarding_of_static_methods
  57745. assert_equal 'a category...', Category.what_are_you
  57746. assert_equal 'a category...', @welcome.categories.what_are_you
  57747. end
  57748. def test_forwarding_to_dynamic_finders
  57749. assert_equal 4, Category.find_all_by_type('SpecialCategory').size
  57750. assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
  57751. assert_equal 2, @welcome.categories.find_all_by_type('Category').size
  57752. end
  57753. def test_nested_scope
  57754. Category.with_scope(:find => { :conditions => '1=1' }) do
  57755. assert_equal 'a comment...', @welcome.comments.what_are_you
  57756. end
  57757. end
  57758. end
  57759. =begin
  57760. # We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
  57761. class BelongsToScopingTest< Test::Unit::TestCase
  57762. fixtures :comments, :posts
  57763. def setup
  57764. @greetings = Comment.find(1)
  57765. end
  57766. def test_forwarding_of_static_method
  57767. assert_equal 'a post...', Post.what_are_you
  57768. assert_equal 'a post...', @greetings.post.what_are_you
  57769. end
  57770. def test_forwarding_to_dynamic_finders
  57771. assert_equal 4, Post.find_all_by_type('Post').size
  57772. assert_equal 1, @greetings.post.find_all_by_type('Post').size
  57773. end
  57774. end
  57775. class HasOneScopingTest< Test::Unit::TestCase
  57776. fixtures :comments, :posts
  57777. def setup
  57778. @sti_comments = Post.find(4)
  57779. end
  57780. def test_forwarding_of_static_methods
  57781. assert_equal 'a comment...', Comment.what_are_you
  57782. assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
  57783. end
  57784. def test_forwarding_to_dynamic_finders
  57785. assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
  57786. assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
  57787. assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
  57788. end
  57789. end
  57790. =end
  57791. require 'abstract_unit'
  57792. require 'fixtures/person'
  57793. require File.dirname(__FILE__) + '/fixtures/migrations/1_people_have_last_names'
  57794. require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders'
  57795. if ActiveRecord::Base.connection.supports_migrations?
  57796. class Reminder < ActiveRecord::Base; end
  57797. class ActiveRecord::Migration
  57798. class <<self
  57799. attr_accessor :message_count
  57800. def puts(text="")
  57801. self.message_count ||= 0
  57802. self.message_count += 1
  57803. end
  57804. end
  57805. end
  57806. class MigrationTest < Test::Unit::TestCase
  57807. self.use_transactional_fixtures = false
  57808. def setup
  57809. ActiveRecord::Migration.verbose = true
  57810. PeopleHaveLastNames.message_count = 0
  57811. end
  57812. def teardown
  57813. ActiveRecord::Base.connection.initialize_schema_information
  57814. ActiveRecord::Base.connection.update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0"
  57815. Reminder.connection.drop_table("reminders") rescue nil
  57816. Reminder.connection.drop_table("people_reminders") rescue nil
  57817. Reminder.connection.drop_table("prefix_reminders_suffix") rescue nil
  57818. Reminder.reset_column_information
  57819. Person.connection.remove_column("people", "last_name") rescue nil
  57820. Person.connection.remove_column("people", "key") rescue nil
  57821. Person.connection.remove_column("people", "bio") rescue nil
  57822. Person.connection.remove_column("people", "age") rescue nil
  57823. Person.connection.remove_column("people", "height") rescue nil
  57824. Person.connection.remove_column("people", "birthday") rescue nil
  57825. Person.connection.remove_column("people", "favorite_day") rescue nil
  57826. Person.connection.remove_column("people", "male") rescue nil
  57827. Person.connection.remove_column("people", "administrator") rescue nil
  57828. Person.reset_column_information
  57829. end
  57830. def test_add_index
  57831. Person.connection.add_column "people", "last_name", :string
  57832. Person.connection.add_column "people", "administrator", :boolean
  57833. Person.connection.add_column "people", "key", :string
  57834. assert_nothing_raised { Person.connection.add_index("people", "last_name") }
  57835. assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
  57836. assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
  57837. assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
  57838. # quoting
  57839. assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key", :unique => true) }
  57840. assert_nothing_raised { Person.connection.remove_index("people", :name => "key") }
  57841. # Sybase adapter does not support indexes on :boolean columns
  57842. unless current_adapter?(:SybaseAdapter)
  57843. assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") }
  57844. assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
  57845. end
  57846. end
  57847. def test_create_table_adds_id
  57848. Person.connection.create_table :testings do |t|
  57849. t.column :foo, :string
  57850. end
  57851. assert_equal %w(foo id),
  57852. Person.connection.columns(:testings).map { |c| c.name }.sort
  57853. ensure
  57854. Person.connection.drop_table :testings rescue nil
  57855. end
  57856. def test_create_table_with_not_null_column
  57857. Person.connection.create_table :testings do |t|
  57858. t.column :foo, :string, :null => false
  57859. end
  57860. assert_raises(ActiveRecord::StatementInvalid) do
  57861. Person.connection.execute "insert into testings (foo) values (NULL)"
  57862. end
  57863. ensure
  57864. Person.connection.drop_table :testings rescue nil
  57865. end
  57866. def test_create_table_with_defaults
  57867. Person.connection.create_table :testings do |t|
  57868. t.column :one, :string, :default => "hello"
  57869. t.column :two, :boolean, :default => true
  57870. t.column :three, :boolean, :default => false
  57871. t.column :four, :integer, :default => 1
  57872. end
  57873. columns = Person.connection.columns(:testings)
  57874. one = columns.detect { |c| c.name == "one" }
  57875. two = columns.detect { |c| c.name == "two" }
  57876. three = columns.detect { |c| c.name == "three" }
  57877. four = columns.detect { |c| c.name == "four" }
  57878. assert_equal "hello", one.default
  57879. if current_adapter?(:OracleAdapter)
  57880. # Oracle doesn't support native booleans
  57881. assert_equal true, two.default == 1
  57882. assert_equal false, three.default != 0
  57883. else
  57884. assert_equal true, two.default
  57885. assert_equal false, three.default
  57886. end
  57887. assert_equal 1, four.default
  57888. ensure
  57889. Person.connection.drop_table :testings rescue nil
  57890. end
  57891. # SQL Server and Sybase will not allow you to add a NOT NULL column
  57892. # to a table without specifying a default value, so the
  57893. # following test must be skipped
  57894. unless current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter)
  57895. def test_add_column_not_null_without_default
  57896. Person.connection.create_table :testings do |t|
  57897. t.column :foo, :string
  57898. end
  57899. Person.connection.add_column :testings, :bar, :string, :null => false
  57900. assert_raises(ActiveRecord::StatementInvalid) do
  57901. Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
  57902. end
  57903. ensure
  57904. Person.connection.drop_table :testings rescue nil
  57905. end
  57906. end
  57907. def test_add_column_not_null_with_default
  57908. Person.connection.create_table :testings do |t|
  57909. t.column :foo, :string
  57910. end
  57911. Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default"
  57912. assert_raises(ActiveRecord::StatementInvalid) do
  57913. Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
  57914. end
  57915. ensure
  57916. Person.connection.drop_table :testings rescue nil
  57917. end
  57918. def test_native_types
  57919. Person.delete_all
  57920. Person.connection.add_column "people", "last_name", :string
  57921. Person.connection.add_column "people", "bio", :text
  57922. Person.connection.add_column "people", "age", :integer
  57923. Person.connection.add_column "people", "height", :float
  57924. Person.connection.add_column "people", "birthday", :datetime
  57925. Person.connection.add_column "people", "favorite_day", :date
  57926. Person.connection.add_column "people", "male", :boolean
  57927. assert_nothing_raised { Person.create :first_name => 'bob', :last_name => 'bobsen', :bio => "I was born ....", :age => 18, :height => 1.78, :birthday => 18.years.ago, :favorite_day => 10.days.ago, :male => true }
  57928. bob = Person.find(:first)
  57929. assert_equal bob.first_name, 'bob'
  57930. assert_equal bob.last_name, 'bobsen'
  57931. assert_equal bob.bio, "I was born ...."
  57932. assert_equal bob.age, 18
  57933. assert_equal bob.male?, true
  57934. assert_equal String, bob.first_name.class
  57935. assert_equal String, bob.last_name.class
  57936. assert_equal String, bob.bio.class
  57937. assert_equal Fixnum, bob.age.class
  57938. assert_equal Time, bob.birthday.class
  57939. if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter) || current_adapter?(:SybaseAdapter)
  57940. # SQL Server, Sybase, and Oracle don't differentiate between date/time
  57941. assert_equal Time, bob.favorite_day.class
  57942. else
  57943. assert_equal Date, bob.favorite_day.class
  57944. end
  57945. assert_equal TrueClass, bob.male?.class
  57946. end
  57947. def test_add_remove_single_field_using_string_arguments
  57948. assert !Person.column_methods_hash.include?(:last_name)
  57949. ActiveRecord::Migration.add_column 'people', 'last_name', :string
  57950. Person.reset_column_information
  57951. assert Person.column_methods_hash.include?(:last_name)
  57952. ActiveRecord::Migration.remove_column 'people', 'last_name'
  57953. Person.reset_column_information
  57954. assert !Person.column_methods_hash.include?(:last_name)
  57955. end
  57956. def test_add_remove_single_field_using_symbol_arguments
  57957. assert !Person.column_methods_hash.include?(:last_name)
  57958. ActiveRecord::Migration.add_column :people, :last_name, :string
  57959. Person.reset_column_information
  57960. assert Person.column_methods_hash.include?(:last_name)
  57961. ActiveRecord::Migration.remove_column :people, :last_name
  57962. Person.reset_column_information
  57963. assert !Person.column_methods_hash.include?(:last_name)
  57964. end
  57965. def test_add_rename
  57966. Person.delete_all
  57967. begin
  57968. Person.connection.add_column "people", "girlfriend", :string
  57969. Person.create :girlfriend => 'bobette'
  57970. Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
  57971. Person.reset_column_information
  57972. bob = Person.find(:first)
  57973. assert_equal "bobette", bob.exgirlfriend
  57974. ensure
  57975. Person.connection.remove_column("people", "girlfriend") rescue nil
  57976. Person.connection.remove_column("people", "exgirlfriend") rescue nil
  57977. end
  57978. end
  57979. def test_rename_column_using_symbol_arguments
  57980. begin
  57981. Person.connection.rename_column :people, :first_name, :nick_name
  57982. Person.reset_column_information
  57983. assert Person.column_names.include?("nick_name")
  57984. ensure
  57985. Person.connection.remove_column("people","nick_name")
  57986. Person.connection.add_column("people","first_name", :string)
  57987. end
  57988. end
  57989. def test_rename_column
  57990. begin
  57991. Person.connection.rename_column "people", "first_name", "nick_name"
  57992. Person.reset_column_information
  57993. assert Person.column_names.include?("nick_name")
  57994. ensure
  57995. Person.connection.remove_column("people","nick_name")
  57996. Person.connection.add_column("people","first_name", :string)
  57997. end
  57998. end
  57999. def test_rename_table
  58000. begin
  58001. ActiveRecord::Base.connection.create_table :octopuses do |t|
  58002. t.column :url, :string
  58003. end
  58004. ActiveRecord::Base.connection.rename_table :octopuses, :octopi
  58005. assert_nothing_raised do
  58006. if current_adapter?(:OracleAdapter)
  58007. # Oracle requires the explicit sequence value for the pk
  58008. ActiveRecord::Base.connection.execute "INSERT INTO octopi (id, url) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
  58009. else
  58010. ActiveRecord::Base.connection.execute "INSERT INTO octopi (url) VALUES ('http://www.foreverflying.com/octopus-black7.jpg')"
  58011. end
  58012. end
  58013. assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
  58014. ensure
  58015. ActiveRecord::Base.connection.drop_table :octopuses rescue nil
  58016. ActiveRecord::Base.connection.drop_table :octopi rescue nil
  58017. end
  58018. end
  58019. def test_change_column
  58020. Person.connection.add_column 'people', 'age', :integer
  58021. old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
  58022. assert old_columns.find { |c| c.name == 'age' and c.type == :integer }
  58023. assert_nothing_raised { Person.connection.change_column "people", "age", :string }
  58024. new_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
  58025. assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
  58026. assert new_columns.find { |c| c.name == 'age' and c.type == :string }
  58027. end
  58028. def test_change_column_with_new_default
  58029. Person.connection.add_column "people", "administrator", :boolean, :default => 1
  58030. Person.reset_column_information
  58031. assert Person.new.administrator?
  58032. assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => 0 }
  58033. Person.reset_column_information
  58034. assert !Person.new.administrator?
  58035. end
  58036. def test_add_table
  58037. assert !Reminder.table_exists?
  58038. WeNeedReminders.up
  58039. assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
  58040. assert_equal "hello world", Reminder.find(:first).content
  58041. WeNeedReminders.down
  58042. assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
  58043. end
  58044. def test_migrator
  58045. assert !Person.column_methods_hash.include?(:last_name)
  58046. assert !Reminder.table_exists?
  58047. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
  58048. assert_equal 3, ActiveRecord::Migrator.current_version
  58049. Person.reset_column_information
  58050. assert Person.column_methods_hash.include?(:last_name)
  58051. assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
  58052. assert_equal "hello world", Reminder.find(:first).content
  58053. ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')
  58054. assert_equal 0, ActiveRecord::Migrator.current_version
  58055. Person.reset_column_information
  58056. assert !Person.column_methods_hash.include?(:last_name)
  58057. assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
  58058. end
  58059. def test_migrator_one_up
  58060. assert !Person.column_methods_hash.include?(:last_name)
  58061. assert !Reminder.table_exists?
  58062. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
  58063. Person.reset_column_information
  58064. assert Person.column_methods_hash.include?(:last_name)
  58065. assert !Reminder.table_exists?
  58066. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 2)
  58067. assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
  58068. assert_equal "hello world", Reminder.find(:first).content
  58069. end
  58070. def test_migrator_one_down
  58071. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
  58072. ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
  58073. Person.reset_column_information
  58074. assert Person.column_methods_hash.include?(:last_name)
  58075. assert !Reminder.table_exists?
  58076. end
  58077. def test_migrator_one_up_one_down
  58078. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
  58079. ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
  58080. assert !Person.column_methods_hash.include?(:last_name)
  58081. assert !Reminder.table_exists?
  58082. end
  58083. def test_migrator_verbosity
  58084. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
  58085. assert PeopleHaveLastNames.message_count > 0
  58086. PeopleHaveLastNames.message_count = 0
  58087. ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
  58088. assert PeopleHaveLastNames.message_count > 0
  58089. PeopleHaveLastNames.message_count = 0
  58090. end
  58091. def test_migrator_verbosity_off
  58092. PeopleHaveLastNames.verbose = false
  58093. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
  58094. assert PeopleHaveLastNames.message_count.zero?
  58095. ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
  58096. assert PeopleHaveLastNames.message_count.zero?
  58097. end
  58098. def test_migrator_going_down_due_to_version_target
  58099. ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
  58100. ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
  58101. assert !Person.column_methods_hash.include?(:last_name)
  58102. assert !Reminder.table_exists?
  58103. ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/')
  58104. Person.reset_column_information
  58105. assert Person.column_methods_hash.include?(:last_name)
  58106. assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
  58107. assert_equal "hello world", Reminder.find(:first).content
  58108. end
  58109. def test_schema_info_table_name
  58110. ActiveRecord::Base.table_name_prefix = "prefix_"
  58111. ActiveRecord::Base.table_name_suffix = "_suffix"
  58112. Reminder.reset_table_name
  58113. assert_equal "prefix_schema_info_suffix", ActiveRecord::Migrator.schema_info_table_name
  58114. ActiveRecord::Base.table_name_prefix = ""
  58115. ActiveRecord::Base.table_name_suffix = ""
  58116. Reminder.reset_table_name
  58117. assert_equal "schema_info", ActiveRecord::Migrator.schema_info_table_name
  58118. ensure
  58119. ActiveRecord::Base.table_name_prefix = ""
  58120. ActiveRecord::Base.table_name_suffix = ""
  58121. end
  58122. def test_proper_table_name
  58123. assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
  58124. assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
  58125. assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
  58126. Reminder.reset_table_name
  58127. assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
  58128. # Use the model's own prefix/suffix if a model is given
  58129. ActiveRecord::Base.table_name_prefix = "ARprefix_"
  58130. ActiveRecord::Base.table_name_suffix = "_ARsuffix"
  58131. Reminder.table_name_prefix = 'prefix_'
  58132. Reminder.table_name_suffix = '_suffix'
  58133. Reminder.reset_table_name
  58134. assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
  58135. Reminder.table_name_prefix = ''
  58136. Reminder.table_name_suffix = ''
  58137. Reminder.reset_table_name
  58138. # Use AR::Base's prefix/suffix if string or symbol is given
  58139. ActiveRecord::Base.table_name_prefix = "prefix_"
  58140. ActiveRecord::Base.table_name_suffix = "_suffix"
  58141. Reminder.reset_table_name
  58142. assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
  58143. assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
  58144. ActiveRecord::Base.table_name_prefix = ""
  58145. ActiveRecord::Base.table_name_suffix = ""
  58146. Reminder.reset_table_name
  58147. end
  58148. def test_add_drop_table_with_prefix_and_suffix
  58149. assert !Reminder.table_exists?
  58150. ActiveRecord::Base.table_name_prefix = 'prefix_'
  58151. ActiveRecord::Base.table_name_suffix = '_suffix'
  58152. Reminder.reset_table_name
  58153. Reminder.reset_sequence_name
  58154. WeNeedReminders.up
  58155. assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
  58156. assert_equal "hello world", Reminder.find(:first).content
  58157. WeNeedReminders.down
  58158. assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
  58159. ensure
  58160. ActiveRecord::Base.table_name_prefix = ''
  58161. ActiveRecord::Base.table_name_suffix = ''
  58162. Reminder.reset_table_name
  58163. Reminder.reset_sequence_name
  58164. end
  58165. def test_create_table_with_binary_column
  58166. Person.connection.drop_table :binary_testings rescue nil
  58167. assert_nothing_raised {
  58168. Person.connection.create_table :binary_testings do |t|
  58169. t.column "data", :binary, :default => "", :null => false
  58170. end
  58171. }
  58172. columns = Person.connection.columns(:binary_testings)
  58173. data_column = columns.detect { |c| c.name == "data" }
  58174. if current_adapter?(:OracleAdapter)
  58175. assert_equal "empty_blob()", data_column.default
  58176. else
  58177. assert_equal "", data_column.default
  58178. end
  58179. Person.connection.drop_table :binary_testings rescue nil
  58180. end
  58181. def test_migrator_with_duplicates
  58182. assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
  58183. ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_duplicate/', nil)
  58184. end
  58185. end
  58186. end
  58187. end
  58188. require 'abstract_unit'
  58189. require 'active_record/acts/nested_set'
  58190. require 'fixtures/mixin'
  58191. require 'pp'
  58192. class MixinNestedSetTest < Test::Unit::TestCase
  58193. fixtures :mixins
  58194. def test_mixing_in_methods
  58195. ns = NestedSet.new
  58196. assert( ns.respond_to?( :all_children ) )
  58197. assert_equal( ns.scope_condition, "root_id IS NULL" )
  58198. check_method_mixins ns
  58199. end
  58200. def test_string_scope
  58201. ns = NestedSetWithStringScope.new
  58202. ns.root_id = 1
  58203. assert_equal( ns.scope_condition, "root_id = 1" )
  58204. ns.root_id = 42
  58205. assert_equal( ns.scope_condition, "root_id = 42" )
  58206. check_method_mixins ns
  58207. end
  58208. def test_symbol_scope
  58209. ns = NestedSetWithSymbolScope.new
  58210. ns.root_id = 1
  58211. assert_equal( ns.scope_condition, "root_id = 1" )
  58212. ns.root_id = 42
  58213. assert_equal( ns.scope_condition, "root_id = 42" )
  58214. check_method_mixins ns
  58215. end
  58216. def check_method_mixins( obj )
  58217. [:scope_condition, :left_col_name, :right_col_name, :parent_column, :root?, :add_child,
  58218. :children_count, :full_set, :all_children, :direct_children].each { |symbol| assert( obj.respond_to?(symbol)) }
  58219. end
  58220. def set( id )
  58221. NestedSet.find( 3000 + id )
  58222. end
  58223. def test_adding_children
  58224. assert( set(1).unknown? )
  58225. assert( set(2).unknown? )
  58226. set(1).add_child set(2)
  58227. # Did we maintain adding the parent_ids?
  58228. assert( set(1).root? )
  58229. assert( set(2).child? )
  58230. assert( set(2).parent_id == set(1).id )
  58231. # Check boundies
  58232. assert_equal( set(1).lft, 1 )
  58233. assert_equal( set(2).lft, 2 )
  58234. assert_equal( set(2).rgt, 3 )
  58235. assert_equal( set(1).rgt, 4 )
  58236. # Check children cound
  58237. assert_equal( set(1).children_count, 1 )
  58238. set(1).add_child set(3)
  58239. #check boundries
  58240. assert_equal( set(1).lft, 1 )
  58241. assert_equal( set(2).lft, 2 )
  58242. assert_equal( set(2).rgt, 3 )
  58243. assert_equal( set(3).lft, 4 )
  58244. assert_equal( set(3).rgt, 5 )
  58245. assert_equal( set(1).rgt, 6 )
  58246. # How is the count looking?
  58247. assert_equal( set(1).children_count, 2 )
  58248. set(2).add_child set(4)
  58249. # boundries
  58250. assert_equal( set(1).lft, 1 )
  58251. assert_equal( set(2).lft, 2 )
  58252. assert_equal( set(4).lft, 3 )
  58253. assert_equal( set(4).rgt, 4 )
  58254. assert_equal( set(2).rgt, 5 )
  58255. assert_equal( set(3).lft, 6 )
  58256. assert_equal( set(3).rgt, 7 )
  58257. assert_equal( set(1).rgt, 8 )
  58258. # Children count
  58259. assert_equal( set(1).children_count, 3 )
  58260. assert_equal( set(2).children_count, 1 )
  58261. assert_equal( set(3).children_count, 0 )
  58262. assert_equal( set(4).children_count, 0 )
  58263. set(2).add_child set(5)
  58264. set(4).add_child set(6)
  58265. assert_equal( set(2).children_count, 3 )
  58266. # Children accessors
  58267. assert_equal( set(1).full_set.length, 6 )
  58268. assert_equal( set(2).full_set.length, 4 )
  58269. assert_equal( set(4).full_set.length, 2 )
  58270. assert_equal( set(1).all_children.length, 5 )
  58271. assert_equal( set(6).all_children.length, 0 )
  58272. assert_equal( set(1).direct_children.length, 2 )
  58273. end
  58274. def test_snipping_tree
  58275. big_tree = NestedSetWithStringScope.find( 4001 )
  58276. # Make sure we have the right one
  58277. assert_equal( 3, big_tree.direct_children.length )
  58278. assert_equal( 10, big_tree.full_set.length )
  58279. NestedSetWithStringScope.find( 4005 ).destroy
  58280. big_tree = NestedSetWithStringScope.find( 4001 )
  58281. assert_equal( 7, big_tree.full_set.length )
  58282. assert_equal( 2, big_tree.direct_children.length )
  58283. assert_equal( 1, NestedSetWithStringScope.find(4001).lft )
  58284. assert_equal( 2, NestedSetWithStringScope.find(4002).lft )
  58285. assert_equal( 3, NestedSetWithStringScope.find(4003).lft )
  58286. assert_equal( 4, NestedSetWithStringScope.find(4003).rgt )
  58287. assert_equal( 5, NestedSetWithStringScope.find(4004).lft )
  58288. assert_equal( 6, NestedSetWithStringScope.find(4004).rgt )
  58289. assert_equal( 7, NestedSetWithStringScope.find(4002).rgt )
  58290. assert_equal( 8, NestedSetWithStringScope.find(4008).lft )
  58291. assert_equal( 9, NestedSetWithStringScope.find(4009).lft )
  58292. assert_equal(10, NestedSetWithStringScope.find(4009).rgt )
  58293. assert_equal(11, NestedSetWithStringScope.find(4010).lft )
  58294. assert_equal(12, NestedSetWithStringScope.find(4010).rgt )
  58295. assert_equal(13, NestedSetWithStringScope.find(4008).rgt )
  58296. assert_equal(14, NestedSetWithStringScope.find(4001).rgt )
  58297. end
  58298. def test_deleting_root
  58299. NestedSetWithStringScope.find(4001).destroy
  58300. assert( NestedSetWithStringScope.count == 0 )
  58301. end
  58302. def test_common_usage
  58303. mixins(:set_1).add_child( mixins(:set_2) )
  58304. assert_equal( 1, mixins(:set_1).direct_children.length )
  58305. mixins(:set_2).add_child( mixins(:set_3) )
  58306. assert_equal( 1, mixins(:set_1).direct_children.length )
  58307. # Local cache is now out of date!
  58308. # Problem: the update_alls update all objects up the tree
  58309. mixins(:set_1).reload
  58310. assert_equal( 2, mixins(:set_1).all_children.length )
  58311. assert_equal( 1, mixins(:set_1).lft )
  58312. assert_equal( 2, mixins(:set_2).lft )
  58313. assert_equal( 3, mixins(:set_3).lft )
  58314. assert_equal( 4, mixins(:set_3).rgt )
  58315. assert_equal( 5, mixins(:set_2).rgt )
  58316. assert_equal( 6, mixins(:set_1).rgt )
  58317. assert( mixins(:set_1).root? )
  58318. begin
  58319. mixins(:set_4).add_child( mixins(:set_1) )
  58320. fail
  58321. rescue
  58322. end
  58323. assert_equal( 2, mixins(:set_1).all_children.length )
  58324. mixins(:set_1).add_child mixins(:set_4)
  58325. assert_equal( 3, mixins(:set_1).all_children.length )
  58326. end
  58327. end
  58328. require 'abstract_unit'
  58329. require 'active_record/acts/tree'
  58330. require 'active_record/acts/list'
  58331. require 'active_record/acts/nested_set'
  58332. require 'fixtures/mixin'
  58333. class ListTest < Test::Unit::TestCase
  58334. fixtures :mixins
  58335. def test_reordering
  58336. assert_equal [mixins(:list_1),
  58337. mixins(:list_2),
  58338. mixins(:list_3),
  58339. mixins(:list_4)],
  58340. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58341. mixins(:list_2).move_lower
  58342. assert_equal [mixins(:list_1),
  58343. mixins(:list_3),
  58344. mixins(:list_2),
  58345. mixins(:list_4)],
  58346. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58347. mixins(:list_2).move_higher
  58348. assert_equal [mixins(:list_1),
  58349. mixins(:list_2),
  58350. mixins(:list_3),
  58351. mixins(:list_4)],
  58352. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58353. mixins(:list_1).move_to_bottom
  58354. assert_equal [mixins(:list_2),
  58355. mixins(:list_3),
  58356. mixins(:list_4),
  58357. mixins(:list_1)],
  58358. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58359. mixins(:list_1).move_to_top
  58360. assert_equal [mixins(:list_1),
  58361. mixins(:list_2),
  58362. mixins(:list_3),
  58363. mixins(:list_4)],
  58364. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58365. mixins(:list_2).move_to_bottom
  58366. assert_equal [mixins(:list_1),
  58367. mixins(:list_3),
  58368. mixins(:list_4),
  58369. mixins(:list_2)],
  58370. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58371. mixins(:list_4).move_to_top
  58372. assert_equal [mixins(:list_4),
  58373. mixins(:list_1),
  58374. mixins(:list_3),
  58375. mixins(:list_2)],
  58376. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58377. end
  58378. def test_move_to_bottom_with_next_to_last_item
  58379. assert_equal [mixins(:list_1),
  58380. mixins(:list_2),
  58381. mixins(:list_3),
  58382. mixins(:list_4)],
  58383. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58384. mixins(:list_3).move_to_bottom
  58385. assert_equal [mixins(:list_1),
  58386. mixins(:list_2),
  58387. mixins(:list_4),
  58388. mixins(:list_3)],
  58389. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58390. end
  58391. def test_next_prev
  58392. assert_equal mixins(:list_2), mixins(:list_1).lower_item
  58393. assert_nil mixins(:list_1).higher_item
  58394. assert_equal mixins(:list_3), mixins(:list_4).higher_item
  58395. assert_nil mixins(:list_4).lower_item
  58396. end
  58397. def test_injection
  58398. item = ListMixin.new("parent_id"=>1)
  58399. assert_equal "parent_id = 1", item.scope_condition
  58400. assert_equal "pos", item.position_column
  58401. end
  58402. def test_insert
  58403. new = ListMixin.create("parent_id"=>20)
  58404. assert_equal 1, new.pos
  58405. assert new.first?
  58406. assert new.last?
  58407. new = ListMixin.create("parent_id"=>20)
  58408. assert_equal 2, new.pos
  58409. assert !new.first?
  58410. assert new.last?
  58411. new = ListMixin.create("parent_id"=>20)
  58412. assert_equal 3, new.pos
  58413. assert !new.first?
  58414. assert new.last?
  58415. new = ListMixin.create("parent_id"=>0)
  58416. assert_equal 1, new.pos
  58417. assert new.first?
  58418. assert new.last?
  58419. end
  58420. def test_insert_at
  58421. new = ListMixin.create("parent_id" => 20)
  58422. assert_equal 1, new.pos
  58423. new = ListMixin.create("parent_id" => 20)
  58424. assert_equal 2, new.pos
  58425. new = ListMixin.create("parent_id" => 20)
  58426. assert_equal 3, new.pos
  58427. new4 = ListMixin.create("parent_id" => 20)
  58428. assert_equal 4, new4.pos
  58429. new4.insert_at(3)
  58430. assert_equal 3, new4.pos
  58431. new.reload
  58432. assert_equal 4, new.pos
  58433. new.insert_at(2)
  58434. assert_equal 2, new.pos
  58435. new4.reload
  58436. assert_equal 4, new4.pos
  58437. new5 = ListMixin.create("parent_id" => 20)
  58438. assert_equal 5, new5.pos
  58439. new5.insert_at(1)
  58440. assert_equal 1, new5.pos
  58441. new4.reload
  58442. assert_equal 5, new4.pos
  58443. end
  58444. def test_delete_middle
  58445. assert_equal [mixins(:list_1),
  58446. mixins(:list_2),
  58447. mixins(:list_3),
  58448. mixins(:list_4)],
  58449. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58450. mixins(:list_2).destroy
  58451. assert_equal [mixins(:list_1, :reload),
  58452. mixins(:list_3, :reload),
  58453. mixins(:list_4, :reload)],
  58454. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58455. assert_equal 1, mixins(:list_1).pos
  58456. assert_equal 2, mixins(:list_3).pos
  58457. assert_equal 3, mixins(:list_4).pos
  58458. mixins(:list_1).destroy
  58459. assert_equal [mixins(:list_3, :reload),
  58460. mixins(:list_4, :reload)],
  58461. ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  58462. assert_equal 1, mixins(:list_3).pos
  58463. assert_equal 2, mixins(:list_4).pos
  58464. end
  58465. def test_with_string_based_scope
  58466. new = ListWithStringScopeMixin.create("parent_id"=>500)
  58467. assert_equal 1, new.pos
  58468. assert new.first?
  58469. assert new.last?
  58470. end
  58471. def test_nil_scope
  58472. new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
  58473. new2.move_higher
  58474. assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
  58475. end
  58476. end
  58477. class TreeTest < Test::Unit::TestCase
  58478. fixtures :mixins
  58479. def test_has_child
  58480. assert_equal true, mixins(:tree_1).has_children?
  58481. assert_equal true, mixins(:tree_2).has_children?
  58482. assert_equal false, mixins(:tree_3).has_children?
  58483. assert_equal false, mixins(:tree_4).has_children?
  58484. end
  58485. def test_children
  58486. assert_equal mixins(:tree_1).children, [mixins(:tree_2), mixins(:tree_4)]
  58487. assert_equal mixins(:tree_2).children, [mixins(:tree_3)]
  58488. assert_equal mixins(:tree_3).children, []
  58489. assert_equal mixins(:tree_4).children, []
  58490. end
  58491. def test_has_parent
  58492. assert_equal false, mixins(:tree_1).has_parent?
  58493. assert_equal true, mixins(:tree_2).has_parent?
  58494. assert_equal true, mixins(:tree_3).has_parent?
  58495. assert_equal true, mixins(:tree_4).has_parent?
  58496. end
  58497. def test_parent
  58498. assert_equal mixins(:tree_2).parent, mixins(:tree_1)
  58499. assert_equal mixins(:tree_2).parent, mixins(:tree_4).parent
  58500. assert_nil mixins(:tree_1).parent
  58501. end
  58502. def test_delete
  58503. assert_equal 6, TreeMixin.count
  58504. mixins(:tree_1).destroy
  58505. assert_equal 2, TreeMixin.count
  58506. mixins(:tree2_1).destroy
  58507. mixins(:tree3_1).destroy
  58508. assert_equal 0, TreeMixin.count
  58509. end
  58510. def test_insert
  58511. @extra = mixins(:tree_1).children.create
  58512. assert @extra
  58513. assert_equal @extra.parent, mixins(:tree_1)
  58514. assert_equal 3, mixins(:tree_1).children.size
  58515. assert mixins(:tree_1).children.include?(@extra)
  58516. assert mixins(:tree_1).children.include?(mixins(:tree_2))
  58517. assert mixins(:tree_1).children.include?(mixins(:tree_4))
  58518. end
  58519. def test_ancestors
  58520. assert_equal [], mixins(:tree_1).ancestors
  58521. assert_equal [mixins(:tree_1)], mixins(:tree_2).ancestors
  58522. assert_equal [mixins(:tree_2), mixins(:tree_1)], mixins(:tree_3).ancestors
  58523. assert_equal [mixins(:tree_1)], mixins(:tree_4).ancestors
  58524. assert_equal [], mixins(:tree2_1).ancestors
  58525. assert_equal [], mixins(:tree3_1).ancestors
  58526. end
  58527. def test_root
  58528. assert_equal mixins(:tree_1), TreeMixin.root
  58529. assert_equal mixins(:tree_1), mixins(:tree_1).root
  58530. assert_equal mixins(:tree_1), mixins(:tree_2).root
  58531. assert_equal mixins(:tree_1), mixins(:tree_3).root
  58532. assert_equal mixins(:tree_1), mixins(:tree_4).root
  58533. assert_equal mixins(:tree2_1), mixins(:tree2_1).root
  58534. assert_equal mixins(:tree3_1), mixins(:tree3_1).root
  58535. end
  58536. def test_roots
  58537. assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], TreeMixin.roots
  58538. end
  58539. def test_siblings
  58540. assert_equal [mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree_1).siblings
  58541. assert_equal [mixins(:tree_4)], mixins(:tree_2).siblings
  58542. assert_equal [], mixins(:tree_3).siblings
  58543. assert_equal [mixins(:tree_2)], mixins(:tree_4).siblings
  58544. assert_equal [mixins(:tree_1), mixins(:tree3_1)], mixins(:tree2_1).siblings
  58545. assert_equal [mixins(:tree_1), mixins(:tree2_1)], mixins(:tree3_1).siblings
  58546. end
  58547. def test_self_and_siblings
  58548. assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree_1).self_and_siblings
  58549. assert_equal [mixins(:tree_2), mixins(:tree_4)], mixins(:tree_2).self_and_siblings
  58550. assert_equal [mixins(:tree_3)], mixins(:tree_3).self_and_siblings
  58551. assert_equal [mixins(:tree_2), mixins(:tree_4)], mixins(:tree_4).self_and_siblings
  58552. assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree2_1).self_and_siblings
  58553. assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree3_1).self_and_siblings
  58554. end
  58555. end
  58556. class TreeTestWithoutOrder < Test::Unit::TestCase
  58557. fixtures :mixins
  58558. def test_root
  58559. assert [mixins(:tree_without_order_1), mixins(:tree_without_order_2)].include?(TreeMixinWithoutOrder.root)
  58560. end
  58561. def test_roots
  58562. assert_equal [], [mixins(:tree_without_order_1), mixins(:tree_without_order_2)] - TreeMixinWithoutOrder.roots
  58563. end
  58564. end
  58565. class TouchTest < Test::Unit::TestCase
  58566. fixtures :mixins
  58567. def test_update
  58568. stamped = Mixin.new
  58569. assert_nil stamped.updated_at
  58570. assert_nil stamped.created_at
  58571. stamped.save
  58572. assert_not_nil stamped.updated_at
  58573. assert_not_nil stamped.created_at
  58574. end
  58575. def test_create
  58576. @obj = Mixin.create
  58577. assert_not_nil @obj.updated_at
  58578. assert_not_nil @obj.created_at
  58579. end
  58580. def test_many_updates
  58581. stamped = Mixin.new
  58582. assert_nil stamped.updated_at
  58583. assert_nil stamped.created_at
  58584. stamped.save
  58585. assert_not_nil stamped.created_at
  58586. assert_not_nil stamped.updated_at
  58587. old_updated_at = stamped.updated_at
  58588. sleep 1
  58589. stamped.save
  58590. assert_not_equal stamped.created_at, stamped.updated_at
  58591. assert_not_equal old_updated_at, stamped.updated_at
  58592. end
  58593. def test_create_turned_off
  58594. Mixin.record_timestamps = false
  58595. assert_nil mixins(:tree_1).updated_at
  58596. mixins(:tree_1).save
  58597. assert_nil mixins(:tree_1).updated_at
  58598. Mixin.record_timestamps = true
  58599. end
  58600. end
  58601. class ListSubTest < Test::Unit::TestCase
  58602. fixtures :mixins
  58603. def test_reordering
  58604. assert_equal [mixins(:list_sub_1),
  58605. mixins(:list_sub_2),
  58606. mixins(:list_sub_3),
  58607. mixins(:list_sub_4)],
  58608. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58609. mixins(:list_sub_2).move_lower
  58610. assert_equal [mixins(:list_sub_1),
  58611. mixins(:list_sub_3),
  58612. mixins(:list_sub_2),
  58613. mixins(:list_sub_4)],
  58614. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58615. mixins(:list_sub_2).move_higher
  58616. assert_equal [mixins(:list_sub_1),
  58617. mixins(:list_sub_2),
  58618. mixins(:list_sub_3),
  58619. mixins(:list_sub_4)],
  58620. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58621. mixins(:list_sub_1).move_to_bottom
  58622. assert_equal [mixins(:list_sub_2),
  58623. mixins(:list_sub_3),
  58624. mixins(:list_sub_4),
  58625. mixins(:list_sub_1)],
  58626. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58627. mixins(:list_sub_1).move_to_top
  58628. assert_equal [mixins(:list_sub_1),
  58629. mixins(:list_sub_2),
  58630. mixins(:list_sub_3),
  58631. mixins(:list_sub_4)],
  58632. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58633. mixins(:list_sub_2).move_to_bottom
  58634. assert_equal [mixins(:list_sub_1),
  58635. mixins(:list_sub_3),
  58636. mixins(:list_sub_4),
  58637. mixins(:list_sub_2)],
  58638. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58639. mixins(:list_sub_4).move_to_top
  58640. assert_equal [mixins(:list_sub_4),
  58641. mixins(:list_sub_1),
  58642. mixins(:list_sub_3),
  58643. mixins(:list_sub_2)],
  58644. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58645. end
  58646. def test_move_to_bottom_with_next_to_last_item
  58647. assert_equal [mixins(:list_sub_1),
  58648. mixins(:list_sub_2),
  58649. mixins(:list_sub_3),
  58650. mixins(:list_sub_4)],
  58651. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58652. mixins(:list_sub_3).move_to_bottom
  58653. assert_equal [mixins(:list_sub_1),
  58654. mixins(:list_sub_2),
  58655. mixins(:list_sub_4),
  58656. mixins(:list_sub_3)],
  58657. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58658. end
  58659. def test_next_prev
  58660. assert_equal mixins(:list_sub_2), mixins(:list_sub_1).lower_item
  58661. assert_nil mixins(:list_sub_1).higher_item
  58662. assert_equal mixins(:list_sub_3), mixins(:list_sub_4).higher_item
  58663. assert_nil mixins(:list_sub_4).lower_item
  58664. end
  58665. def test_injection
  58666. item = ListMixin.new("parent_id"=>1)
  58667. assert_equal "parent_id = 1", item.scope_condition
  58668. assert_equal "pos", item.position_column
  58669. end
  58670. def test_insert_at
  58671. new = ListMixin.create("parent_id" => 20)
  58672. assert_equal 1, new.pos
  58673. new = ListMixinSub1.create("parent_id" => 20)
  58674. assert_equal 2, new.pos
  58675. new = ListMixinSub2.create("parent_id" => 20)
  58676. assert_equal 3, new.pos
  58677. new4 = ListMixin.create("parent_id" => 20)
  58678. assert_equal 4, new4.pos
  58679. new4.insert_at(3)
  58680. assert_equal 3, new4.pos
  58681. new.reload
  58682. assert_equal 4, new.pos
  58683. new.insert_at(2)
  58684. assert_equal 2, new.pos
  58685. new4.reload
  58686. assert_equal 4, new4.pos
  58687. new5 = ListMixinSub1.create("parent_id" => 20)
  58688. assert_equal 5, new5.pos
  58689. new5.insert_at(1)
  58690. assert_equal 1, new5.pos
  58691. new4.reload
  58692. assert_equal 5, new4.pos
  58693. end
  58694. def test_delete_middle
  58695. assert_equal [mixins(:list_sub_1),
  58696. mixins(:list_sub_2),
  58697. mixins(:list_sub_3),
  58698. mixins(:list_sub_4)],
  58699. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58700. mixins(:list_sub_2).destroy
  58701. assert_equal [mixins(:list_sub_1, :reload),
  58702. mixins(:list_sub_3, :reload),
  58703. mixins(:list_sub_4, :reload)],
  58704. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58705. assert_equal 1, mixins(:list_sub_1).pos
  58706. assert_equal 2, mixins(:list_sub_3).pos
  58707. assert_equal 3, mixins(:list_sub_4).pos
  58708. mixins(:list_sub_1).destroy
  58709. assert_equal [mixins(:list_sub_3, :reload),
  58710. mixins(:list_sub_4, :reload)],
  58711. ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  58712. assert_equal 1, mixins(:list_sub_3).pos
  58713. assert_equal 2, mixins(:list_sub_4).pos
  58714. end
  58715. end
  58716. require 'abstract_unit'
  58717. require 'fixtures/company_in_module'
  58718. class ModulesTest < Test::Unit::TestCase
  58719. fixtures :accounts, :companies, :projects, :developers
  58720. def test_module_spanning_associations
  58721. assert MyApplication::Business::Firm.find(:first).has_clients?, "Firm should have clients"
  58722. firm = MyApplication::Business::Firm.find(:first)
  58723. assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name"
  58724. assert_equal 2, firm.clients_count, "Firm should have two clients"
  58725. end
  58726. def test_module_spanning_has_and_belongs_to_many_associations
  58727. project = MyApplication::Business::Project.find(:first)
  58728. project.developers << MyApplication::Business::Developer.create("name" => "John")
  58729. assert "John", project.developers.last.name
  58730. end
  58731. def test_associations_spanning_cross_modules
  58732. account = MyApplication::Billing::Account.find(:first, :order => 'id')
  58733. assert_kind_of MyApplication::Business::Firm, account.firm
  58734. assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm
  58735. assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm
  58736. assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_qualified_billing_firm
  58737. assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_unqualified_billing_firm
  58738. end
  58739. end
  58740. require 'abstract_unit'
  58741. require 'fixtures/entrant'
  58742. # So we can test whether Course.connection survives a reload.
  58743. require_dependency 'fixtures/course'
  58744. class MultipleDbTest < Test::Unit::TestCase
  58745. self.use_transactional_fixtures = false
  58746. def setup
  58747. @courses = create_fixtures("courses") { Course.retrieve_connection }
  58748. @entrants = create_fixtures("entrants")
  58749. end
  58750. def test_connected
  58751. assert_not_nil Entrant.connection
  58752. assert_not_nil Course.connection
  58753. end
  58754. def test_proper_connection
  58755. assert_not_equal(Entrant.connection, Course.connection)
  58756. assert_equal(Entrant.connection, Entrant.retrieve_connection)
  58757. assert_equal(Course.connection, Course.retrieve_connection)
  58758. assert_equal(ActiveRecord::Base.connection, Entrant.connection)
  58759. end
  58760. def test_find
  58761. c1 = Course.find(1)
  58762. assert_equal "Ruby Development", c1.name
  58763. c2 = Course.find(2)
  58764. assert_equal "Java Development", c2.name
  58765. e1 = Entrant.find(1)
  58766. assert_equal "Ruby Developer", e1.name
  58767. e2 = Entrant.find(2)
  58768. assert_equal "Ruby Guru", e2.name
  58769. e3 = Entrant.find(3)
  58770. assert_equal "Java Lover", e3.name
  58771. end
  58772. def test_associations
  58773. c1 = Course.find(1)
  58774. assert_equal 2, c1.entrants_count
  58775. e1 = Entrant.find(1)
  58776. assert_equal e1.course.id, c1.id
  58777. c2 = Course.find(2)
  58778. assert_equal 1, c2.entrants_count
  58779. e3 = Entrant.find(3)
  58780. assert_equal e3.course.id, c2.id
  58781. end
  58782. def test_course_connection_should_survive_dependency_reload
  58783. assert Course.connection
  58784. Dependencies.clear
  58785. Object.send(:remove_const, :Course)
  58786. require_dependency 'fixtures/course'
  58787. assert Course.connection
  58788. end
  58789. end
  58790. require "#{File.dirname(__FILE__)}/abstract_unit"
  58791. require 'fixtures/topic'
  58792. require 'fixtures/subscriber'
  58793. require 'fixtures/movie'
  58794. require 'fixtures/keyboard'
  58795. class PrimaryKeysTest < Test::Unit::TestCase
  58796. fixtures :topics, :subscribers, :movies
  58797. def test_integer_key
  58798. topic = Topic.find(1)
  58799. assert_equal(topics(:first).author_name, topic.author_name)
  58800. topic = Topic.find(2)
  58801. assert_equal(topics(:second).author_name, topic.author_name)
  58802. topic = Topic.new
  58803. topic.title = "New Topic"
  58804. assert_equal(nil, topic.id)
  58805. assert_nothing_raised { topic.save! }
  58806. id = topic.id
  58807. topicReloaded = Topic.find(id)
  58808. assert_equal("New Topic", topicReloaded.title)
  58809. end
  58810. def test_customized_primary_key_auto_assigns_on_save
  58811. Keyboard.delete_all
  58812. keyboard = Keyboard.new(:name => 'HHKB')
  58813. assert_nothing_raised { keyboard.save! }
  58814. assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id
  58815. end
  58816. def test_customized_primary_key_can_be_get_before_saving
  58817. keyboard = Keyboard.new
  58818. assert_nil keyboard.id
  58819. assert_nothing_raised { assert_nil keyboard.key_number }
  58820. end
  58821. def test_customized_string_primary_key_settable_before_save
  58822. subscriber = Subscriber.new
  58823. assert_nothing_raised { subscriber.id = 'webster123' }
  58824. assert_equal 'webster123', subscriber.id
  58825. assert_equal 'webster123', subscriber.nick
  58826. end
  58827. def test_string_key
  58828. subscriber = Subscriber.find(subscribers(:first).nick)
  58829. assert_equal(subscribers(:first).name, subscriber.name)
  58830. subscriber = Subscriber.find(subscribers(:second).nick)
  58831. assert_equal(subscribers(:second).name, subscriber.name)
  58832. subscriber = Subscriber.new
  58833. subscriber.id = "jdoe"
  58834. assert_equal("jdoe", subscriber.id)
  58835. subscriber.name = "John Doe"
  58836. assert_nothing_raised { subscriber.save! }
  58837. assert_equal("jdoe", subscriber.id)
  58838. subscriberReloaded = Subscriber.find("jdoe")
  58839. assert_equal("John Doe", subscriberReloaded.name)
  58840. end
  58841. def test_find_with_more_than_one_string_key
  58842. assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length
  58843. end
  58844. def test_primary_key_prefix
  58845. ActiveRecord::Base.primary_key_prefix_type = :table_name
  58846. Topic.reset_primary_key
  58847. assert_equal "topicid", Topic.primary_key
  58848. ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
  58849. Topic.reset_primary_key
  58850. assert_equal "topic_id", Topic.primary_key
  58851. ActiveRecord::Base.primary_key_prefix_type = nil
  58852. Topic.reset_primary_key
  58853. assert_equal "id", Topic.primary_key
  58854. end
  58855. end
  58856. require 'abstract_unit'
  58857. require 'fixtures/post'
  58858. require 'fixtures/comment'
  58859. require 'fixtures/developer'
  58860. require 'fixtures/project'
  58861. require 'fixtures/reader'
  58862. require 'fixtures/person'
  58863. # Dummy class methods to test implicit association scoping.
  58864. def Comment.foo() find :first end
  58865. def Project.foo() find :first end
  58866. class ReadOnlyTest < Test::Unit::TestCase
  58867. fixtures :posts, :comments, :developers, :projects, :developers_projects
  58868. def test_cant_save_readonly_record
  58869. dev = Developer.find(1)
  58870. assert !dev.readonly?
  58871. dev.readonly!
  58872. assert dev.readonly?
  58873. assert_nothing_raised do
  58874. dev.name = 'Luscious forbidden fruit.'
  58875. assert !dev.save
  58876. dev.name = 'Forbidden.'
  58877. end
  58878. assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
  58879. assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
  58880. end
  58881. def test_find_with_readonly_option
  58882. Developer.find(:all).each { |d| assert !d.readonly? }
  58883. Developer.find(:all, :readonly => false).each { |d| assert !d.readonly? }
  58884. Developer.find(:all, :readonly => true).each { |d| assert d.readonly? }
  58885. end
  58886. def test_find_with_joins_option_implies_readonly
  58887. # Blank joins don't count.
  58888. Developer.find(:all, :joins => ' ').each { |d| assert !d.readonly? }
  58889. Developer.find(:all, :joins => ' ', :readonly => false).each { |d| assert !d.readonly? }
  58890. # Others do.
  58891. Developer.find(:all, :joins => ', projects').each { |d| assert d.readonly? }
  58892. Developer.find(:all, :joins => ', projects', :readonly => false).each { |d| assert !d.readonly? }
  58893. end
  58894. def test_habtm_find_readonly
  58895. dev = Developer.find(1)
  58896. assert !dev.projects.empty?
  58897. assert dev.projects.all?(&:readonly?)
  58898. assert dev.projects.find(:all).all?(&:readonly?)
  58899. assert dev.projects.find(:all, :readonly => true).all?(&:readonly?)
  58900. end
  58901. def test_has_many_find_readonly
  58902. post = Post.find(1)
  58903. assert !post.comments.empty?
  58904. assert !post.comments.any?(&:readonly?)
  58905. assert !post.comments.find(:all).any?(&:readonly?)
  58906. assert post.comments.find(:all, :readonly => true).all?(&:readonly?)
  58907. end
  58908. def test_has_many_with_through_is_not_implicitly_marked_readonly
  58909. assert people = Post.find(1).people
  58910. assert !people.any?(&:readonly?)
  58911. end
  58912. def test_readonly_scoping
  58913. Post.with_scope(:find => { :conditions => '1=1' }) do
  58914. assert !Post.find(1).readonly?
  58915. assert Post.find(1, :readonly => true).readonly?
  58916. assert !Post.find(1, :readonly => false).readonly?
  58917. end
  58918. Post.with_scope(:find => { :joins => ' ' }) do
  58919. assert !Post.find(1).readonly?
  58920. assert Post.find(1, :readonly => true).readonly?
  58921. assert !Post.find(1, :readonly => false).readonly?
  58922. end
  58923. # Oracle barfs on this because the join includes unqualified and
  58924. # conflicting column names
  58925. unless current_adapter?(:OracleAdapter)
  58926. Post.with_scope(:find => { :joins => ', developers' }) do
  58927. assert Post.find(1).readonly?
  58928. assert Post.find(1, :readonly => true).readonly?
  58929. assert !Post.find(1, :readonly => false).readonly?
  58930. end
  58931. end
  58932. Post.with_scope(:find => { :readonly => true }) do
  58933. assert Post.find(1).readonly?
  58934. assert Post.find(1, :readonly => true).readonly?
  58935. assert !Post.find(1, :readonly => false).readonly?
  58936. end
  58937. end
  58938. def test_association_collection_method_missing_scoping_not_readonly
  58939. assert !Developer.find(1).projects.foo.readonly?
  58940. assert !Post.find(1).comments.foo.readonly?
  58941. end
  58942. end
  58943. require 'abstract_unit'
  58944. require 'fixtures/topic'
  58945. require 'fixtures/customer'
  58946. require 'fixtures/company'
  58947. require 'fixtures/company_in_module'
  58948. require 'fixtures/subscriber'
  58949. class ReflectionTest < Test::Unit::TestCase
  58950. fixtures :topics, :customers, :companies, :subscribers
  58951. def setup
  58952. @first = Topic.find(1)
  58953. end
  58954. def test_column_null_not_null
  58955. subscriber = Subscriber.find(:first)
  58956. assert subscriber.column_for_attribute("name").null
  58957. assert !subscriber.column_for_attribute("nick").null
  58958. end
  58959. def test_read_attribute_names
  58960. assert_equal(
  58961. %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id type ).sort,
  58962. @first.attribute_names
  58963. )
  58964. end
  58965. def test_columns
  58966. assert_equal 12, Topic.columns.length
  58967. end
  58968. def test_columns_are_returned_in_the_order_they_were_declared
  58969. column_names = Topic.columns.map { |column| column.name }
  58970. assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id type), column_names
  58971. end
  58972. def test_content_columns
  58973. content_columns = Topic.content_columns
  58974. content_column_names = content_columns.map {|column| column.name}
  58975. assert_equal 8, content_columns.length
  58976. assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved).sort, content_column_names.sort
  58977. end
  58978. def test_column_string_type_and_limit
  58979. assert_equal :string, @first.column_for_attribute("title").type
  58980. assert_equal 255, @first.column_for_attribute("title").limit
  58981. end
  58982. def test_human_name_for_column
  58983. assert_equal "Author name", @first.column_for_attribute("author_name").human_name
  58984. end
  58985. def test_integer_columns
  58986. assert_equal :integer, @first.column_for_attribute("id").type
  58987. end
  58988. def test_aggregation_reflection
  58989. reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new(
  58990. :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
  58991. )
  58992. reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new(
  58993. :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
  58994. )
  58995. reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new(
  58996. :composed_of, :gps_location, { }, Customer
  58997. )
  58998. assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
  58999. assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
  59000. assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
  59001. assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
  59002. assert_equal Address, Customer.reflect_on_aggregation(:address).klass
  59003. assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
  59004. end
  59005. def test_has_many_reflection
  59006. reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
  59007. assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
  59008. assert_equal Client, Firm.reflect_on_association(:clients).klass
  59009. assert_equal 'companies', Firm.reflect_on_association(:clients).table_name
  59010. assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass
  59011. assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name
  59012. end
  59013. def test_has_one_reflection
  59014. reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
  59015. assert_equal reflection_for_account, Firm.reflect_on_association(:account)
  59016. assert_equal Account, Firm.reflect_on_association(:account).klass
  59017. assert_equal 'accounts', Firm.reflect_on_association(:account).table_name
  59018. end
  59019. def test_association_reflection_in_modules
  59020. assert_reflection MyApplication::Business::Firm,
  59021. :clients_of_firm,
  59022. :klass => MyApplication::Business::Client,
  59023. :class_name => 'Client',
  59024. :table_name => 'companies'
  59025. assert_reflection MyApplication::Billing::Account,
  59026. :firm,
  59027. :klass => MyApplication::Business::Firm,
  59028. :class_name => 'MyApplication::Business::Firm',
  59029. :table_name => 'companies'
  59030. assert_reflection MyApplication::Billing::Account,
  59031. :qualified_billing_firm,
  59032. :klass => MyApplication::Billing::Firm,
  59033. :class_name => 'MyApplication::Billing::Firm',
  59034. :table_name => 'companies'
  59035. assert_reflection MyApplication::Billing::Account,
  59036. :unqualified_billing_firm,
  59037. :klass => MyApplication::Billing::Firm,
  59038. :class_name => 'Firm',
  59039. :table_name => 'companies'
  59040. assert_reflection MyApplication::Billing::Account,
  59041. :nested_qualified_billing_firm,
  59042. :klass => MyApplication::Billing::Nested::Firm,
  59043. :class_name => 'MyApplication::Billing::Nested::Firm',
  59044. :table_name => 'companies'
  59045. assert_reflection MyApplication::Billing::Account,
  59046. :nested_unqualified_billing_firm,
  59047. :klass => MyApplication::Billing::Nested::Firm,
  59048. :class_name => 'Nested::Firm',
  59049. :table_name => 'companies'
  59050. end
  59051. def test_reflection_of_all_associations
  59052. assert_equal 13, Firm.reflect_on_all_associations.size
  59053. assert_equal 11, Firm.reflect_on_all_associations(:has_many).size
  59054. assert_equal 2, Firm.reflect_on_all_associations(:has_one).size
  59055. assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
  59056. end
  59057. private
  59058. def assert_reflection(klass, association, options)
  59059. assert reflection = klass.reflect_on_association(association)
  59060. options.each do |method, value|
  59061. assert_equal(value, reflection.send(method))
  59062. end
  59063. end
  59064. end
  59065. require 'abstract_unit'
  59066. require "#{File.dirname(__FILE__)}/../lib/active_record/schema_dumper"
  59067. require 'stringio'
  59068. if ActiveRecord::Base.connection.respond_to?(:tables)
  59069. class SchemaDumperTest < Test::Unit::TestCase
  59070. def test_schema_dump
  59071. stream = StringIO.new
  59072. ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
  59073. output = stream.string
  59074. assert_match %r{create_table "accounts"}, output
  59075. assert_match %r{create_table "authors"}, output
  59076. assert_no_match %r{create_table "schema_info"}, output
  59077. end
  59078. def test_schema_dump_includes_not_null_columns
  59079. stream = StringIO.new
  59080. ActiveRecord::SchemaDumper.ignore_tables = [/^[^s]/]
  59081. ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
  59082. output = stream.string
  59083. assert_match %r{:null => false}, output
  59084. end
  59085. def test_schema_dump_with_string_ignored_table
  59086. stream = StringIO.new
  59087. ActiveRecord::SchemaDumper.ignore_tables = ['accounts']
  59088. ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
  59089. output = stream.string
  59090. assert_no_match %r{create_table "accounts"}, output
  59091. assert_match %r{create_table "authors"}, output
  59092. assert_no_match %r{create_table "schema_info"}, output
  59093. end
  59094. def test_schema_dump_with_regexp_ignored_table
  59095. stream = StringIO.new
  59096. ActiveRecord::SchemaDumper.ignore_tables = [/^account/]
  59097. ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
  59098. output = stream.string
  59099. assert_no_match %r{create_table "accounts"}, output
  59100. assert_match %r{create_table "authors"}, output
  59101. assert_no_match %r{create_table "schema_info"}, output
  59102. end
  59103. def test_schema_dump_illegal_ignored_table_value
  59104. stream = StringIO.new
  59105. ActiveRecord::SchemaDumper.ignore_tables = [5]
  59106. assert_raise(StandardError) do
  59107. ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
  59108. end
  59109. end
  59110. end
  59111. end
  59112. require 'abstract_unit'
  59113. class SchemaTest < Test::Unit::TestCase
  59114. self.use_transactional_fixtures = false
  59115. SCHEMA_NAME = 'test_schema'
  59116. TABLE_NAME = 'things'
  59117. COLUMNS = [
  59118. 'id integer',
  59119. 'name character varying(50)',
  59120. 'moment timestamp without time zone default now()'
  59121. ]
  59122. def setup
  59123. @connection = ActiveRecord::Base.connection
  59124. @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
  59125. end
  59126. def teardown
  59127. @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
  59128. end
  59129. def test_with_schema_prefixed_table_name
  59130. assert_nothing_raised do
  59131. assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}")
  59132. end
  59133. end
  59134. def test_with_schema_search_path
  59135. assert_nothing_raised do
  59136. with_schema_search_path(SCHEMA_NAME) do
  59137. assert_equal COLUMNS, columns(TABLE_NAME)
  59138. end
  59139. end
  59140. end
  59141. def test_raise_on_unquoted_schema_name
  59142. assert_raise(ActiveRecord::StatementInvalid) do
  59143. with_schema_search_path '$user,public'
  59144. end
  59145. end
  59146. def test_without_schema_search_path
  59147. assert_raise(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
  59148. end
  59149. def test_ignore_nil_schema_search_path
  59150. assert_nothing_raised { with_schema_search_path nil }
  59151. end
  59152. private
  59153. def columns(table_name)
  59154. @connection.send(:column_definitions, table_name).map do |name, type, default|
  59155. "#{name} #{type}" + (default ? " default #{default}" : '')
  59156. end
  59157. end
  59158. def with_schema_search_path(schema_search_path)
  59159. @connection.schema_search_path = schema_search_path
  59160. yield if block_given?
  59161. ensure
  59162. @connection.schema_search_path = "'$user', public"
  59163. end
  59164. end
  59165. require 'abstract_unit'
  59166. require 'fixtures/topic'
  59167. require 'fixtures/subject'
  59168. # confirm that synonyms work just like tables; in this case
  59169. # the "subjects" table in Oracle (defined in oci.sql) is just
  59170. # a synonym to the "topics" table
  59171. class TestOracleSynonym < Test::Unit::TestCase
  59172. def test_oracle_synonym
  59173. topic = Topic.new
  59174. subject = Subject.new
  59175. assert_equal(topic.attributes, subject.attributes)
  59176. end
  59177. end
  59178. require 'abstract_unit'
  59179. require 'fixtures/topic'
  59180. class ThreadedConnectionsTest < Test::Unit::TestCase
  59181. self.use_transactional_fixtures = false
  59182. fixtures :topics
  59183. def setup
  59184. @connection = ActiveRecord::Base.remove_connection
  59185. @connections = []
  59186. @allow_concurrency = ActiveRecord::Base.allow_concurrency
  59187. end
  59188. def teardown
  59189. # clear the connection cache
  59190. ActiveRecord::Base.send(:clear_all_cached_connections!)
  59191. # set allow_concurrency to saved value
  59192. ActiveRecord::Base.allow_concurrency = @allow_concurrency
  59193. # reestablish old connection
  59194. ActiveRecord::Base.establish_connection(@connection)
  59195. end
  59196. def gather_connections(use_threaded_connections)
  59197. ActiveRecord::Base.allow_concurrency = use_threaded_connections
  59198. ActiveRecord::Base.establish_connection(@connection)
  59199. 5.times do
  59200. Thread.new do
  59201. Topic.find :first
  59202. @connections << ActiveRecord::Base.active_connections.values.first
  59203. end.join
  59204. end
  59205. end
  59206. def test_threaded_connections
  59207. gather_connections(true)
  59208. assert_equal @connections.uniq.length, 5
  59209. end
  59210. def test_unthreaded_connections
  59211. gather_connections(false)
  59212. assert_equal @connections.uniq.length, 1
  59213. end
  59214. end
  59215. require 'abstract_unit'
  59216. require 'fixtures/topic'
  59217. require 'fixtures/developer'
  59218. class TransactionTest < Test::Unit::TestCase
  59219. self.use_transactional_fixtures = false
  59220. fixtures :topics, :developers
  59221. def setup
  59222. # sqlite does not seem to return these in the right order, so we sort them
  59223. # explicitly for sqlite's sake. sqlite3 does fine.
  59224. @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
  59225. end
  59226. def test_successful
  59227. Topic.transaction do
  59228. @first.approved = true
  59229. @second.approved = false
  59230. @first.save
  59231. @second.save
  59232. end
  59233. assert Topic.find(1).approved?, "First should have been approved"
  59234. assert !Topic.find(2).approved?, "Second should have been unapproved"
  59235. end
  59236. def transaction_with_return
  59237. Topic.transaction do
  59238. @first.approved = true
  59239. @second.approved = false
  59240. @first.save
  59241. @second.save
  59242. return
  59243. end
  59244. end
  59245. def test_successful_with_return
  59246. class << Topic.connection
  59247. alias :real_commit_db_transaction :commit_db_transaction
  59248. def commit_db_transaction
  59249. $committed = true
  59250. real_commit_db_transaction
  59251. end
  59252. end
  59253. $committed = false
  59254. transaction_with_return
  59255. assert $committed
  59256. assert Topic.find(1).approved?, "First should have been approved"
  59257. assert !Topic.find(2).approved?, "Second should have been unapproved"
  59258. ensure
  59259. class << Topic.connection
  59260. alias :commit_db_transaction :real_commit_db_transaction rescue nil
  59261. end
  59262. end
  59263. def test_successful_with_instance_method
  59264. @first.transaction do
  59265. @first.approved = true
  59266. @second.approved = false
  59267. @first.save
  59268. @second.save
  59269. end
  59270. assert Topic.find(1).approved?, "First should have been approved"
  59271. assert !Topic.find(2).approved?, "Second should have been unapproved"
  59272. end
  59273. def test_failing_on_exception
  59274. begin
  59275. Topic.transaction do
  59276. @first.approved = true
  59277. @second.approved = false
  59278. @first.save
  59279. @second.save
  59280. raise "Bad things!"
  59281. end
  59282. rescue
  59283. # caught it
  59284. end
  59285. assert @first.approved?, "First should still be changed in the objects"
  59286. assert !@second.approved?, "Second should still be changed in the objects"
  59287. assert !Topic.find(1).approved?, "First shouldn't have been approved"
  59288. assert Topic.find(2).approved?, "Second should still be approved"
  59289. end
  59290. def test_failing_with_object_rollback
  59291. assert !@first.approved?, "First should be unapproved initially"
  59292. begin
  59293. Topic.transaction(@first, @second) do
  59294. @first.approved = true
  59295. @second.approved = false
  59296. @first.save
  59297. @second.save
  59298. raise "Bad things!"
  59299. end
  59300. rescue
  59301. # caught it
  59302. end
  59303. assert !@first.approved?, "First shouldn't have been approved"
  59304. assert @second.approved?, "Second should still be approved"
  59305. end
  59306. def test_callback_rollback_in_save
  59307. add_exception_raising_after_save_callback_to_topic
  59308. begin
  59309. @first.approved = true
  59310. @first.save
  59311. flunk
  59312. rescue => e
  59313. assert_equal "Make the transaction rollback", e.message
  59314. assert !Topic.find(1).approved?
  59315. ensure
  59316. remove_exception_raising_after_save_callback_to_topic
  59317. end
  59318. end
  59319. def test_nested_explicit_transactions
  59320. Topic.transaction do
  59321. Topic.transaction do
  59322. @first.approved = true
  59323. @second.approved = false
  59324. @first.save
  59325. @second.save
  59326. end
  59327. end
  59328. assert Topic.find(1).approved?, "First should have been approved"
  59329. assert !Topic.find(2).approved?, "Second should have been unapproved"
  59330. end
  59331. # This will cause transactions to overlap and fail unless they are
  59332. # performed on separate database connections.
  59333. def test_transaction_per_thread
  59334. assert_nothing_raised do
  59335. threads = (1..20).map do
  59336. Thread.new do
  59337. Topic.transaction do
  59338. topic = Topic.find(:first)
  59339. topic.approved = !topic.approved?
  59340. topic.save!
  59341. topic.approved = !topic.approved?
  59342. topic.save!
  59343. end
  59344. end
  59345. end
  59346. threads.each { |t| t.join }
  59347. end
  59348. end
  59349. # Test for dirty reads among simultaneous transactions.
  59350. def test_transaction_isolation__read_committed
  59351. # Should be invariant.
  59352. original_salary = Developer.find(1).salary
  59353. temporary_salary = 200000
  59354. assert_nothing_raised do
  59355. threads = (1..20).map do
  59356. Thread.new do
  59357. Developer.transaction do
  59358. # Expect original salary.
  59359. dev = Developer.find(1)
  59360. assert_equal original_salary, dev.salary
  59361. dev.salary = temporary_salary
  59362. dev.save!
  59363. # Expect temporary salary.
  59364. dev = Developer.find(1)
  59365. assert_equal temporary_salary, dev.salary
  59366. dev.salary = original_salary
  59367. dev.save!
  59368. # Expect original salary.
  59369. dev = Developer.find(1)
  59370. assert_equal original_salary, dev.salary
  59371. end
  59372. end
  59373. end
  59374. # Keep our eyes peeled.
  59375. threads << Thread.new do
  59376. 10.times do
  59377. sleep 0.05
  59378. Developer.transaction do
  59379. # Always expect original salary.
  59380. assert_equal original_salary, Developer.find(1).salary
  59381. end
  59382. end
  59383. end
  59384. threads.each { |t| t.join }
  59385. end
  59386. assert_equal original_salary, Developer.find(1).salary
  59387. end
  59388. private
  59389. def add_exception_raising_after_save_callback_to_topic
  59390. Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
  59391. end
  59392. def remove_exception_raising_after_save_callback_to_topic
  59393. Topic.class_eval { remove_method :after_save }
  59394. end
  59395. end
  59396. require 'abstract_unit'
  59397. class TestRecord < ActiveRecord::Base
  59398. end
  59399. class TestUnconnectedAdaptor < Test::Unit::TestCase
  59400. self.use_transactional_fixtures = false
  59401. def setup
  59402. @underlying = ActiveRecord::Base.connection
  59403. @specification = ActiveRecord::Base.remove_connection
  59404. end
  59405. def teardown
  59406. @underlying = nil
  59407. ActiveRecord::Base.establish_connection(@specification)
  59408. end
  59409. def test_connection_no_longer_established
  59410. assert_raise(ActiveRecord::ConnectionNotEstablished) do
  59411. TestRecord.find(1)
  59412. end
  59413. assert_raise(ActiveRecord::ConnectionNotEstablished) do
  59414. TestRecord.new.save
  59415. end
  59416. end
  59417. def test_underlying_adapter_no_longer_active
  59418. assert !@underlying.active?, "Removed adapter should no longer be active"
  59419. end
  59420. end
  59421. require 'abstract_unit'
  59422. require 'fixtures/topic'
  59423. require 'fixtures/reply'
  59424. require 'fixtures/developer'
  59425. # The following methods in Topic are used in test_conditional_validation_*
  59426. class Topic
  59427. def condition_is_true
  59428. return true
  59429. end
  59430. def condition_is_true_but_its_not
  59431. return false
  59432. end
  59433. end
  59434. class ValidationsTest < Test::Unit::TestCase
  59435. fixtures :topics, :developers
  59436. def setup
  59437. Topic.write_inheritable_attribute(:validate, nil)
  59438. Topic.write_inheritable_attribute(:validate_on_create, nil)
  59439. Topic.write_inheritable_attribute(:validate_on_update, nil)
  59440. end
  59441. def test_single_field_validation
  59442. r = Reply.new
  59443. r.title = "There's no content!"
  59444. assert !r.save, "A reply without content shouldn't be saveable"
  59445. r.content = "Messa content!"
  59446. assert r.save, "A reply with content should be saveable"
  59447. end
  59448. def test_single_attr_validation_and_error_msg
  59449. r = Reply.new
  59450. r.title = "There's no content!"
  59451. r.save
  59452. assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
  59453. assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
  59454. assert_equal 1, r.errors.count
  59455. end
  59456. def test_double_attr_validation_and_error_msg
  59457. r = Reply.new
  59458. assert !r.save
  59459. assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
  59460. assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
  59461. assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
  59462. assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
  59463. assert_equal 2, r.errors.count
  59464. end
  59465. def test_error_on_create
  59466. r = Reply.new
  59467. r.title = "Wrong Create"
  59468. assert !r.save
  59469. assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
  59470. assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
  59471. end
  59472. def test_error_on_update
  59473. r = Reply.new
  59474. r.title = "Bad"
  59475. r.content = "Good"
  59476. assert r.save, "First save should be successful"
  59477. r.title = "Wrong Update"
  59478. assert !r.save, "Second save should fail"
  59479. assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
  59480. assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
  59481. end
  59482. def test_invalid_record_exception
  59483. assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
  59484. assert_raises(ActiveRecord::RecordInvalid) { Reply.new.save! }
  59485. begin
  59486. r = Reply.new
  59487. r.save!
  59488. flunk
  59489. rescue ActiveRecord::RecordInvalid => invalid
  59490. assert_equal r, invalid.record
  59491. end
  59492. end
  59493. def test_single_error_per_attr_iteration
  59494. r = Reply.new
  59495. r.save
  59496. errors = []
  59497. r.errors.each { |attr, msg| errors << [attr, msg] }
  59498. assert errors.include?(["title", "Empty"])
  59499. assert errors.include?(["content", "Empty"])
  59500. end
  59501. def test_multiple_errors_per_attr_iteration_with_full_error_composition
  59502. r = Reply.new
  59503. r.title = "Wrong Create"
  59504. r.content = "Mismatch"
  59505. r.save
  59506. errors = []
  59507. r.errors.each_full { |error| errors << error }
  59508. assert_equal "Title is Wrong Create", errors[0]
  59509. assert_equal "Title is Content Mismatch", errors[1]
  59510. assert_equal 2, r.errors.count
  59511. end
  59512. def test_errors_on_base
  59513. r = Reply.new
  59514. r.content = "Mismatch"
  59515. r.save
  59516. r.errors.add_to_base "Reply is not dignifying"
  59517. errors = []
  59518. r.errors.each_full { |error| errors << error }
  59519. assert_equal "Reply is not dignifying", r.errors.on_base
  59520. assert errors.include?("Title Empty")
  59521. assert errors.include?("Reply is not dignifying")
  59522. assert_equal 2, r.errors.count
  59523. end
  59524. def test_create_without_validation
  59525. reply = Reply.new
  59526. assert !reply.save
  59527. assert reply.save(false)
  59528. end
  59529. def test_validates_each
  59530. perform = true
  59531. hits = 0
  59532. Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
  59533. if perform
  59534. record.errors.add attr, 'gotcha'
  59535. hits += 1
  59536. end
  59537. end
  59538. t = Topic.new("title" => "valid", "content" => "whatever")
  59539. assert !t.save
  59540. assert_equal 4, hits
  59541. assert_equal %w(gotcha gotcha), t.errors.on(:title)
  59542. assert_equal %w(gotcha gotcha), t.errors.on(:content)
  59543. ensure
  59544. perform = false
  59545. end
  59546. def test_errors_on_boundary_breaking
  59547. developer = Developer.new("name" => "xs")
  59548. assert !developer.save
  59549. assert_equal "is too short (minimum is 3 characters)", developer.errors.on("name")
  59550. developer.name = "All too very long for this boundary, it really is"
  59551. assert !developer.save
  59552. assert_equal "is too long (maximum is 20 characters)", developer.errors.on("name")
  59553. developer.name = "Just right"
  59554. assert developer.save
  59555. end
  59556. def test_title_confirmation_no_confirm
  59557. Topic.validates_confirmation_of(:title)
  59558. t = Topic.create("title" => "We should not be confirmed")
  59559. assert t.save
  59560. end
  59561. def test_title_confirmation
  59562. Topic.validates_confirmation_of(:title)
  59563. t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
  59564. assert !t.save
  59565. t.title_confirmation = "We should be confirmed"
  59566. assert t.save
  59567. end
  59568. def test_terms_of_service_agreement_no_acceptance
  59569. Topic.validates_acceptance_of(:terms_of_service, :on => :create)
  59570. t = Topic.create("title" => "We should not be confirmed")
  59571. assert t.save
  59572. end
  59573. def test_terms_of_service_agreement
  59574. Topic.validates_acceptance_of(:terms_of_service, :on => :create)
  59575. t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
  59576. assert !t.save
  59577. assert_equal "must be accepted", t.errors.on(:terms_of_service)
  59578. t.terms_of_service = "1"
  59579. assert t.save
  59580. end
  59581. def test_eula
  59582. Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
  59583. t = Topic.create("title" => "We should be confirmed","eula" => "")
  59584. assert !t.save
  59585. assert_equal "must be abided", t.errors.on(:eula)
  59586. t.eula = "1"
  59587. assert t.save
  59588. end
  59589. def test_terms_of_service_agreement_with_accept_value
  59590. Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
  59591. t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
  59592. assert !t.save
  59593. assert_equal "must be accepted", t.errors.on(:terms_of_service)
  59594. t.terms_of_service = "I agree."
  59595. assert t.save
  59596. end
  59597. def test_validate_presences
  59598. Topic.validates_presence_of(:title, :content)
  59599. t = Topic.create
  59600. assert !t.save
  59601. assert_equal "can't be blank", t.errors.on(:title)
  59602. assert_equal "can't be blank", t.errors.on(:content)
  59603. t.title = "something"
  59604. t.content = " "
  59605. assert !t.save
  59606. assert_equal "can't be blank", t.errors.on(:content)
  59607. t.content = "like stuff"
  59608. assert t.save
  59609. end
  59610. def test_validate_uniqueness
  59611. Topic.validates_uniqueness_of(:title)
  59612. t = Topic.new("title" => "I'm unique!")
  59613. assert t.save, "Should save t as unique"
  59614. t.content = "Remaining unique"
  59615. assert t.save, "Should still save t as unique"
  59616. t2 = Topic.new("title" => "I'm unique!")
  59617. assert !t2.valid?, "Shouldn't be valid"
  59618. assert !t2.save, "Shouldn't save t2 as unique"
  59619. assert_equal "has already been taken", t2.errors.on(:title)
  59620. t2.title = "Now Im really also unique"
  59621. assert t2.save, "Should now save t2 as unique"
  59622. end
  59623. def test_validate_uniqueness_with_scope
  59624. Reply.validates_uniqueness_of(:content, :scope => "parent_id")
  59625. t = Topic.create("title" => "I'm unique!")
  59626. r1 = t.replies.create "title" => "r1", "content" => "hello world"
  59627. assert r1.valid?, "Saving r1"
  59628. r2 = t.replies.create "title" => "r2", "content" => "hello world"
  59629. assert !r2.valid?, "Saving r2 first time"
  59630. r2.content = "something else"
  59631. assert r2.save, "Saving r2 second time"
  59632. t2 = Topic.create("title" => "I'm unique too!")
  59633. r3 = t2.replies.create "title" => "r3", "content" => "hello world"
  59634. assert r3.valid?, "Saving r3"
  59635. end
  59636. def test_validate_uniqueness_with_scope_array
  59637. Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
  59638. t = Topic.create("title" => "The earth is actually flat!")
  59639. r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
  59640. assert r1.valid?, "Saving r1"
  59641. r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
  59642. assert !r2.valid?, "Saving r2. Double reply by same author."
  59643. r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
  59644. assert r2.save, "Saving r2 the second time."
  59645. r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
  59646. assert !r3.valid?, "Saving r3"
  59647. r3.author_name = "jj"
  59648. assert r3.save, "Saving r3 the second time."
  59649. r3.author_name = "jeremy"
  59650. assert !r3.save, "Saving r3 the third time."
  59651. end
  59652. def test_validate_format
  59653. Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
  59654. t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
  59655. assert !t.valid?, "Shouldn't be valid"
  59656. assert !t.save, "Shouldn't save because it's invalid"
  59657. assert_equal "is bad data", t.errors.on(:title)
  59658. assert_nil t.errors.on(:content)
  59659. t.title = "Validation macros rule!"
  59660. assert t.save
  59661. assert_nil t.errors.on(:title)
  59662. assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
  59663. end
  59664. # testing ticket #3142
  59665. def test_validate_format_numeric
  59666. Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
  59667. t = Topic.create("title" => "72x", "content" => "6789")
  59668. assert !t.valid?, "Shouldn't be valid"
  59669. assert !t.save, "Shouldn't save because it's invalid"
  59670. assert_equal "is bad data", t.errors.on(:title)
  59671. assert_nil t.errors.on(:content)
  59672. t.title = "-11"
  59673. assert !t.valid?, "Shouldn't be valid"
  59674. t.title = "03"
  59675. assert !t.valid?, "Shouldn't be valid"
  59676. t.title = "z44"
  59677. assert !t.valid?, "Shouldn't be valid"
  59678. t.title = "5v7"
  59679. assert !t.valid?, "Shouldn't be valid"
  59680. t.title = "1"
  59681. assert t.save
  59682. assert_nil t.errors.on(:title)
  59683. end
  59684. def test_validates_inclusion_of
  59685. Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
  59686. assert !Topic.create("title" => "a!", "content" => "abc").valid?
  59687. assert !Topic.create("title" => "a b", "content" => "abc").valid?
  59688. assert !Topic.create("title" => nil, "content" => "def").valid?
  59689. t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
  59690. assert t.valid?
  59691. t.title = "uhoh"
  59692. assert !t.valid?
  59693. assert t.errors.on(:title)
  59694. assert_equal "is not included in the list", t.errors["title"]
  59695. assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
  59696. assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
  59697. assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
  59698. assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
  59699. assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
  59700. end
  59701. def test_validates_inclusion_of_with_allow_nil
  59702. Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
  59703. assert !Topic.create("title" => "a!", "content" => "abc").valid?
  59704. assert !Topic.create("title" => "", "content" => "abc").valid?
  59705. assert Topic.create("title" => nil, "content" => "abc").valid?
  59706. end
  59707. def test_numericality_with_allow_nil_and_getter_method
  59708. Developer.validates_numericality_of( :salary, :allow_nil => true)
  59709. developer = Developer.new("name" => "michael", "salary" => nil)
  59710. developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
  59711. assert developer.valid?
  59712. end
  59713. def test_validates_exclusion_of
  59714. Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
  59715. assert Topic.create("title" => "something", "content" => "abc").valid?
  59716. assert !Topic.create("title" => "monkey", "content" => "abc").valid?
  59717. end
  59718. def test_validates_length_of_using_minimum
  59719. Topic.validates_length_of :title, :minimum => 5
  59720. t = Topic.create("title" => "valid", "content" => "whatever")
  59721. assert t.valid?
  59722. t.title = "not"
  59723. assert !t.valid?
  59724. assert t.errors.on(:title)
  59725. assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
  59726. t.title = ""
  59727. assert !t.valid?
  59728. assert t.errors.on(:title)
  59729. assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
  59730. t.title = nil
  59731. assert !t.valid?
  59732. assert t.errors.on(:title)
  59733. assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
  59734. end
  59735. def test_optionally_validates_length_of_using_minimum
  59736. Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
  59737. t = Topic.create("title" => "valid", "content" => "whatever")
  59738. assert t.valid?
  59739. t.title = nil
  59740. assert t.valid?
  59741. end
  59742. def test_validates_length_of_using_maximum
  59743. Topic.validates_length_of :title, :maximum => 5
  59744. t = Topic.create("title" => "valid", "content" => "whatever")
  59745. assert t.valid?
  59746. t.title = "notvalid"
  59747. assert !t.valid?
  59748. assert t.errors.on(:title)
  59749. assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
  59750. t.title = ""
  59751. assert t.valid?
  59752. t.title = nil
  59753. assert !t.valid?
  59754. end
  59755. def test_optionally_validates_length_of_using_maximum
  59756. Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
  59757. t = Topic.create("title" => "valid", "content" => "whatever")
  59758. assert t.valid?
  59759. t.title = nil
  59760. assert t.valid?
  59761. end
  59762. def test_validates_length_of_using_within
  59763. Topic.validates_length_of(:title, :content, :within => 3..5)
  59764. t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
  59765. assert !t.valid?
  59766. assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
  59767. assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
  59768. t.title = nil
  59769. t.content = nil
  59770. assert !t.valid?
  59771. assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
  59772. assert_equal "is too short (minimum is 3 characters)", t.errors.on(:content)
  59773. t.title = "abe"
  59774. t.content = "mad"
  59775. assert t.valid?
  59776. end
  59777. def test_optionally_validates_length_of_using_within
  59778. Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
  59779. t = Topic.create('title' => 'abc', 'content' => 'abcd')
  59780. assert t.valid?
  59781. t.title = nil
  59782. assert t.valid?
  59783. end
  59784. def test_optionally_validates_length_of_using_within_on_create
  59785. Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %d"
  59786. t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
  59787. assert !t.save
  59788. assert t.errors.on(:title)
  59789. assert_equal "my string is too long: 10", t.errors[:title]
  59790. t.title = "butthisis"
  59791. assert t.save
  59792. t.title = "few"
  59793. assert t.save
  59794. t.content = "andthisislong"
  59795. assert t.save
  59796. t.content = t.title = "iamfine"
  59797. assert t.save
  59798. end
  59799. def test_optionally_validates_length_of_using_within_on_update
  59800. Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %d"
  59801. t = Topic.create("title" => "vali", "content" => "whatever")
  59802. assert !t.save
  59803. assert t.errors.on(:title)
  59804. t.title = "not"
  59805. assert !t.save
  59806. assert t.errors.on(:title)
  59807. assert_equal "my string is too short: 5", t.errors[:title]
  59808. t.title = "valid"
  59809. t.content = "andthisistoolong"
  59810. assert !t.save
  59811. assert t.errors.on(:content)
  59812. t.content = "iamfine"
  59813. assert t.save
  59814. end
  59815. def test_validates_length_of_using_is
  59816. Topic.validates_length_of :title, :is => 5
  59817. t = Topic.create("title" => "valid", "content" => "whatever")
  59818. assert t.valid?
  59819. t.title = "notvalid"
  59820. assert !t.valid?
  59821. assert t.errors.on(:title)
  59822. assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
  59823. t.title = ""
  59824. assert !t.valid?
  59825. t.title = nil
  59826. assert !t.valid?
  59827. end
  59828. def test_optionally_validates_length_of_using_is
  59829. Topic.validates_length_of :title, :is => 5, :allow_nil => true
  59830. t = Topic.create("title" => "valid", "content" => "whatever")
  59831. assert t.valid?
  59832. t.title = nil
  59833. assert t.valid?
  59834. end
  59835. def test_validates_length_of_using_bignum
  59836. bigmin = 2 ** 30
  59837. bigmax = 2 ** 32
  59838. bigrange = bigmin...bigmax
  59839. assert_nothing_raised do
  59840. Topic.validates_length_of :title, :is => bigmin + 5
  59841. Topic.validates_length_of :title, :within => bigrange
  59842. Topic.validates_length_of :title, :in => bigrange
  59843. Topic.validates_length_of :title, :minimum => bigmin
  59844. Topic.validates_length_of :title, :maximum => bigmax
  59845. end
  59846. end
  59847. def test_validates_length_with_globaly_modified_error_message
  59848. ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
  59849. Topic.validates_length_of :title, :minimum => 10
  59850. t = Topic.create(:title => 'too short')
  59851. assert !t.valid?
  59852. assert_equal 'tu est trops petit hombre 10', t.errors['title']
  59853. end
  59854. def test_validates_size_of_association
  59855. assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
  59856. t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
  59857. assert !t.save
  59858. assert t.errors.on(:replies)
  59859. t.replies.create('title' => 'areply', 'content' => 'whateveragain')
  59860. assert t.valid?
  59861. end
  59862. def test_validates_length_of_nasty_params
  59863. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
  59864. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
  59865. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
  59866. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
  59867. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
  59868. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
  59869. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
  59870. assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
  59871. end
  59872. def test_validates_length_of_custom_errors_for_minimum_with_message
  59873. Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" )
  59874. t = Topic.create("title" => "uhoh", "content" => "whatever")
  59875. assert !t.valid?
  59876. assert t.errors.on(:title)
  59877. assert_equal "boo 5", t.errors["title"]
  59878. end
  59879. def test_validates_length_of_custom_errors_for_minimum_with_too_short
  59880. Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" )
  59881. t = Topic.create("title" => "uhoh", "content" => "whatever")
  59882. assert !t.valid?
  59883. assert t.errors.on(:title)
  59884. assert_equal "hoo 5", t.errors["title"]
  59885. end
  59886. def test_validates_length_of_custom_errors_for_maximum_with_message
  59887. Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" )
  59888. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  59889. assert !t.valid?
  59890. assert t.errors.on(:title)
  59891. assert_equal "boo 5", t.errors["title"]
  59892. end
  59893. def test_validates_length_of_custom_errors_for_maximum_with_too_long
  59894. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" )
  59895. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  59896. assert !t.valid?
  59897. assert t.errors.on(:title)
  59898. assert_equal "hoo 5", t.errors["title"]
  59899. end
  59900. def test_validates_length_of_custom_errors_for_is_with_message
  59901. Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" )
  59902. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  59903. assert !t.valid?
  59904. assert t.errors.on(:title)
  59905. assert_equal "boo 5", t.errors["title"]
  59906. end
  59907. def test_validates_length_of_custom_errors_for_is_with_wrong_length
  59908. Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" )
  59909. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  59910. assert !t.valid?
  59911. assert t.errors.on(:title)
  59912. assert_equal "hoo 5", t.errors["title"]
  59913. end
  59914. def kcode_scope(kcode)
  59915. orig_kcode = $KCODE
  59916. $KCODE = kcode
  59917. begin
  59918. yield
  59919. ensure
  59920. $KCODE = orig_kcode
  59921. end
  59922. end
  59923. def test_validates_length_of_using_minimum_utf8
  59924. kcode_scope('UTF8') do
  59925. Topic.validates_length_of :title, :minimum => 5
  59926. t = Topic.create("title" => "一二三四五", "content" => "whatever")
  59927. assert t.valid?
  59928. t.title = "一二三四"
  59929. assert !t.valid?
  59930. assert t.errors.on(:title)
  59931. assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
  59932. end
  59933. end
  59934. def test_validates_length_of_using_maximum_utf8
  59935. kcode_scope('UTF8') do
  59936. Topic.validates_length_of :title, :maximum => 5
  59937. t = Topic.create("title" => "一二三四五", "content" => "whatever")
  59938. assert t.valid?
  59939. t.title = "一二34五六"
  59940. assert !t.valid?
  59941. assert t.errors.on(:title)
  59942. assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
  59943. end
  59944. end
  59945. def test_validates_length_of_using_within_utf8
  59946. kcode_scope('UTF8') do
  59947. Topic.validates_length_of(:title, :content, :within => 3..5)
  59948. t = Topic.new("title" => "一二", "content" => "12三四五六七")
  59949. assert !t.valid?
  59950. assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
  59951. assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
  59952. t.title = "一二三"
  59953. t.content = "12三"
  59954. assert t.valid?
  59955. end
  59956. end
  59957. def test_optionally_validates_length_of_using_within_utf8
  59958. kcode_scope('UTF8') do
  59959. Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
  59960. t = Topic.create('title' => '一二三', 'content' => '一二三四五')
  59961. assert t.valid?
  59962. t.title = nil
  59963. assert t.valid?
  59964. end
  59965. end
  59966. def test_optionally_validates_length_of_using_within_on_create_utf8
  59967. kcode_scope('UTF8') do
  59968. Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "長すぎます: %d"
  59969. t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
  59970. assert !t.save
  59971. assert t.errors.on(:title)
  59972. assert_equal "長すぎます: 10", t.errors[:title]
  59973. t.title = "一二三四五六七八九"
  59974. assert t.save
  59975. t.title = "一二3"
  59976. assert t.save
  59977. t.content = "一二三四五六七八九十"
  59978. assert t.save
  59979. t.content = t.title = "一二三四五六"
  59980. assert t.save
  59981. end
  59982. end
  59983. def test_optionally_validates_length_of_using_within_on_update_utf8
  59984. kcode_scope('UTF8') do
  59985. Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "短すぎます: %d"
  59986. t = Topic.create("title" => "一二三4", "content" => "whatever")
  59987. assert !t.save
  59988. assert t.errors.on(:title)
  59989. t.title = "1二三4"
  59990. assert !t.save
  59991. assert t.errors.on(:title)
  59992. assert_equal "短すぎます: 5", t.errors[:title]
  59993. t.title = "valid"
  59994. t.content = "一二三四五六七八九十A"
  59995. assert !t.save
  59996. assert t.errors.on(:content)
  59997. t.content = "一二345"
  59998. assert t.save
  59999. end
  60000. end
  60001. def test_validates_length_of_using_is_utf8
  60002. kcode_scope('UTF8') do
  60003. Topic.validates_length_of :title, :is => 5
  60004. t = Topic.create("title" => "一二345", "content" => "whatever")
  60005. assert t.valid?
  60006. t.title = "一二345六"
  60007. assert !t.valid?
  60008. assert t.errors.on(:title)
  60009. assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
  60010. end
  60011. end
  60012. def test_validates_size_of_association_utf8
  60013. kcode_scope('UTF8') do
  60014. assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
  60015. t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
  60016. assert !t.save
  60017. assert t.errors.on(:replies)
  60018. t.replies.create('title' => 'あいうえお', 'content' => 'かきくけこ')
  60019. assert t.valid?
  60020. end
  60021. end
  60022. def test_validates_associated_many
  60023. Topic.validates_associated( :replies )
  60024. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60025. t.replies << [r = Reply.create("title" => "A reply"), r2 = Reply.create("title" => "Another reply")]
  60026. assert !t.valid?
  60027. assert t.errors.on(:replies)
  60028. assert_equal 1, r.errors.count # make sure all associated objects have been validated
  60029. assert_equal 1, r2.errors.count
  60030. r.content = r2.content = "non-empty"
  60031. assert t.valid?
  60032. end
  60033. def test_validates_associated_one
  60034. Reply.validates_associated( :topic )
  60035. Topic.validates_presence_of( :content )
  60036. r = Reply.create("title" => "A reply", "content" => "with content!")
  60037. r.topic = Topic.create("title" => "uhohuhoh")
  60038. assert !r.valid?
  60039. assert r.errors.on(:topic)
  60040. r.topic.content = "non-empty"
  60041. assert r.valid?
  60042. end
  60043. def test_validate_block
  60044. Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
  60045. t = Topic.create("title" => "Title", "content" => "whatever")
  60046. assert !t.valid?
  60047. assert t.errors.on(:title)
  60048. assert_equal "will never be valid", t.errors["title"]
  60049. end
  60050. def test_invalid_validator
  60051. Topic.validate 3
  60052. assert_raise(ActiveRecord::ActiveRecordError) { t = Topic.create }
  60053. end
  60054. def test_throw_away_typing
  60055. d = Developer.create "name" => "David", "salary" => "100,000"
  60056. assert !d.valid?
  60057. assert_equal 100, d.salary
  60058. assert_equal "100,000", d.salary_before_type_cast
  60059. end
  60060. def test_validates_acceptance_of_with_custom_error_using_quotes
  60061. Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
  60062. d = Developer.new
  60063. d.salary = "0"
  60064. assert !d.valid?
  60065. assert_equal d.errors.on(:salary).first, "This string contains 'single' and \"double\" quotes"
  60066. end
  60067. def test_validates_confirmation_of_with_custom_error_using_quotes
  60068. Developer.validates_confirmation_of :name, :message=> "This string contains 'single' and \"double\" quotes"
  60069. d = Developer.new
  60070. d.name = "John"
  60071. d.name_confirmation = "Johnny"
  60072. assert !d.valid?
  60073. assert_equal d.errors.on(:name), "This string contains 'single' and \"double\" quotes"
  60074. end
  60075. def test_validates_format_of_with_custom_error_using_quotes
  60076. Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "This string contains 'single' and \"double\" quotes"
  60077. d = Developer.new
  60078. d.name = "John 32"
  60079. assert !d.valid?
  60080. assert_equal d.errors.on(:name), "This string contains 'single' and \"double\" quotes"
  60081. end
  60082. def test_validates_inclusion_of_with_custom_error_using_quotes
  60083. Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
  60084. d = Developer.new
  60085. d.salary = "90,000"
  60086. assert !d.valid?
  60087. assert_equal d.errors.on(:salary).first, "This string contains 'single' and \"double\" quotes"
  60088. end
  60089. def test_validates_length_of_with_custom_too_long_using_quotes
  60090. Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
  60091. d = Developer.new
  60092. d.name = "Jeffrey"
  60093. assert !d.valid?
  60094. assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  60095. end
  60096. def test_validates_length_of_with_custom_too_short_using_quotes
  60097. Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
  60098. d = Developer.new
  60099. d.name = "Joe"
  60100. assert !d.valid?
  60101. assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  60102. end
  60103. def test_validates_length_of_with_custom_message_using_quotes
  60104. Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
  60105. d = Developer.new
  60106. d.name = "Joe"
  60107. assert !d.valid?
  60108. assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  60109. end
  60110. def test_validates_presence_of_with_custom_message_using_quotes
  60111. Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
  60112. d = Developer.new
  60113. d.name = "Joe"
  60114. assert !d.valid?
  60115. assert_equal d.errors.on(:non_existent), "This string contains 'single' and \"double\" quotes"
  60116. end
  60117. def test_validates_uniqueness_of_with_custom_message_using_quotes
  60118. Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
  60119. d = Developer.new
  60120. d.name = "David"
  60121. assert !d.valid?
  60122. assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  60123. end
  60124. def test_validates_associated_with_custom_message_using_quotes
  60125. Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
  60126. Topic.validates_presence_of :content
  60127. r = Reply.create("title" => "A reply", "content" => "with content!")
  60128. r.topic = Topic.create("title" => "uhohuhoh")
  60129. assert !r.valid?
  60130. assert_equal r.errors.on(:topic).first, "This string contains 'single' and \"double\" quotes"
  60131. end
  60132. def test_conditional_validation_using_method_true
  60133. # When the method returns true
  60134. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true )
  60135. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60136. assert !t.valid?
  60137. assert t.errors.on(:title)
  60138. assert_equal "hoo 5", t.errors["title"]
  60139. end
  60140. def test_conditional_validation_using_method_false
  60141. # When the method returns false
  60142. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
  60143. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60144. assert t.valid?
  60145. assert !t.errors.on(:title)
  60146. end
  60147. def test_conditional_validation_using_string_true
  60148. # When the evaluated string returns true
  60149. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" )
  60150. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60151. assert !t.valid?
  60152. assert t.errors.on(:title)
  60153. assert_equal "hoo 5", t.errors["title"]
  60154. end
  60155. def test_conditional_validation_using_string_false
  60156. # When the evaluated string returns false
  60157. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false")
  60158. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60159. assert t.valid?
  60160. assert !t.errors.on(:title)
  60161. end
  60162. def test_conditional_validation_using_block_true
  60163. # When the block returns true
  60164. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
  60165. :if => Proc.new { |r| r.content.size > 4 } )
  60166. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60167. assert !t.valid?
  60168. assert t.errors.on(:title)
  60169. assert_equal "hoo 5", t.errors["title"]
  60170. end
  60171. def test_conditional_validation_using_block_false
  60172. # When the block returns false
  60173. Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
  60174. :if => Proc.new { |r| r.title != "uhohuhoh"} )
  60175. t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
  60176. assert t.valid?
  60177. assert !t.errors.on(:title)
  60178. end
  60179. def test_validates_associated_missing
  60180. Reply.validates_presence_of(:topic)
  60181. r = Reply.create("title" => "A reply", "content" => "with content!")
  60182. assert !r.valid?
  60183. assert r.errors.on(:topic)
  60184. r.topic = Topic.find :first
  60185. assert r.valid?
  60186. end
  60187. end
  60188. class ValidatesNumericalityTest
  60189. NIL = [nil, "", " ", " \t \r \n"]
  60190. FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1)
  60191. INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
  60192. FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
  60193. INTEGERS = [0, 10, -10] + INTEGER_STRINGS
  60194. JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12"]
  60195. def setup
  60196. Topic.write_inheritable_attribute(:validate, nil)
  60197. Topic.write_inheritable_attribute(:validate_on_create, nil)
  60198. Topic.write_inheritable_attribute(:validate_on_update, nil)
  60199. end
  60200. def test_default_validates_numericality_of
  60201. Topic.validates_numericality_of :approved
  60202. invalid!(NIL + JUNK)
  60203. valid!(FLOATS + INTEGERS)
  60204. end
  60205. def test_validates_numericality_of_with_nil_allowed
  60206. Topic.validates_numericality_of :approved, :allow_nil => true
  60207. invalid!(JUNK)
  60208. valid!(NIL + FLOATS + INTEGERS)
  60209. end
  60210. def test_validates_numericality_of_with_integer_only
  60211. Topic.validates_numericality_of :approved, :only_integer => true
  60212. invalid!(NIL + JUNK + FLOATS)
  60213. valid!(INTEGERS)
  60214. end
  60215. def test_validates_numericality_of_with_integer_only_and_nil_allowed
  60216. Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
  60217. invalid!(JUNK + FLOATS)
  60218. valid!(NIL + INTEGERS)
  60219. end
  60220. private
  60221. def invalid!(values)
  60222. values.each do |value|
  60223. topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
  60224. assert !topic.valid?, "#{value} not rejected as a number"
  60225. assert topic.errors.on(:approved)
  60226. end
  60227. end
  60228. def valid!(values)
  60229. values.each do |value|
  60230. topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
  60231. assert topic.valid?, "#{value} not accepted as a number"
  60232. end
  60233. end
  60234. end
  60235. begin
  60236. require 'simplecc'
  60237. rescue LoadError
  60238. class Continuation # :nodoc: # for RDoc
  60239. end
  60240. def Continuation.create(*args, &block) # :nodoc:
  60241. cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
  60242. result ||= args
  60243. return *[cc, *result]
  60244. end
  60245. end
  60246. class Binding; end # for RDoc
  60247. # This method returns the binding of the method that called your
  60248. # method. It will raise an Exception when you're not inside a method.
  60249. #
  60250. # It's used like this:
  60251. # def inc_counter(amount = 1)
  60252. # Binding.of_caller do |binding|
  60253. # # Create a lambda that will increase the variable 'counter'
  60254. # # in the caller of this method when called.
  60255. # inc = eval("lambda { |arg| counter += arg }", binding)
  60256. # # We can refer to amount from inside this block safely.
  60257. # inc.call(amount)
  60258. # end
  60259. # # No other statements can go here. Put them inside the block.
  60260. # end
  60261. # counter = 0
  60262. # 2.times { inc_counter }
  60263. # counter # => 2
  60264. #
  60265. # Binding.of_caller must be the last statement in the method.
  60266. # This means that you will have to put everything you want to
  60267. # do after the call to Binding.of_caller into the block of it.
  60268. # This should be no problem however, because Ruby has closures.
  60269. # If you don't do this an Exception will be raised. Because of
  60270. # the way that Binding.of_caller is implemented it has to be
  60271. # done this way.
  60272. def Binding.of_caller(&block)
  60273. old_critical = Thread.critical
  60274. Thread.critical = true
  60275. count = 0
  60276. cc, result, error, extra_data = Continuation.create(nil, nil)
  60277. error.call if error
  60278. tracer = lambda do |*args|
  60279. type, context, extra_data = args[0], args[4], args
  60280. if type == "return"
  60281. count += 1
  60282. # First this method and then calling one will return --
  60283. # the trace event of the second event gets the context
  60284. # of the method which called the method that called this
  60285. # method.
  60286. if count == 2
  60287. # It would be nice if we could restore the trace_func
  60288. # that was set before we swapped in our own one, but
  60289. # this is impossible without overloading set_trace_func
  60290. # in current Ruby.
  60291. set_trace_func(nil)
  60292. cc.call(eval("binding", context), nil, extra_data)
  60293. end
  60294. elsif type == "line" then
  60295. nil
  60296. elsif type == "c-return" and extra_data[3] == :set_trace_func then
  60297. nil
  60298. else
  60299. set_trace_func(nil)
  60300. error_msg = "Binding.of_caller used in non-method context or " +
  60301. "trailing statements of method using it aren't in the block."
  60302. cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
  60303. end
  60304. end
  60305. unless result
  60306. set_trace_func(tracer)
  60307. return nil
  60308. else
  60309. Thread.critical = old_critical
  60310. case block.arity
  60311. when 1 then yield(result)
  60312. else yield(result, extra_data)
  60313. end
  60314. end
  60315. end
  60316. # The Breakpoint library provides the convenience of
  60317. # being able to inspect and modify state, diagnose
  60318. # bugs all via IRB by simply setting breakpoints in
  60319. # your applications by the call of a method.
  60320. #
  60321. # This library was written and is supported by me,
  60322. # Florian Gross. I can be reached at flgr@ccan.de
  60323. # and enjoy getting feedback about my libraries.
  60324. #
  60325. # The whole library (including breakpoint_client.rb
  60326. # and binding_of_caller.rb) is licensed under the
  60327. # same license that Ruby uses. (Which is currently
  60328. # either the GNU General Public License or a custom
  60329. # one that allows for commercial usage.) If you for
  60330. # some good reason need to use this under another
  60331. # license please contact me.
  60332. require 'irb'
  60333. require File.dirname(__FILE__) + '/binding_of_caller' unless defined? Binding.of_caller
  60334. require 'drb'
  60335. require 'drb/acl'
  60336. module Breakpoint
  60337. id = %q$Id: breakpoint.rb 92 2005-02-04 22:35:53Z flgr $
  60338. Version = id.split(" ")[2].to_i
  60339. extend self
  60340. # This will pop up an interactive ruby session at a
  60341. # pre-defined break point in a Ruby application. In
  60342. # this session you can examine the environment of
  60343. # the break point.
  60344. #
  60345. # You can get a list of variables in the context using
  60346. # local_variables via +local_variables+. You can then
  60347. # examine their values by typing their names.
  60348. #
  60349. # You can have a look at the call stack via +caller+.
  60350. #
  60351. # The source code around the location where the breakpoint
  60352. # was executed can be examined via +source_lines+. Its
  60353. # argument specifies how much lines of context to display.
  60354. # The default amount of context is 5 lines. Note that
  60355. # the call to +source_lines+ can raise an exception when
  60356. # it isn't able to read in the source code.
  60357. #
  60358. # breakpoints can also return a value. They will execute
  60359. # a supplied block for getting a default return value.
  60360. # A custom value can be returned from the session by doing
  60361. # +throw(:debug_return, value)+.
  60362. #
  60363. # You can also give names to break points which will be
  60364. # used in the message that is displayed upon execution
  60365. # of them.
  60366. #
  60367. # Here's a sample of how breakpoints should be placed:
  60368. #
  60369. # class Person
  60370. # def initialize(name, age)
  60371. # @name, @age = name, age
  60372. # breakpoint("Person#initialize")
  60373. # end
  60374. #
  60375. # attr_reader :age
  60376. # def name
  60377. # breakpoint("Person#name") { @name }
  60378. # end
  60379. # end
  60380. #
  60381. # person = Person.new("Random Person", 23)
  60382. # puts "Name: #{person.name}"
  60383. #
  60384. # And here is a sample debug session:
  60385. #
  60386. # Executing break point "Person#initialize" at file.rb:4 in `initialize'
  60387. # irb(#<Person:0x292fbe8>):001:0> local_variables
  60388. # => ["name", "age", "_", "__"]
  60389. # irb(#<Person:0x292fbe8>):002:0> [name, age]
  60390. # => ["Random Person", 23]
  60391. # irb(#<Person:0x292fbe8>):003:0> [@name, @age]
  60392. # => ["Random Person", 23]
  60393. # irb(#<Person:0x292fbe8>):004:0> self
  60394. # => #<Person:0x292fbe8 @age=23, @name="Random Person">
  60395. # irb(#<Person:0x292fbe8>):005:0> @age += 1; self
  60396. # => #<Person:0x292fbe8 @age=24, @name="Random Person">
  60397. # irb(#<Person:0x292fbe8>):006:0> exit
  60398. # Executing break point "Person#name" at file.rb:9 in `name'
  60399. # irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
  60400. # Name: Overriden name
  60401. #
  60402. # Breakpoint sessions will automatically have a few
  60403. # convenience methods available. See Breakpoint::CommandBundle
  60404. # for a list of them.
  60405. #
  60406. # Breakpoints can also be used remotely over sockets.
  60407. # This is implemented by running part of the IRB session
  60408. # in the application and part of it in a special client.
  60409. # You have to call Breakpoint.activate_drb to enable
  60410. # support for remote breakpoints and then run
  60411. # breakpoint_client.rb which is distributed with this
  60412. # library. See the documentation of Breakpoint.activate_drb
  60413. # for details.
  60414. def breakpoint(id = nil, context = nil, &block)
  60415. callstack = caller
  60416. callstack.slice!(0, 3) if callstack.first["breakpoint"]
  60417. file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
  60418. message = "Executing break point " + (id ? "#{id.inspect} " : "") +
  60419. "at #{file}:#{line}" + (method ? " in `#{method}'" : "")
  60420. if context then
  60421. return handle_breakpoint(context, message, file, line, &block)
  60422. end
  60423. Binding.of_caller do |binding_context|
  60424. handle_breakpoint(binding_context, message, file, line, &block)
  60425. end
  60426. end
  60427. module CommandBundle #:nodoc:
  60428. # Proxy to a Breakpoint client. Lets you directly execute code
  60429. # in the context of the client.
  60430. class Client #:nodoc:
  60431. def initialize(eval_handler) # :nodoc:
  60432. eval_handler.untaint
  60433. @eval_handler = eval_handler
  60434. end
  60435. instance_methods.each do |method|
  60436. next if method[/^__.+__$/]
  60437. undef_method method
  60438. end
  60439. # Executes the specified code at the client.
  60440. def eval(code)
  60441. @eval_handler.call(code)
  60442. end
  60443. # Will execute the specified statement at the client.
  60444. def method_missing(method, *args, &block)
  60445. if args.empty? and not block
  60446. result = eval "#{method}"
  60447. else
  60448. # This is a bit ugly. The alternative would be using an
  60449. # eval context instead of an eval handler for executing
  60450. # the code at the client. The problem with that approach
  60451. # is that we would have to handle special expressions
  60452. # like "self", "nil" or constants ourself which is hard.
  60453. remote = eval %{
  60454. result = lambda { |block, *args| #{method}(*args, &block) }
  60455. def result.call_with_block(*args, &block)
  60456. call(block, *args)
  60457. end
  60458. result
  60459. }
  60460. remote.call_with_block(*args, &block)
  60461. end
  60462. return result
  60463. end
  60464. end
  60465. # Returns the source code surrounding the location where the
  60466. # breakpoint was issued.
  60467. def source_lines(context = 5, return_line_numbers = false)
  60468. lines = File.readlines(@__bp_file).map { |line| line.chomp }
  60469. break_line = @__bp_line
  60470. start_line = [break_line - context, 1].max
  60471. end_line = break_line + context
  60472. result = lines[(start_line - 1) .. (end_line - 1)]
  60473. if return_line_numbers then
  60474. return [start_line, break_line, result]
  60475. else
  60476. return result
  60477. end
  60478. end
  60479. # Lets an object that will forward method calls to the breakpoint
  60480. # client. This is useful for outputting longer things at the client
  60481. # and so on. You can for example do these things:
  60482. #
  60483. # client.puts "Hello" # outputs "Hello" at client console
  60484. # # outputs "Hello" into the file temp.txt at the client
  60485. # client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
  60486. def client()
  60487. if Breakpoint.use_drb? then
  60488. sleep(0.5) until Breakpoint.drb_service.eval_handler
  60489. Client.new(Breakpoint.drb_service.eval_handler)
  60490. else
  60491. Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
  60492. end
  60493. end
  60494. end
  60495. def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
  60496. catch(:debug_return) do |value|
  60497. eval(%{
  60498. @__bp_file = #{file.inspect}
  60499. @__bp_line = #{line}
  60500. extend Breakpoint::CommandBundle
  60501. extend DRbUndumped if self
  60502. }, context) rescue nil
  60503. if not use_drb? then
  60504. puts message
  60505. IRB.start(nil, IRB::WorkSpace.new(context))
  60506. else
  60507. @drb_service.add_breakpoint(context, message)
  60508. end
  60509. block.call if block
  60510. end
  60511. end
  60512. # These exceptions will be raised on failed asserts
  60513. # if Breakpoint.asserts_cause_exceptions is set to
  60514. # true.
  60515. class FailedAssertError < RuntimeError #:nodoc:
  60516. end
  60517. # This asserts that the block evaluates to true.
  60518. # If it doesn't evaluate to true a breakpoint will
  60519. # automatically be created at that execution point.
  60520. #
  60521. # You can disable assert checking in production
  60522. # code by setting Breakpoint.optimize_asserts to
  60523. # true. (It will still be enabled when Ruby is run
  60524. # via the -d argument.)
  60525. #
  60526. # Example:
  60527. # person_name = "Foobar"
  60528. # assert { not person_name.nil? }
  60529. #
  60530. # Note: If you want to use this method from an
  60531. # unit test, you will have to call it by its full
  60532. # name, Breakpoint.assert.
  60533. def assert(context = nil, &condition)
  60534. return if Breakpoint.optimize_asserts and not $DEBUG
  60535. return if yield
  60536. callstack = caller
  60537. callstack.slice!(0, 3) if callstack.first["assert"]
  60538. file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
  60539. message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
  60540. if Breakpoint.asserts_cause_exceptions and not $DEBUG then
  60541. raise(Breakpoint::FailedAssertError, message)
  60542. end
  60543. message += " Executing implicit breakpoint."
  60544. if context then
  60545. return handle_breakpoint(context, message, file, line)
  60546. end
  60547. Binding.of_caller do |context|
  60548. handle_breakpoint(context, message, file, line)
  60549. end
  60550. end
  60551. # Whether asserts should be ignored if not in debug mode.
  60552. # Debug mode can be enabled by running ruby with the -d
  60553. # switch or by setting $DEBUG to true.
  60554. attr_accessor :optimize_asserts
  60555. self.optimize_asserts = false
  60556. # Whether an Exception should be raised on failed asserts
  60557. # in non-$DEBUG code or not. By default this is disabled.
  60558. attr_accessor :asserts_cause_exceptions
  60559. self.asserts_cause_exceptions = false
  60560. @use_drb = false
  60561. attr_reader :drb_service # :nodoc:
  60562. class DRbService # :nodoc:
  60563. include DRbUndumped
  60564. def initialize
  60565. @handler = @eval_handler = @collision_handler = nil
  60566. IRB.instance_eval { @CONF[:RC] = true }
  60567. IRB.run_config
  60568. end
  60569. def collision
  60570. sleep(0.5) until @collision_handler
  60571. @collision_handler.untaint
  60572. @collision_handler.call
  60573. end
  60574. def ping() end
  60575. def add_breakpoint(context, message)
  60576. workspace = IRB::WorkSpace.new(context)
  60577. workspace.extend(DRbUndumped)
  60578. sleep(0.5) until @handler
  60579. @handler.untaint
  60580. @handler.call(workspace, message)
  60581. end
  60582. attr_accessor :handler, :eval_handler, :collision_handler
  60583. end
  60584. # Will run Breakpoint in DRb mode. This will spawn a server
  60585. # that can be attached to via the breakpoint-client command
  60586. # whenever a breakpoint is executed. This is useful when you
  60587. # are debugging CGI applications or other applications where
  60588. # you can't access debug sessions via the standard input and
  60589. # output of your application.
  60590. #
  60591. # You can specify an URI where the DRb server will run at.
  60592. # This way you can specify the port the server runs on. The
  60593. # default URI is druby://localhost:42531.
  60594. #
  60595. # Please note that breakpoints will be skipped silently in
  60596. # case the DRb server can not spawned. (This can happen if
  60597. # the port is already used by another instance of your
  60598. # application on CGI or another application.)
  60599. #
  60600. # Also note that by default this will only allow access
  60601. # from localhost. You can however specify a list of
  60602. # allowed hosts or nil (to allow access from everywhere).
  60603. # But that will still not protect you from somebody
  60604. # reading the data as it goes through the net.
  60605. #
  60606. # A good approach for getting security and remote access
  60607. # is setting up an SSH tunnel between the DRb service
  60608. # and the client. This is usually done like this:
  60609. #
  60610. # $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
  60611. # (This will connect port 20000 at the client side to port
  60612. # 20000 at the server side, and port 10000 at the server
  60613. # side to port 10000 at the client side.)
  60614. #
  60615. # After that do this on the server side: (the code being debugged)
  60616. # Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
  60617. #
  60618. # And at the client side:
  60619. # ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
  60620. #
  60621. # Running through such a SSH proxy will also let you use
  60622. # breakpoint.rb in case you are behind a firewall.
  60623. #
  60624. # Detailed information about running DRb through firewalls is
  60625. # available at http://www.rubygarden.org/ruby?DrbTutorial
  60626. def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
  60627. ignore_collisions = false)
  60628. return false if @use_drb
  60629. uri ||= 'druby://localhost:42531'
  60630. if allowed_hosts then
  60631. acl = ["deny", "all"]
  60632. Array(allowed_hosts).each do |host|
  60633. acl += ["allow", host]
  60634. end
  60635. DRb.install_acl(ACL.new(acl))
  60636. end
  60637. @use_drb = true
  60638. @drb_service = DRbService.new
  60639. did_collision = false
  60640. begin
  60641. @service = DRb.start_service(uri, @drb_service)
  60642. rescue Errno::EADDRINUSE
  60643. if ignore_collisions then
  60644. nil
  60645. else
  60646. # The port is already occupied by another
  60647. # Breakpoint service. We will try to tell
  60648. # the old service that we want its port.
  60649. # It will then forward that request to the
  60650. # user and retry.
  60651. unless did_collision then
  60652. DRbObject.new(nil, uri).collision
  60653. did_collision = true
  60654. end
  60655. sleep(10)
  60656. retry
  60657. end
  60658. end
  60659. return true
  60660. end
  60661. # Deactivates a running Breakpoint service.
  60662. def deactivate_drb
  60663. @service.stop_service unless @service.nil?
  60664. @service = nil
  60665. @use_drb = false
  60666. @drb_service = nil
  60667. end
  60668. # Returns true when Breakpoints are used over DRb.
  60669. # Breakpoint.activate_drb causes this to be true.
  60670. def use_drb?
  60671. @use_drb == true
  60672. end
  60673. end
  60674. module IRB #:nodoc:
  60675. class << self; remove_method :start; end
  60676. def self.start(ap_path = nil, main_context = nil, workspace = nil)
  60677. $0 = File::basename(ap_path, ".rb") if ap_path
  60678. # suppress some warnings about redefined constants
  60679. old_verbose, $VERBOSE = $VERBOSE, nil
  60680. IRB.setup(ap_path)
  60681. $VERBOSE = old_verbose
  60682. if @CONF[:SCRIPT] then
  60683. irb = Irb.new(main_context, @CONF[:SCRIPT])
  60684. else
  60685. irb = Irb.new(main_context)
  60686. end
  60687. if workspace then
  60688. irb.context.workspace = workspace
  60689. end
  60690. @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
  60691. @CONF[:MAIN_CONTEXT] = irb.context
  60692. old_sigint = trap("SIGINT") do
  60693. begin
  60694. irb.signal_handle
  60695. rescue RubyLex::TerminateLineInput
  60696. # ignored
  60697. end
  60698. end
  60699. catch(:IRB_EXIT) do
  60700. irb.eval_input
  60701. end
  60702. ensure
  60703. trap("SIGINT", old_sigint)
  60704. end
  60705. class << self
  60706. alias :old_CurrentContext :CurrentContext
  60707. remove_method :CurrentContext
  60708. end
  60709. def IRB.CurrentContext
  60710. if old_CurrentContext.nil? and Breakpoint.use_drb? then
  60711. result = Object.new
  60712. def result.last_value; end
  60713. return result
  60714. else
  60715. old_CurrentContext
  60716. end
  60717. end
  60718. def IRB.parse_opts() end
  60719. class Context #:nodoc:
  60720. alias :old_evaluate :evaluate
  60721. def evaluate(line, line_no)
  60722. if line.chomp == "exit" then
  60723. exit
  60724. else
  60725. old_evaluate(line, line_no)
  60726. end
  60727. end
  60728. end
  60729. class WorkSpace #:nodoc:
  60730. alias :old_evaluate :evaluate
  60731. def evaluate(*args)
  60732. if Breakpoint.use_drb? then
  60733. result = old_evaluate(*args)
  60734. if args[0] != :no_proxy and
  60735. not [true, false, nil].include?(result)
  60736. then
  60737. result.extend(DRbUndumped) rescue nil
  60738. end
  60739. return result
  60740. else
  60741. old_evaluate(*args)
  60742. end
  60743. end
  60744. end
  60745. module InputCompletor #:nodoc:
  60746. def self.eval(code, context, *more)
  60747. # Big hack, this assumes that InputCompletor
  60748. # will only call eval() when it wants code
  60749. # to be executed in the IRB context.
  60750. IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
  60751. end
  60752. end
  60753. end
  60754. module DRb # :nodoc:
  60755. class DRbObject #:nodoc:
  60756. undef :inspect if method_defined?(:inspect)
  60757. undef :clone if method_defined?(:clone)
  60758. end
  60759. end
  60760. # See Breakpoint.breakpoint
  60761. def breakpoint(id = nil, &block)
  60762. Binding.of_caller do |context|
  60763. Breakpoint.breakpoint(id, context, &block)
  60764. end
  60765. end
  60766. # See Breakpoint.assert
  60767. def assert(&block)
  60768. Binding.of_caller do |context|
  60769. Breakpoint.assert(context, &block)
  60770. end
  60771. end
  60772. module ActiveSupport
  60773. module CachingTools #:nodoc:
  60774. # Provide shortcuts to simply the creation of nested default hashes. This
  60775. # pattern is useful, common practice, and unsightly when done manually.
  60776. module HashCaching
  60777. # Dynamically create a nested hash structure used to cache calls to +method_name+
  60778. # The cache method is named +#{method_name}_cache+ unless :as => :alternate_name
  60779. # is given.
  60780. #
  60781. # The hash structure is created using nested Hash.new. For example:
  60782. #
  60783. # def slow_method(a, b) a ** b end
  60784. #
  60785. # can be cached using hash_cache :slow_method, which will define the method
  60786. # slow_method_cache. We can then find the result of a ** b using:
  60787. #
  60788. # slow_method_cache[a][b]
  60789. #
  60790. # The hash structure returned by slow_method_cache would look like this:
  60791. #
  60792. # Hash.new do |as, a|
  60793. # as[a] = Hash.new do |bs, b|
  60794. # bs[b] = slow_method(a, b)
  60795. # end
  60796. # end
  60797. #
  60798. # The generated code is actually compressed onto a single line to maintain
  60799. # sensible backtrace signatures.
  60800. #
  60801. def hash_cache(method_name, options = {})
  60802. selector = options[:as] || "#{method_name}_cache"
  60803. method = self.instance_method(method_name)
  60804. args = []
  60805. code = "def #{selector}(); @#{selector} ||= "
  60806. (1..method.arity).each do |n|
  60807. args << "v#{n}"
  60808. code << "Hash.new {|h#{n}, v#{n}| h#{n}[v#{n}] = "
  60809. end
  60810. # Add the method call with arguments, followed by closing braces and end.
  60811. code << "#{method_name}(#{args * ', '}) #{'}' * method.arity} end"
  60812. # Extract the line number information from the caller. Exceptions arising
  60813. # in the generated code should point to the +hash_cache :...+ line.
  60814. if caller[0] && /^(.*):(\d+)$/ =~ caller[0]
  60815. file, line_number = $1, $2.to_i
  60816. else # We can't give good trackback info; fallback to this line:
  60817. file, line_number = __FILE__, __LINE__
  60818. end
  60819. # We use eval rather than building proc's because it allows us to avoid
  60820. # linking the Hash's to this method's binding. Experience has shown that
  60821. # doing so can cause obtuse memory leaks.
  60822. class_eval code, file, line_number
  60823. end
  60824. end
  60825. end
  60826. end
  60827. require 'logger'
  60828. require File.dirname(__FILE__) + '/core_ext/class/attribute_accessors'
  60829. class Logger #:nodoc:
  60830. cattr_accessor :silencer
  60831. self.silencer = true
  60832. # Silences the logger for the duration of the block.
  60833. def silence(temporary_level = Logger::ERROR)
  60834. if silencer
  60835. begin
  60836. old_logger_level, self.level = level, temporary_level
  60837. yield self
  60838. ensure
  60839. self.level = old_logger_level
  60840. end
  60841. else
  60842. yield self
  60843. end
  60844. end
  60845. private
  60846. alias old_format_message format_message
  60847. # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
  60848. # We can't test RUBY_VERSION because some distributions don't keep Ruby
  60849. # and its standard library in sync, leading to installations of Ruby 1.8.2
  60850. # with Logger from 1.8.3 and vice versa.
  60851. if method_defined?(:formatter=)
  60852. def format_message(severity, timestamp, progname, msg)
  60853. "#{msg}\n"
  60854. end
  60855. else
  60856. def format_message(severity, timestamp, msg, progname)
  60857. "#{msg}\n"
  60858. end
  60859. end
  60860. end
  60861. module ActiveSupport #:nodoc:
  60862. module CoreExtensions #:nodoc:
  60863. module Array #:nodoc:
  60864. module Conversions
  60865. # Converts the array to comma-seperated sentence where the last element is joined by the connector word. Options:
  60866. # * <tt>:connector</tt>: The word used to join the last element in arrays with more than two elements (default: "and")
  60867. # * <tt>:skip_last_comma</tt>: Set to true to return "a, b, and c" instead of "a, b and c".
  60868. def to_sentence(options = {})
  60869. options.assert_valid_keys(:connector, :skip_last_comma)
  60870. options.reverse_merge! :connector => 'and', :skip_last_comma => false
  60871. case length
  60872. when 0
  60873. ""
  60874. when 1
  60875. self[0]
  60876. when 2
  60877. "#{self[0]} #{options[:connector]} #{self[1]}"
  60878. else
  60879. "#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]} #{self[-1]}"
  60880. end
  60881. end
  60882. # When an array is given to url_for, it is converted to a slash separated string.
  60883. def to_param
  60884. join '/'
  60885. end
  60886. def to_xml(options = {})
  60887. raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
  60888. options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
  60889. options[:children] ||= options[:root].singularize
  60890. options[:indent] ||= 2
  60891. options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
  60892. root = options.delete(:root)
  60893. children = options.delete(:children)
  60894. options[:builder].instruct! unless options.delete(:skip_instruct)
  60895. options[:builder].tag!(root.to_s.dasherize) { each { |e| e.to_xml(options.merge({ :skip_instruct => true, :root => children })) } }
  60896. end
  60897. end
  60898. end
  60899. end
  60900. end
  60901. require File.dirname(__FILE__) + '/array/conversions'
  60902. class Array #:nodoc:
  60903. include ActiveSupport::CoreExtensions::Array::Conversions
  60904. # Iterate over an array in groups of a certain size, padding any remaining
  60905. # slots with specified value (<tt>nil</tt> by default).
  60906. #
  60907. # E.g.
  60908. #
  60909. # %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
  60910. # ["1", "2", "3"]
  60911. # ["4", "5", "6"]
  60912. # ["7", nil, nil]
  60913. def in_groups_of(number, fill_with = nil, &block)
  60914. require 'enumerator'
  60915. collection = dup
  60916. collection << fill_with until collection.size.modulo(number).zero?
  60917. collection.each_slice(number, &block)
  60918. end
  60919. end
  60920. class Object #:nodoc:
  60921. # "", " ", nil, [], and {} are blank
  60922. def blank?
  60923. if respond_to?(:empty?) && respond_to?(:strip)
  60924. empty? or strip.empty?
  60925. elsif respond_to?(:empty?)
  60926. empty?
  60927. else
  60928. !self
  60929. end
  60930. end
  60931. end
  60932. class NilClass #:nodoc:
  60933. def blank?
  60934. true
  60935. end
  60936. end
  60937. class FalseClass #:nodoc:
  60938. def blank?
  60939. true
  60940. end
  60941. end
  60942. class TrueClass #:nodoc:
  60943. def blank?
  60944. false
  60945. end
  60946. end
  60947. class Array #:nodoc:
  60948. alias_method :blank?, :empty?
  60949. end
  60950. class Hash #:nodoc:
  60951. alias_method :blank?, :empty?
  60952. end
  60953. class String #:nodoc:
  60954. def blank?
  60955. empty? || strip.empty?
  60956. end
  60957. end
  60958. class Numeric #:nodoc:
  60959. def blank?
  60960. false
  60961. end
  60962. endmodule ActiveSupport #:nodoc:
  60963. module CoreExtensions #:nodoc:
  60964. module CGI #:nodoc:
  60965. module EscapeSkippingSlashes #:nodoc:
  60966. def escape_skipping_slashes(str)
  60967. str = str.join('/') if str.respond_to? :join
  60968. str.gsub(/([^ \/a-zA-Z0-9_.-])/n) do
  60969. "%#{$1.unpack('H2').first.upcase}"
  60970. end.tr(' ', '+')
  60971. end
  60972. end
  60973. end
  60974. end
  60975. end
  60976. require File.dirname(__FILE__) + '/cgi/escape_skipping_slashes'
  60977. class CGI #:nodoc:
  60978. extend(ActiveSupport::CoreExtensions::CGI::EscapeSkippingSlashes)
  60979. end
  60980. # Extends the class object with class and instance accessors for class attributes,
  60981. # just like the native attr* accessors for instance attributes.
  60982. class Class # :nodoc:
  60983. def cattr_reader(*syms)
  60984. syms.flatten.each do |sym|
  60985. class_eval(<<-EOS, __FILE__, __LINE__)
  60986. unless defined? @@#{sym}
  60987. @@#{sym} = nil
  60988. end
  60989. def self.#{sym}
  60990. @@#{sym}
  60991. end
  60992. def #{sym}
  60993. @@#{sym}
  60994. end
  60995. EOS
  60996. end
  60997. end
  60998. def cattr_writer(*syms)
  60999. syms.flatten.each do |sym|
  61000. class_eval(<<-EOS, __FILE__, __LINE__)
  61001. unless defined? @@#{sym}
  61002. @@#{sym} = nil
  61003. end
  61004. def self.#{sym}=(obj)
  61005. @@#{sym} = obj
  61006. end
  61007. def #{sym}=(obj)
  61008. @@#{sym} = obj
  61009. end
  61010. EOS
  61011. end
  61012. end
  61013. def cattr_accessor(*syms)
  61014. cattr_reader(*syms)
  61015. cattr_writer(*syms)
  61016. end
  61017. end
  61018. # Retain for backward compatibility. Methods are now included in Class.
  61019. module ClassInheritableAttributes # :nodoc:
  61020. end
  61021. # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
  61022. # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
  61023. # to, for example, an array without those additions being shared with either their parent, siblings, or
  61024. # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
  61025. class Class # :nodoc:
  61026. def class_inheritable_reader(*syms)
  61027. syms.each do |sym|
  61028. class_eval <<-EOS
  61029. def self.#{sym}
  61030. read_inheritable_attribute(:#{sym})
  61031. end
  61032. def #{sym}
  61033. self.class.#{sym}
  61034. end
  61035. EOS
  61036. end
  61037. end
  61038. def class_inheritable_writer(*syms)
  61039. syms.each do |sym|
  61040. class_eval <<-EOS
  61041. def self.#{sym}=(obj)
  61042. write_inheritable_attribute(:#{sym}, obj)
  61043. end
  61044. def #{sym}=(obj)
  61045. self.class.#{sym} = obj
  61046. end
  61047. EOS
  61048. end
  61049. end
  61050. def class_inheritable_array_writer(*syms)
  61051. syms.each do |sym|
  61052. class_eval <<-EOS
  61053. def self.#{sym}=(obj)
  61054. write_inheritable_array(:#{sym}, obj)
  61055. end
  61056. def #{sym}=(obj)
  61057. self.class.#{sym} = obj
  61058. end
  61059. EOS
  61060. end
  61061. end
  61062. def class_inheritable_hash_writer(*syms)
  61063. syms.each do |sym|
  61064. class_eval <<-EOS
  61065. def self.#{sym}=(obj)
  61066. write_inheritable_hash(:#{sym}, obj)
  61067. end
  61068. def #{sym}=(obj)
  61069. self.class.#{sym} = obj
  61070. end
  61071. EOS
  61072. end
  61073. end
  61074. def class_inheritable_accessor(*syms)
  61075. class_inheritable_reader(*syms)
  61076. class_inheritable_writer(*syms)
  61077. end
  61078. def class_inheritable_array(*syms)
  61079. class_inheritable_reader(*syms)
  61080. class_inheritable_array_writer(*syms)
  61081. end
  61082. def class_inheritable_hash(*syms)
  61083. class_inheritable_reader(*syms)
  61084. class_inheritable_hash_writer(*syms)
  61085. end
  61086. def inheritable_attributes
  61087. @inheritable_attributes ||= {}
  61088. end
  61089. def write_inheritable_attribute(key, value)
  61090. inheritable_attributes[key] = value
  61091. end
  61092. def write_inheritable_array(key, elements)
  61093. write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
  61094. write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
  61095. end
  61096. def write_inheritable_hash(key, hash)
  61097. write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
  61098. write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
  61099. end
  61100. def read_inheritable_attribute(key)
  61101. inheritable_attributes[key]
  61102. end
  61103. def reset_inheritable_attributes
  61104. inheritable_attributes.clear
  61105. end
  61106. private
  61107. def inherited_with_inheritable_attributes(child)
  61108. inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
  61109. child.instance_variable_set('@inheritable_attributes', inheritable_attributes.dup)
  61110. end
  61111. alias inherited_without_inheritable_attributes inherited
  61112. alias inherited inherited_with_inheritable_attributes
  61113. end
  61114. class Class #:nodoc:
  61115. def remove_subclasses
  61116. Object.remove_subclasses_of(self)
  61117. end
  61118. def subclasses
  61119. Object.subclasses_of(self).map { |o| o.to_s }
  61120. end
  61121. def remove_class(*klasses)
  61122. klasses.flatten.each do |klass|
  61123. # Skip this class if there is nothing bound to this name
  61124. next unless defined?(klass.name)
  61125. basename = klass.to_s.split("::").last
  61126. parent = klass.parent
  61127. # Skip this class if it does not match the current one bound to this name
  61128. next unless parent.const_defined?(basename) && klass = parent.const_get(basename)
  61129. parent.send :remove_const, basename unless parent == klass
  61130. end
  61131. end
  61132. end
  61133. require File.dirname(__FILE__) + '/class/attribute_accessors'
  61134. require File.dirname(__FILE__) + '/class/inheritable_attributes'
  61135. require File.dirname(__FILE__) + '/class/removal'module ActiveSupport #:nodoc:
  61136. module CoreExtensions #:nodoc:
  61137. module Date #:nodoc:
  61138. # Getting dates in different convenient string representations and other objects
  61139. module Conversions
  61140. DATE_FORMATS = {
  61141. :short => "%e %b",
  61142. :long => "%B %e, %Y"
  61143. }
  61144. def self.included(klass) #:nodoc:
  61145. klass.send(:alias_method, :to_default_s, :to_s)
  61146. klass.send(:alias_method, :to_s, :to_formatted_s)
  61147. end
  61148. def to_formatted_s(format = :default)
  61149. DATE_FORMATS[format] ? strftime(DATE_FORMATS[format]).strip : to_default_s
  61150. end
  61151. # To be able to keep Dates and Times interchangeable on conversions
  61152. def to_date
  61153. self
  61154. end
  61155. def to_time(form = :local)
  61156. ::Time.send(form, year, month, day)
  61157. end
  61158. alias :xmlschema :to_s
  61159. end
  61160. end
  61161. end
  61162. endrequire 'date'
  61163. require File.dirname(__FILE__) + '/date/conversions'
  61164. class Date#:nodoc:
  61165. include ActiveSupport::CoreExtensions::Date::Conversions
  61166. end
  61167. module Enumerable #:nodoc:
  61168. def first_match
  61169. match = nil
  61170. each do |items|
  61171. break if match = yield(items)
  61172. end
  61173. match
  61174. end
  61175. # Collect an enumerable into sets, grouped by the result of a block. Useful,
  61176. # for example, for grouping records by date.
  61177. #
  61178. # e.g.
  61179. #
  61180. # latest_transcripts.group_by(&:day).each do |day, transcripts|
  61181. # p "#{day} -> #{transcripts.map(&:class) * ', '}"
  61182. # end
  61183. # "2006-03-01 -> Transcript"
  61184. # "2006-02-28 -> Transcript"
  61185. # "2006-02-27 -> Transcript, Transcript"
  61186. # "2006-02-26 -> Transcript, Transcript"
  61187. # "2006-02-25 -> Transcript"
  61188. # "2006-02-24 -> Transcript, Transcript"
  61189. # "2006-02-23 -> Transcript"
  61190. def group_by
  61191. inject({}) do |groups, element|
  61192. (groups[yield(element)] ||= []) << element
  61193. groups
  61194. end
  61195. end
  61196. end
  61197. class Exception # :nodoc:
  61198. def clean_message
  61199. Pathname.clean_within message
  61200. end
  61201. TraceSubstitutions = []
  61202. FrameworkRegexp = /generated|vendor|dispatch|ruby|script\/\w+/
  61203. def clean_backtrace
  61204. backtrace.collect do |line|
  61205. Pathname.clean_within(TraceSubstitutions.inject(line) do |line, (regexp, sub)|
  61206. line.gsub regexp, sub
  61207. end)
  61208. end
  61209. end
  61210. def application_backtrace
  61211. before_application_frame = true
  61212. trace = clean_backtrace.reject do |line|
  61213. non_app_frame = (line =~ FrameworkRegexp)
  61214. before_application_frame = false unless non_app_frame
  61215. non_app_frame && ! before_application_frame
  61216. end
  61217. # If we didn't find any application frames, return an empty app trace.
  61218. before_application_frame ? [] : trace
  61219. end
  61220. def framework_backtrace
  61221. clean_backtrace.select {|line| line =~ FrameworkRegexp}
  61222. end
  61223. endmodule ActiveSupport #:nodoc:
  61224. module CoreExtensions #:nodoc:
  61225. module Hash #:nodoc:
  61226. module Conversions
  61227. XML_TYPE_NAMES = {
  61228. "Fixnum" => "integer",
  61229. "Date" => "date",
  61230. "Time" => "datetime",
  61231. "TrueClass" => "boolean",
  61232. "FalseClass" => "boolean"
  61233. }
  61234. XML_FORMATTING = {
  61235. "date" => Proc.new { |date| date.to_s(:db) },
  61236. "datetime" => Proc.new { |time| time.xmlschema }
  61237. }
  61238. def to_xml(options = {})
  61239. options[:indent] ||= 2
  61240. options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]), :root => "hash" })
  61241. options[:builder].instruct! unless options.delete(:skip_instruct)
  61242. options[:builder].__send__(options[:root].to_s.dasherize) do
  61243. each do |key, value|
  61244. case value
  61245. when ::Hash
  61246. value.to_xml(options.merge({ :root => key, :skip_instruct => true }))
  61247. when ::Array
  61248. value.to_xml(options.merge({ :root => key, :children => key.to_s.singularize, :skip_instruct => true}))
  61249. else
  61250. type_name = XML_TYPE_NAMES[value.class.to_s]
  61251. options[:builder].tag!(key.to_s.dasherize,
  61252. XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value,
  61253. options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
  61254. )
  61255. end
  61256. end
  61257. end
  61258. end
  61259. end
  61260. end
  61261. end
  61262. end
  61263. module ActiveSupport #:nodoc:
  61264. module CoreExtensions #:nodoc:
  61265. module Hash #:nodoc:
  61266. module Diff
  61267. def diff(h2)
  61268. self.dup.delete_if { |k, v| h2[k] == v }.merge(h2.dup.delete_if { |k, v| self.has_key?(k) })
  61269. end
  61270. end
  61271. end
  61272. end
  61273. end
  61274. # this class has dubious semantics and we only have it so that
  61275. # people can write params[:key] instead of params['key']
  61276. class HashWithIndifferentAccess < Hash
  61277. def initialize(constructor = {})
  61278. if constructor.is_a?(Hash)
  61279. super()
  61280. update(constructor)
  61281. else
  61282. super(constructor)
  61283. end
  61284. end
  61285. def default(key)
  61286. self[key.to_s] if key.is_a?(Symbol)
  61287. end
  61288. alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
  61289. alias_method :regular_update, :update unless method_defined?(:regular_update)
  61290. def []=(key, value)
  61291. regular_writer(convert_key(key), convert_value(value))
  61292. end
  61293. def update(other_hash)
  61294. other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
  61295. self
  61296. end
  61297. alias_method :merge!, :update
  61298. def key?(key)
  61299. super(convert_key(key))
  61300. end
  61301. alias_method :include?, :key?
  61302. alias_method :has_key?, :key?
  61303. alias_method :member?, :key?
  61304. def fetch(key, *extras)
  61305. super(convert_key(key), *extras)
  61306. end
  61307. def values_at(*indices)
  61308. indices.collect {|key| self[convert_key(key)]}
  61309. end
  61310. def dup
  61311. HashWithIndifferentAccess.new(self)
  61312. end
  61313. def merge(hash)
  61314. self.dup.update(hash)
  61315. end
  61316. def delete(key)
  61317. super(convert_key(key))
  61318. end
  61319. protected
  61320. def convert_key(key)
  61321. key.kind_of?(Symbol) ? key.to_s : key
  61322. end
  61323. def convert_value(value)
  61324. value.is_a?(Hash) ? value.with_indifferent_access : value
  61325. end
  61326. end
  61327. module ActiveSupport #:nodoc:
  61328. module CoreExtensions #:nodoc:
  61329. module Hash #:nodoc:
  61330. module IndifferentAccess #:nodoc:
  61331. def with_indifferent_access
  61332. HashWithIndifferentAccess.new(self)
  61333. end
  61334. end
  61335. end
  61336. end
  61337. end
  61338. module ActiveSupport #:nodoc:
  61339. module CoreExtensions #:nodoc:
  61340. module Hash #:nodoc:
  61341. module Keys
  61342. # Return a new hash with all keys converted to strings.
  61343. def stringify_keys
  61344. inject({}) do |options, (key, value)|
  61345. options[key.to_s] = value
  61346. options
  61347. end
  61348. end
  61349. # Destructively convert all keys to strings.
  61350. def stringify_keys!
  61351. keys.each do |key|
  61352. unless key.class.to_s == "String" # weird hack to make the tests run when string_ext_test.rb is also running
  61353. self[key.to_s] = self[key]
  61354. delete(key)
  61355. end
  61356. end
  61357. self
  61358. end
  61359. # Return a new hash with all keys converted to symbols.
  61360. def symbolize_keys
  61361. inject({}) do |options, (key, value)|
  61362. options[key.to_sym] = value
  61363. options
  61364. end
  61365. end
  61366. # Destructively convert all keys to symbols.
  61367. def symbolize_keys!
  61368. keys.each do |key|
  61369. unless key.is_a?(Symbol)
  61370. self[key.to_sym] = self[key]
  61371. delete(key)
  61372. end
  61373. end
  61374. self
  61375. end
  61376. alias_method :to_options, :symbolize_keys
  61377. alias_method :to_options!, :symbolize_keys!
  61378. def assert_valid_keys(*valid_keys)
  61379. unknown_keys = keys - [valid_keys].flatten
  61380. raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
  61381. end
  61382. end
  61383. end
  61384. end
  61385. end
  61386. module ActiveSupport #:nodoc:
  61387. module CoreExtensions #:nodoc:
  61388. module Hash #:nodoc:
  61389. # Allows for reverse merging where its the keys in the calling hash that wins over those in the <tt>other_hash</tt>.
  61390. # This is particularly useful for initializing an incoming option hash with default values:
  61391. #
  61392. # def setup(options = {})
  61393. # options.reverse_merge! :size => 25, :velocity => 10
  61394. # end
  61395. #
  61396. # The default :size and :velocity is only set if the +options+ passed in doesn't already have those keys set.
  61397. module ReverseMerge
  61398. def reverse_merge(other_hash)
  61399. other_hash.merge(self)
  61400. end
  61401. def reverse_merge!(other_hash)
  61402. replace(reverse_merge(other_hash))
  61403. end
  61404. alias_method :reverse_update, :reverse_merge
  61405. end
  61406. end
  61407. end
  61408. endrequire File.dirname(__FILE__) + '/hash/keys'
  61409. require File.dirname(__FILE__) + '/hash/indifferent_access'
  61410. require File.dirname(__FILE__) + '/hash/reverse_merge'
  61411. require File.dirname(__FILE__) + '/hash/conversions'
  61412. require File.dirname(__FILE__) + '/hash/diff'
  61413. class Hash #:nodoc:
  61414. include ActiveSupport::CoreExtensions::Hash::Keys
  61415. include ActiveSupport::CoreExtensions::Hash::IndifferentAccess
  61416. include ActiveSupport::CoreExtensions::Hash::ReverseMerge
  61417. include ActiveSupport::CoreExtensions::Hash::Conversions
  61418. include ActiveSupport::CoreExtensions::Hash::Diff
  61419. end
  61420. module ActiveSupport #:nodoc:
  61421. module CoreExtensions #:nodoc:
  61422. module Integer #:nodoc:
  61423. # For checking if a fixnum is even or odd.
  61424. # * 1.even? # => false
  61425. # * 1.odd? # => true
  61426. # * 2.even? # => true
  61427. # * 2.odd? # => false
  61428. module EvenOdd
  61429. def multiple_of?(number)
  61430. self % number == 0
  61431. end
  61432. def even?
  61433. multiple_of? 2
  61434. end
  61435. def odd?
  61436. !even?
  61437. end
  61438. end
  61439. end
  61440. end
  61441. end
  61442. require File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector
  61443. module ActiveSupport #:nodoc:
  61444. module CoreExtensions #:nodoc:
  61445. module Integer #:nodoc:
  61446. module Inflections
  61447. # 1.ordinalize # => "1st"
  61448. # 3.ordinalize # => "3rd"
  61449. # 10.ordinalize # => "10th"
  61450. def ordinalize
  61451. Inflector.ordinalize(self)
  61452. end
  61453. end
  61454. end
  61455. end
  61456. end
  61457. require File.dirname(__FILE__) + '/integer/even_odd'
  61458. require File.dirname(__FILE__) + '/integer/inflections'
  61459. class Integer #:nodoc:
  61460. include ActiveSupport::CoreExtensions::Integer::EvenOdd
  61461. include ActiveSupport::CoreExtensions::Integer::Inflections
  61462. end
  61463. class Object
  61464. # Makes backticks behave (somewhat more) similarly on all platforms.
  61465. # On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the
  61466. # spawned shell prints a message to stderr and sets $?. We emulate
  61467. # Unix on the former but not the latter.
  61468. def `(command) #:nodoc:
  61469. super
  61470. rescue Errno::ENOENT => e
  61471. STDERR.puts "#$0: #{e}"
  61472. end
  61473. endmodule Kernel
  61474. # Turns the current script into a daemon process that detaches from the console.
  61475. # It can be shut down with a TERM signal.
  61476. def daemonize
  61477. exit if fork # Parent exits, child continues.
  61478. Process.setsid # Become session leader.
  61479. exit if fork # Zap session leader. See [1].
  61480. Dir.chdir "/" # Release old working directory.
  61481. File.umask 0000 # Ensure sensible umask. Adjust as needed.
  61482. STDIN.reopen "/dev/null" # Free file descriptors and
  61483. STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
  61484. STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile.
  61485. trap("TERM") { exit }
  61486. end
  61487. endmodule Kernel
  61488. # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
  61489. #
  61490. # silence_warnings do
  61491. # value = noisy_call # no warning voiced
  61492. # end
  61493. #
  61494. # noisy_call # warning voiced
  61495. def silence_warnings
  61496. old_verbose, $VERBOSE = $VERBOSE, nil
  61497. yield
  61498. ensure
  61499. $VERBOSE = old_verbose
  61500. end
  61501. # Sets $VERBOSE to true for the duration of the block and back to its original value afterwards.
  61502. def enable_warnings
  61503. old_verbose, $VERBOSE = $VERBOSE, true
  61504. yield
  61505. ensure
  61506. $VERBOSE = old_verbose
  61507. end
  61508. # For compatibility
  61509. def silence_stderr #:nodoc:
  61510. silence_stream(STDERR) { yield }
  61511. end
  61512. # Silences any stream for the duration of the block.
  61513. #
  61514. # silence_stream(STDOUT) do
  61515. # puts 'This will never be seen'
  61516. # end
  61517. #
  61518. # puts 'But this will'
  61519. def silence_stream(stream)
  61520. old_stream = stream.dup
  61521. stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
  61522. stream.sync = true
  61523. yield
  61524. ensure
  61525. stream.reopen(old_stream)
  61526. end
  61527. def suppress(*exception_classes)
  61528. begin yield
  61529. rescue Exception => e
  61530. raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
  61531. end
  61532. end
  61533. endmodule Kernel
  61534. # Require a library with fallback to RubyGems. Warnings during library
  61535. # loading are silenced to increase signal/noise for application warnings.
  61536. def require_library_or_gem(library_name)
  61537. silence_warnings do
  61538. begin
  61539. require library_name
  61540. rescue LoadError => cannot_require
  61541. # 1. Requiring the module is unsuccessful, maybe it's a gem and nobody required rubygems yet. Try.
  61542. begin
  61543. require 'rubygems'
  61544. rescue LoadError => rubygems_not_installed
  61545. raise cannot_require
  61546. end
  61547. # 2. Rubygems is installed and loaded. Try to load the library again
  61548. begin
  61549. require library_name
  61550. rescue LoadError => gem_not_installed
  61551. raise cannot_require
  61552. end
  61553. end
  61554. end
  61555. end
  61556. endrequire File.dirname(__FILE__) + '/kernel/daemonizing'
  61557. require File.dirname(__FILE__) + '/kernel/reporting'
  61558. require File.dirname(__FILE__) + '/kernel/agnostics'
  61559. require File.dirname(__FILE__) + '/kernel/requires'
  61560. class MissingSourceFile < LoadError #:nodoc:
  61561. attr_reader :path
  61562. def initialize(message, path)
  61563. super(message)
  61564. @path = path
  61565. end
  61566. def is_missing?(path)
  61567. path.gsub(/\.rb$/, '') == self.path.gsub(/\.rb$/, '')
  61568. end
  61569. def self.from_message(message)
  61570. REGEXPS.each do |regexp, capture|
  61571. match = regexp.match(message)
  61572. return MissingSourceFile.new(message, match[capture]) unless match.nil?
  61573. end
  61574. nil
  61575. end
  61576. REGEXPS = [
  61577. [/^no such file to load -- (.+)$/i, 1],
  61578. [/^Missing \w+ (file\s*)?([^\s]+.rb)$/i, 2],
  61579. [/^Missing API definition file in (.+)$/i, 1]
  61580. ]
  61581. end
  61582. module ActiveSupport #:nodoc:
  61583. module CoreExtensions #:nodoc:
  61584. module LoadErrorExtensions #:nodoc:
  61585. module LoadErrorClassMethods #:nodoc:
  61586. def new(*args)
  61587. (self == LoadError && MissingSourceFile.from_message(args.first)) || super
  61588. end
  61589. end
  61590. ::LoadError.extend(LoadErrorClassMethods)
  61591. end
  61592. end
  61593. end
  61594. # Adds the 'around_level' method to Logger.
  61595. class Logger
  61596. def self.define_around_helper(level)
  61597. module_eval <<-end_eval
  61598. def around_#{level}(before_message, after_message, &block)
  61599. self.#{level}(before_message)
  61600. return_value = block.call(self)
  61601. self.#{level}(after_message)
  61602. return return_value
  61603. end
  61604. end_eval
  61605. end
  61606. [:debug, :info, :error, :fatal].each {|level| define_around_helper(level) }
  61607. end# Extends the module object with module and instance accessors for class attributes,
  61608. # just like the native attr* accessors for instance attributes.
  61609. class Module # :nodoc:
  61610. def mattr_reader(*syms)
  61611. syms.each do |sym|
  61612. class_eval(<<-EOS, __FILE__, __LINE__)
  61613. unless defined? @@#{sym}
  61614. @@#{sym} = nil
  61615. end
  61616. def self.#{sym}
  61617. @@#{sym}
  61618. end
  61619. def #{sym}
  61620. @@#{sym}
  61621. end
  61622. EOS
  61623. end
  61624. end
  61625. def mattr_writer(*syms)
  61626. syms.each do |sym|
  61627. class_eval(<<-EOS, __FILE__, __LINE__)
  61628. unless defined? @@#{sym}
  61629. @@#{sym} = nil
  61630. end
  61631. def self.#{sym}=(obj)
  61632. @@#{sym} = obj
  61633. end
  61634. def #{sym}=(obj)
  61635. @@#{sym} = obj
  61636. end
  61637. EOS
  61638. end
  61639. end
  61640. def mattr_accessor(*syms)
  61641. mattr_reader(*syms)
  61642. mattr_writer(*syms)
  61643. end
  61644. end
  61645. class Module
  61646. def delegate(*methods)
  61647. options = methods.pop
  61648. unless options.is_a?(Hash) && to = options[:to]
  61649. raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key"
  61650. end
  61651. methods.each do |method|
  61652. module_eval(<<-EOS, "(__DELEGATION__)", 1)
  61653. def #{method}(*args, &block)
  61654. #{to}.__send__(#{method.inspect}, *args, &block)
  61655. end
  61656. EOS
  61657. end
  61658. end
  61659. endclass Module
  61660. def included_in_classes
  61661. classes = []
  61662. ObjectSpace.each_object(Class) { |k| classes << k if k.included_modules.include?(self) }
  61663. classes.reverse.inject([]) do |unique_classes, klass|
  61664. unique_classes << klass unless unique_classes.collect { |k| k.to_s }.include?(klass.to_s)
  61665. unique_classes
  61666. end
  61667. end
  61668. endclass Module
  61669. # Return the module which contains this one; if this is a root module, such as
  61670. # +::MyModule+, then Object is returned.
  61671. def parent
  61672. parent_name = name.split('::')[0..-2] * '::'
  61673. parent_name.empty? ? Object : parent_name.constantize
  61674. end
  61675. # Return all the parents of this module, ordered from nested outwards. The
  61676. # receiver is not contained within the result.
  61677. def parents
  61678. parents = []
  61679. parts = name.split('::')[0..-2]
  61680. until parts.empty?
  61681. parents << (parts * '::').constantize
  61682. parts.pop
  61683. end
  61684. parents << Object unless parents.include? Object
  61685. parents
  61686. end
  61687. end
  61688. class Module
  61689. def as_load_path
  61690. if self == Object || self == Kernel
  61691. ''
  61692. elsif is_a? Class
  61693. parent == self ? '' : parent.as_load_path
  61694. else
  61695. name.split('::').collect do |word|
  61696. word.underscore
  61697. end * '/'
  61698. end
  61699. end
  61700. endrequire File.dirname(__FILE__) + '/module/inclusion'
  61701. require File.dirname(__FILE__) + '/module/attribute_accessors'
  61702. require File.dirname(__FILE__) + '/module/delegation'
  61703. require File.dirname(__FILE__) + '/module/introspection'
  61704. require File.dirname(__FILE__) + '/module/loading'
  61705. module ActiveSupport #:nodoc:
  61706. module CoreExtensions #:nodoc:
  61707. module Numeric #:nodoc:
  61708. # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
  61709. module Bytes
  61710. def bytes
  61711. self
  61712. end
  61713. alias :byte :bytes
  61714. def kilobytes
  61715. self * 1024
  61716. end
  61717. alias :kilobyte :kilobytes
  61718. def megabytes
  61719. self * 1024.kilobytes
  61720. end
  61721. alias :megabyte :megabytes
  61722. def gigabytes
  61723. self * 1024.megabytes
  61724. end
  61725. alias :gigabyte :gigabytes
  61726. def terabytes
  61727. self * 1024.gigabytes
  61728. end
  61729. alias :terabyte :terabytes
  61730. def petabytes
  61731. self * 1024.terabytes
  61732. end
  61733. alias :petabyte :petabytes
  61734. def exabytes
  61735. self * 1024.petabytes
  61736. end
  61737. alias :exabyte :exabytes
  61738. end
  61739. end
  61740. end
  61741. end
  61742. module ActiveSupport #:nodoc:
  61743. module CoreExtensions #:nodoc:
  61744. module Numeric #:nodoc:
  61745. # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
  61746. #
  61747. # If you need precise date calculations that doesn't just treat months as 30 days, then have
  61748. # a look at Time#advance.
  61749. #
  61750. # Some of these methods are approximations, Ruby's core
  61751. # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
  61752. # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
  61753. # date and time arithmetic
  61754. module Time
  61755. def seconds
  61756. self
  61757. end
  61758. alias :second :seconds
  61759. def minutes
  61760. self * 60
  61761. end
  61762. alias :minute :minutes
  61763. def hours
  61764. self * 60.minutes
  61765. end
  61766. alias :hour :hours
  61767. def days
  61768. self * 24.hours
  61769. end
  61770. alias :day :days
  61771. def weeks
  61772. self * 7.days
  61773. end
  61774. alias :week :weeks
  61775. def fortnights
  61776. self * 2.weeks
  61777. end
  61778. alias :fortnight :fortnights
  61779. def months
  61780. self * 30.days
  61781. end
  61782. alias :month :months
  61783. def years
  61784. (self * 365.25.days).to_i
  61785. end
  61786. alias :year :years
  61787. # Reads best without arguments: 10.minutes.ago
  61788. def ago(time = ::Time.now)
  61789. time - self
  61790. end
  61791. # Reads best with argument: 10.minutes.until(time)
  61792. alias :until :ago
  61793. # Reads best with argument: 10.minutes.since(time)
  61794. def since(time = ::Time.now)
  61795. time + self
  61796. end
  61797. # Reads best without arguments: 10.minutes.from_now
  61798. alias :from_now :since
  61799. end
  61800. end
  61801. end
  61802. end
  61803. require File.dirname(__FILE__) + '/numeric/time'
  61804. require File.dirname(__FILE__) + '/numeric/bytes'
  61805. class Numeric #:nodoc:
  61806. include ActiveSupport::CoreExtensions::Numeric::Time
  61807. include ActiveSupport::CoreExtensions::Numeric::Bytes
  61808. end
  61809. class Object #:nodoc:
  61810. def remove_subclasses_of(*superclasses)
  61811. Class.remove_class(*subclasses_of(*superclasses))
  61812. end
  61813. def subclasses_of(*superclasses)
  61814. subclasses = []
  61815. ObjectSpace.each_object(Class) do |k|
  61816. next if # Exclude this class if
  61817. (k.ancestors & superclasses).empty? || # It's not a subclass of our supers
  61818. superclasses.include?(k) || # It *is* one of the supers
  61819. eval("! defined?(::#{k})") || # It's not defined.
  61820. eval("::#{k}").object_id != k.object_id
  61821. subclasses << k
  61822. end
  61823. subclasses
  61824. end
  61825. def extended_by
  61826. ancestors = class << self; ancestors end
  61827. ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
  61828. end
  61829. def copy_instance_variables_from(object, exclude = [])
  61830. exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables
  61831. instance_variables = object.instance_variables - exclude.map { |name| name.to_s }
  61832. instance_variables.each { |name| instance_variable_set(name, object.instance_variable_get(name)) }
  61833. end
  61834. def extend_with_included_modules_from(object)
  61835. object.extended_by.each { |mod| extend mod }
  61836. end
  61837. def instance_values
  61838. instance_variables.inject({}) do |values, name|
  61839. values[name[1..-1]] = instance_variable_get(name)
  61840. values
  61841. end
  61842. end
  61843. unless defined? instance_exec # 1.9
  61844. def instance_exec(*arguments, &block)
  61845. block.bind(self)[*arguments]
  61846. end
  61847. end
  61848. end
  61849. class Object #:nodoc:
  61850. # A Ruby-ized realization of the K combinator, courtesy of Mikael Brockman.
  61851. #
  61852. # def foo
  61853. # returning values = [] do
  61854. # values << 'bar'
  61855. # values << 'baz'
  61856. # end
  61857. # end
  61858. #
  61859. # foo # => ['bar', 'baz']
  61860. #
  61861. # def foo
  61862. # returning [] do |values|
  61863. # values << 'bar'
  61864. # values << 'baz'
  61865. # end
  61866. # end
  61867. #
  61868. # foo # => ['bar', 'baz']
  61869. #
  61870. def returning(value)
  61871. yield(value)
  61872. value
  61873. end
  61874. def with_options(options)
  61875. yield ActiveSupport::OptionMerger.new(self, options)
  61876. end
  61877. def to_json
  61878. ActiveSupport::JSON.encode(self)
  61879. end
  61880. endrequire File.dirname(__FILE__) + '/object/extending'
  61881. require File.dirname(__FILE__) + '/object/misc'module ActiveSupport #:nodoc:
  61882. module CoreExtensions #:nodoc:
  61883. module Pathname #:nodoc:
  61884. module CleanWithin
  61885. # Clean the paths contained in the provided string.
  61886. def clean_within(string)
  61887. string.gsub(%r{[\w. ]+(/[\w. ]+)+(\.rb)?(\b|$)}) do |path|
  61888. new(path).cleanpath
  61889. end
  61890. end
  61891. end
  61892. end
  61893. end
  61894. end
  61895. require 'pathname'
  61896. require File.dirname(__FILE__) + '/pathname/clean_within'
  61897. class Pathname#:nodoc:
  61898. extend ActiveSupport::CoreExtensions::Pathname::CleanWithin
  61899. end
  61900. class Proc #:nodoc:
  61901. def bind(object)
  61902. block, time = self, Time.now
  61903. (class << object; self end).class_eval do
  61904. method_name = "__bind_#{time.to_i}_#{time.usec}"
  61905. define_method(method_name, &block)
  61906. method = instance_method(method_name)
  61907. remove_method(method_name)
  61908. method
  61909. end.bind(object)
  61910. end
  61911. end
  61912. module ActiveSupport #:nodoc:
  61913. module CoreExtensions #:nodoc:
  61914. module Range #:nodoc:
  61915. # Getting dates in different convenient string representations and other objects
  61916. module Conversions
  61917. DATE_FORMATS = {
  61918. :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
  61919. }
  61920. def self.included(klass) #:nodoc:
  61921. klass.send(:alias_method, :to_default_s, :to_s)
  61922. klass.send(:alias_method, :to_s, :to_formatted_s)
  61923. end
  61924. def to_formatted_s(format = :default)
  61925. DATE_FORMATS[format] ? DATE_FORMATS[format].call(first, last) : to_default_s
  61926. end
  61927. end
  61928. end
  61929. end
  61930. endrequire File.dirname(__FILE__) + '/range/conversions'
  61931. class Range #:nodoc:
  61932. include ActiveSupport::CoreExtensions::Range::Conversions
  61933. end
  61934. module ActiveSupport #:nodoc:
  61935. module CoreExtensions #:nodoc:
  61936. module String #:nodoc:
  61937. # Makes it easier to access parts of a string, such as specific characters and substrings.
  61938. module Access
  61939. # Returns the character at the +position+ treating the string as an array (where 0 is the first character).
  61940. #
  61941. # Examples:
  61942. # "hello".at(0) # => "h"
  61943. # "hello".at(4) # => "o"
  61944. # "hello".at(10) # => nil
  61945. def at(position)
  61946. self[position, 1]
  61947. end
  61948. # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
  61949. #
  61950. # Examples:
  61951. # "hello".from(0) # => "hello"
  61952. # "hello".from(2) # => "llo"
  61953. # "hello".from(10) # => nil
  61954. def from(position)
  61955. self[position..-1]
  61956. end
  61957. # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
  61958. #
  61959. # Examples:
  61960. # "hello".to(0) # => "h"
  61961. # "hello".to(2) # => "hel"
  61962. # "hello".to(10) # => "hello"
  61963. def to(position)
  61964. self[0..position]
  61965. end
  61966. # Returns the first character of the string or the first +limit+ characters.
  61967. #
  61968. # Examples:
  61969. # "hello".first # => "h"
  61970. # "hello".first(2) # => "he"
  61971. # "hello".first(10) # => "hello"
  61972. def first(limit = 1)
  61973. self[0..(limit - 1)]
  61974. end
  61975. # Returns the last character of the string or the last +limit+ characters.
  61976. #
  61977. # Examples:
  61978. # "hello".last # => "o"
  61979. # "hello".last(2) # => "lo"
  61980. # "hello".last(10) # => "hello"
  61981. def last(limit = 1)
  61982. self[(-limit)..-1] || self
  61983. end
  61984. end
  61985. end
  61986. end
  61987. end
  61988. require 'parsedate'
  61989. module ActiveSupport #:nodoc:
  61990. module CoreExtensions #:nodoc:
  61991. module String #:nodoc:
  61992. # Converting strings to other objects
  61993. module Conversions
  61994. # Form can be either :utc (default) or :local.
  61995. def to_time(form = :utc)
  61996. ::Time.send(form, *ParseDate.parsedate(self))
  61997. end
  61998. def to_date
  61999. ::Date.new(*ParseDate.parsedate(self)[0..2])
  62000. end
  62001. end
  62002. end
  62003. end
  62004. endrequire File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector
  62005. module ActiveSupport #:nodoc:
  62006. module CoreExtensions #:nodoc:
  62007. module String #:nodoc:
  62008. # Makes it possible to do "posts".singularize that returns "post" and "MegaCoolClass".underscore that returns "mega_cool_class".
  62009. module Inflections
  62010. def pluralize
  62011. Inflector.pluralize(self)
  62012. end
  62013. def singularize
  62014. Inflector.singularize(self)
  62015. end
  62016. def camelize(first_letter = :upper)
  62017. case first_letter
  62018. when :upper then Inflector.camelize(self, true)
  62019. when :lower then Inflector.camelize(self, false)
  62020. end
  62021. end
  62022. alias_method :camelcase, :camelize
  62023. def titleize
  62024. Inflector.titleize(self)
  62025. end
  62026. alias_method :titlecase, :titleize
  62027. def underscore
  62028. Inflector.underscore(self)
  62029. end
  62030. def dasherize
  62031. Inflector.dasherize(self)
  62032. end
  62033. def demodulize
  62034. Inflector.demodulize(self)
  62035. end
  62036. def tableize
  62037. Inflector.tableize(self)
  62038. end
  62039. def classify
  62040. Inflector.classify(self)
  62041. end
  62042. # Capitalizes the first word and turns underscores into spaces and strips _id, so "employee_salary" becomes "Employee salary"
  62043. # and "author_id" becomes "Author".
  62044. def humanize
  62045. Inflector.humanize(self)
  62046. end
  62047. def foreign_key(separate_class_name_and_id_with_underscore = true)
  62048. Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
  62049. end
  62050. def constantize
  62051. Inflector.constantize(self)
  62052. end
  62053. end
  62054. end
  62055. end
  62056. end
  62057. require 'strscan'
  62058. module ActiveSupport #:nodoc:
  62059. module CoreExtensions #:nodoc:
  62060. module String #:nodoc:
  62061. # Custom string iterators
  62062. module Iterators
  62063. # Yields a single-character string for each character in the string.
  62064. # When $KCODE = 'UTF8', multi-byte characters are yielded appropriately.
  62065. def each_char
  62066. scanner, char = StringScanner.new(self), /./mu
  62067. loop { yield(scanner.scan(char) || break) }
  62068. end
  62069. end
  62070. end
  62071. end
  62072. end
  62073. module ActiveSupport #:nodoc:
  62074. module CoreExtensions #:nodoc:
  62075. module String #:nodoc:
  62076. # Additional string tests.
  62077. module StartsEndsWith
  62078. # Does the string start with the specified +prefix+?
  62079. def starts_with?(prefix)
  62080. prefix = prefix.to_s
  62081. self[0, prefix.length] == prefix
  62082. end
  62083. # Does the string end with the specified +suffix+?
  62084. def ends_with?(suffix)
  62085. suffix = suffix.to_s
  62086. self[-suffix.length, suffix.length] == suffix
  62087. end
  62088. end
  62089. end
  62090. end
  62091. end
  62092. require File.dirname(__FILE__) + '/string/inflections'
  62093. require File.dirname(__FILE__) + '/string/conversions'
  62094. require File.dirname(__FILE__) + '/string/access'
  62095. require File.dirname(__FILE__) + '/string/starts_ends_with'
  62096. require File.dirname(__FILE__) + '/string/iterators'
  62097. class String #:nodoc:
  62098. include ActiveSupport::CoreExtensions::String::Access
  62099. include ActiveSupport::CoreExtensions::String::Conversions
  62100. include ActiveSupport::CoreExtensions::String::Inflections
  62101. include ActiveSupport::CoreExtensions::String::StartsEndsWith
  62102. include ActiveSupport::CoreExtensions::String::Iterators
  62103. end
  62104. class Symbol
  62105. # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
  62106. #
  62107. # # The same as people.collect { |p| p.name }
  62108. # people.collect(&:name)
  62109. #
  62110. # # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
  62111. # people.select(&:manager?).collect(&:salary)
  62112. def to_proc
  62113. Proc.new { |obj, *args| obj.send(self, *args) }
  62114. end
  62115. end
  62116. module ActiveSupport #:nodoc:
  62117. module CoreExtensions #:nodoc:
  62118. module Time #:nodoc:
  62119. # Enables the use of time calculations within Time itself
  62120. module Calculations
  62121. def self.append_features(base) #:nodoc:
  62122. super
  62123. base.extend(ClassMethods)
  62124. end
  62125. module ClassMethods
  62126. # Return the number of days in the given month. If a year is given,
  62127. # February will return the correct number of days for leap years.
  62128. # Otherwise, this method will always report February as having 28
  62129. # days.
  62130. def days_in_month(month, year=nil)
  62131. if month == 2
  62132. !year.nil? && (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) ? 29 : 28
  62133. elsif month <= 7
  62134. month % 2 == 0 ? 30 : 31
  62135. else
  62136. month % 2 == 0 ? 31 : 30
  62137. end
  62138. end
  62139. end
  62140. # Seconds since midnight: Time.now.seconds_since_midnight
  62141. def seconds_since_midnight
  62142. self.hour.hours + self.min.minutes + self.sec + (self.usec/1.0e+6)
  62143. end
  62144. # Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
  62145. # (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
  62146. # minute is passed, then sec and usec is set to 0.
  62147. def change(options)
  62148. ::Time.send(
  62149. self.utc? ? :utc : :local,
  62150. options[:year] || self.year,
  62151. options[:month] || self.month,
  62152. options[:mday] || self.mday,
  62153. options[:hour] || self.hour,
  62154. options[:min] || (options[:hour] ? 0 : self.min),
  62155. options[:sec] || ((options[:hour] || options[:min]) ? 0 : self.sec),
  62156. options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : self.usec)
  62157. )
  62158. end
  62159. # Uses Date to provide precise Time calculations for years, months, and days. The +options+ parameter takes a hash with
  62160. # any of these keys: :months, :days, :years.
  62161. def advance(options)
  62162. d = ::Date.new(year + (options.delete(:years) || 0), month, day)
  62163. d = d >> options.delete(:months) if options[:months]
  62164. d = d + options.delete(:days) if options[:days]
  62165. change(options.merge(:year => d.year, :month => d.month, :mday => d.day))
  62166. end
  62167. # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
  62168. # Do not use this method in combination with x.months, use months_ago instead!
  62169. def ago(seconds)
  62170. seconds.until(self)
  62171. end
  62172. # Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around
  62173. #the Numeric extension. Do not use this method in combination with x.months, use months_since instead!
  62174. def since(seconds)
  62175. seconds.since(self)
  62176. end
  62177. alias :in :since
  62178. # Returns a new Time representing the time a number of specified months ago
  62179. def months_ago(months)
  62180. months_since(-months)
  62181. end
  62182. def months_since(months)
  62183. year, month, mday = self.year, self.month, self.mday
  62184. month += months
  62185. # in case months is negative
  62186. while month < 1
  62187. month += 12
  62188. year -= 1
  62189. end
  62190. # in case months is positive
  62191. while month > 12
  62192. month -= 12
  62193. year += 1
  62194. end
  62195. max = ::Time.days_in_month(month, year)
  62196. mday = max if mday > max
  62197. change(:year => year, :month => month, :mday => mday)
  62198. end
  62199. # Returns a new Time representing the time a number of specified years ago
  62200. def years_ago(years)
  62201. change(:year => self.year - years)
  62202. end
  62203. def years_since(years)
  62204. change(:year => self.year + years)
  62205. end
  62206. # Short-hand for years_ago(1)
  62207. def last_year
  62208. years_ago(1)
  62209. end
  62210. # Short-hand for years_since(1)
  62211. def next_year
  62212. years_since(1)
  62213. end
  62214. # Short-hand for months_ago(1)
  62215. def last_month
  62216. months_ago(1)
  62217. end
  62218. # Short-hand for months_since(1)
  62219. def next_month
  62220. months_since(1)
  62221. end
  62222. # Returns a new Time representing the "start" of this week (Monday, 0:00)
  62223. def beginning_of_week
  62224. days_to_monday = self.wday!=0 ? self.wday-1 : 6
  62225. (self - days_to_monday.days).midnight
  62226. end
  62227. alias :monday :beginning_of_week
  62228. alias :at_beginning_of_week :beginning_of_week
  62229. # Returns a new Time representing the start of the given day in next week (default is Monday).
  62230. def next_week(day = :monday)
  62231. days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
  62232. since(1.week).beginning_of_week.since(days_into_week[day].day).change(:hour => 0)
  62233. end
  62234. # Returns a new Time representing the start of the day (0:00)
  62235. def beginning_of_day
  62236. (self - self.seconds_since_midnight).change(:usec => 0)
  62237. end
  62238. alias :midnight :beginning_of_day
  62239. alias :at_midnight :beginning_of_day
  62240. alias :at_beginning_of_day :beginning_of_day
  62241. # Returns a new Time representing the start of the month (1st of the month, 0:00)
  62242. def beginning_of_month
  62243. #self - ((self.mday-1).days + self.seconds_since_midnight)
  62244. change(:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
  62245. end
  62246. alias :at_beginning_of_month :beginning_of_month
  62247. # Returns a new Time representing the end of the month (last day of the month, 0:00)
  62248. def end_of_month
  62249. #self - ((self.mday-1).days + self.seconds_since_midnight)
  62250. last_day = ::Time.days_in_month( self.month, self.year )
  62251. change(:mday => last_day,:hour => 0, :min => 0, :sec => 0, :usec => 0)
  62252. end
  62253. alias :at_end_of_month :end_of_month
  62254. # Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
  62255. def beginning_of_quarter
  62256. beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
  62257. end
  62258. alias :at_beginning_of_quarter :beginning_of_quarter
  62259. # Returns a new Time representing the start of the year (1st of january, 0:00)
  62260. def beginning_of_year
  62261. change(:month => 1,:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
  62262. end
  62263. alias :at_beginning_of_year :beginning_of_year
  62264. # Convenience method which returns a new Time representing the time 1 day ago
  62265. def yesterday
  62266. self.ago(1.day)
  62267. end
  62268. # Convenience method which returns a new Time representing the time 1 day since the instance time
  62269. def tomorrow
  62270. self.since(1.day)
  62271. end
  62272. end
  62273. end
  62274. end
  62275. end
  62276. require 'date'
  62277. require 'time'
  62278. module ActiveSupport #:nodoc:
  62279. module CoreExtensions #:nodoc:
  62280. module Time #:nodoc:
  62281. # Getting times in different convenient string representations and other objects
  62282. module Conversions
  62283. DATE_FORMATS = {
  62284. :db => "%Y-%m-%d %H:%M:%S",
  62285. :short => "%d %b %H:%M",
  62286. :long => "%B %d, %Y %H:%M",
  62287. :rfc822 => "%a, %d %b %Y %H:%M:%S %z"
  62288. }
  62289. def self.append_features(klass)
  62290. super
  62291. klass.send(:alias_method, :to_default_s, :to_s)
  62292. klass.send(:alias_method, :to_s, :to_formatted_s)
  62293. end
  62294. def to_formatted_s(format = :default)
  62295. DATE_FORMATS[format] ? strftime(DATE_FORMATS[format]).strip : to_default_s
  62296. end
  62297. def to_date
  62298. ::Date.new(year, month, day)
  62299. end
  62300. # To be able to keep Dates and Times interchangeable on conversions
  62301. def to_time
  62302. self
  62303. end
  62304. end
  62305. end
  62306. end
  62307. end
  62308. require File.dirname(__FILE__) + '/time/calculations'
  62309. require File.dirname(__FILE__) + '/time/conversions'
  62310. class Time#:nodoc:
  62311. include ActiveSupport::CoreExtensions::Time::Calculations
  62312. include ActiveSupport::CoreExtensions::Time::Conversions
  62313. end
  62314. Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each { |file| require(file) }
  62315. require 'set'
  62316. require File.dirname(__FILE__) + '/core_ext/module/attribute_accessors'
  62317. require File.dirname(__FILE__) + '/core_ext/load_error'
  62318. require File.dirname(__FILE__) + '/core_ext/kernel'
  62319. module Dependencies #:nodoc:
  62320. extend self
  62321. # Should we turn on Ruby warnings on the first load of dependent files?
  62322. mattr_accessor :warnings_on_first_load
  62323. self.warnings_on_first_load = false
  62324. # All files ever loaded.
  62325. mattr_accessor :history
  62326. self.history = Set.new
  62327. # All files currently loaded.
  62328. mattr_accessor :loaded
  62329. self.loaded = Set.new
  62330. # Should we load files or require them?
  62331. mattr_accessor :mechanism
  62332. self.mechanism = :load
  62333. def load?
  62334. mechanism == :load
  62335. end
  62336. def depend_on(file_name, swallow_load_errors = false)
  62337. require_or_load(file_name)
  62338. rescue LoadError
  62339. raise unless swallow_load_errors
  62340. end
  62341. def associate_with(file_name)
  62342. depend_on(file_name, true)
  62343. end
  62344. def clear
  62345. loaded.clear
  62346. end
  62347. def require_or_load(file_name)
  62348. file_name = $1 if file_name =~ /^(.*)\.rb$/
  62349. return if loaded.include?(file_name)
  62350. # Record that we've seen this file *before* loading it to avoid an
  62351. # infinite loop with mutual dependencies.
  62352. loaded << file_name
  62353. if load?
  62354. begin
  62355. # Enable warnings iff this file has not been loaded before and
  62356. # warnings_on_first_load is set.
  62357. if !warnings_on_first_load or history.include?(file_name)
  62358. load "#{file_name}.rb"
  62359. else
  62360. enable_warnings { load "#{file_name}.rb" }
  62361. end
  62362. rescue
  62363. loaded.delete file_name
  62364. raise
  62365. end
  62366. else
  62367. require file_name
  62368. end
  62369. # Record history *after* loading so first load gets warnings.
  62370. history << file_name
  62371. end
  62372. class LoadingModule
  62373. # Old style environment.rb referenced this method directly. Please note, it doesn't
  62374. # actualy *do* anything any more.
  62375. def self.root(*args)
  62376. if defined?(RAILS_DEFAULT_LOGGER)
  62377. RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
  62378. RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
  62379. end
  62380. end
  62381. end
  62382. end
  62383. Object.send(:define_method, :require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
  62384. Object.send(:define_method, :require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
  62385. Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
  62386. class Module #:nodoc:
  62387. # Rename the original handler so we can chain it to the new one
  62388. alias :rails_original_const_missing :const_missing
  62389. # Use const_missing to autoload associations so we don't have to
  62390. # require_association when using single-table inheritance.
  62391. def const_missing(class_id)
  62392. file_name = class_id.to_s.demodulize.underscore
  62393. file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
  62394. begin
  62395. require_dependency(file_path)
  62396. brief_name = self == Object ? '' : "#{name}::"
  62397. raise NameError.new("uninitialized constant #{brief_name}#{class_id}") unless const_defined?(class_id)
  62398. return const_get(class_id)
  62399. rescue MissingSourceFile => e
  62400. # Re-raise the error if it does not concern the file we were trying to load.
  62401. raise unless e.is_missing? file_path
  62402. # Look for a directory in the load path that we ought to load.
  62403. if $LOAD_PATH.any? { |base| File.directory? "#{base}/#{file_path}" }
  62404. mod = Module.new
  62405. const_set class_id, mod # Create the new module
  62406. return mod
  62407. end
  62408. # Attempt to access the name from the parent, unless we don't have a valid
  62409. # parent, or the constant is already defined in the parent. If the latter
  62410. # is the case, then we are being queried via self::class_id, and we should
  62411. # avoid returning the constant from the parent if possible.
  62412. if parent && parent != self && ! parents.any? { |p| p.const_defined?(class_id) }
  62413. suppress(NameError) do
  62414. return parent.send(:const_missing, class_id)
  62415. end
  62416. end
  62417. raise NameError.new("uninitialized constant #{class_id}").copy_blame!(e)
  62418. end
  62419. end
  62420. end
  62421. class Class
  62422. def const_missing(class_id)
  62423. if [Object, Kernel].include?(self) || parent == self
  62424. super
  62425. else
  62426. parent.send :const_missing, class_id
  62427. end
  62428. end
  62429. end
  62430. class Object #:nodoc:
  62431. def load(file, *extras)
  62432. super(file, *extras)
  62433. rescue Object => exception
  62434. exception.blame_file! file
  62435. raise
  62436. end
  62437. def require(file, *extras)
  62438. super(file, *extras)
  62439. rescue Object => exception
  62440. exception.blame_file! file
  62441. raise
  62442. end
  62443. end
  62444. # Add file-blaming to exceptions
  62445. class Exception #:nodoc:
  62446. def blame_file!(file)
  62447. (@blamed_files ||= []).unshift file
  62448. end
  62449. def blamed_files
  62450. @blamed_files ||= []
  62451. end
  62452. def describe_blame
  62453. return nil if blamed_files.empty?
  62454. "This error occured while loading the following files:\n #{blamed_files.join "\n "}"
  62455. end
  62456. def copy_blame!(exc)
  62457. @blamed_files = exc.blamed_files.clone
  62458. self
  62459. end
  62460. endInflector.inflections do |inflect|
  62461. inflect.plural(/$/, 's')
  62462. inflect.plural(/s$/i, 's')
  62463. inflect.plural(/(ax|test)is$/i, '\1es')
  62464. inflect.plural(/(octop|vir)us$/i, '\1i')
  62465. inflect.plural(/(alias|status)$/i, '\1es')
  62466. inflect.plural(/(bu)s$/i, '\1ses')
  62467. inflect.plural(/(buffal|tomat)o$/i, '\1oes')
  62468. inflect.plural(/([ti])um$/i, '\1a')
  62469. inflect.plural(/sis$/i, 'ses')
  62470. inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
  62471. inflect.plural(/(hive)$/i, '\1s')
  62472. inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
  62473. inflect.plural(/([^aeiouy]|qu)ies$/i, '\1y')
  62474. inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
  62475. inflect.plural(/(matr|vert|ind)ix|ex$/i, '\1ices')
  62476. inflect.plural(/([m|l])ouse$/i, '\1ice')
  62477. inflect.plural(/^(ox)$/i, '\1en')
  62478. inflect.plural(/(quiz)$/i, '\1zes')
  62479. inflect.singular(/s$/i, '')
  62480. inflect.singular(/(n)ews$/i, '\1ews')
  62481. inflect.singular(/([ti])a$/i, '\1um')
  62482. inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
  62483. inflect.singular(/(^analy)ses$/i, '\1sis')
  62484. inflect.singular(/([^f])ves$/i, '\1fe')
  62485. inflect.singular(/(hive)s$/i, '\1')
  62486. inflect.singular(/(tive)s$/i, '\1')
  62487. inflect.singular(/([lr])ves$/i, '\1f')
  62488. inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
  62489. inflect.singular(/(s)eries$/i, '\1eries')
  62490. inflect.singular(/(m)ovies$/i, '\1ovie')
  62491. inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
  62492. inflect.singular(/([m|l])ice$/i, '\1ouse')
  62493. inflect.singular(/(bus)es$/i, '\1')
  62494. inflect.singular(/(o)es$/i, '\1')
  62495. inflect.singular(/(shoe)s$/i, '\1')
  62496. inflect.singular(/(cris|ax|test)es$/i, '\1is')
  62497. inflect.singular(/([octop|vir])i$/i, '\1us')
  62498. inflect.singular(/(alias|status)es$/i, '\1')
  62499. inflect.singular(/^(ox)en/i, '\1')
  62500. inflect.singular(/(vert|ind)ices$/i, '\1ex')
  62501. inflect.singular(/(matr)ices$/i, '\1ix')
  62502. inflect.singular(/(quiz)zes$/i, '\1')
  62503. inflect.irregular('person', 'people')
  62504. inflect.irregular('man', 'men')
  62505. inflect.irregular('child', 'children')
  62506. inflect.irregular('sex', 'sexes')
  62507. inflect.irregular('move', 'moves')
  62508. inflect.uncountable(%w(equipment information rice money species series fish sheep))
  62509. end
  62510. require 'singleton'
  62511. # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
  62512. # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
  62513. # in inflections.rb.
  62514. module Inflector
  62515. # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
  62516. # inflection rules. Examples:
  62517. #
  62518. # Inflector.inflections do |inflect|
  62519. # inflect.plural /^(ox)$/i, '\1\2en'
  62520. # inflect.singular /^(ox)en/i, '\1'
  62521. #
  62522. # inflect.irregular 'octopus', 'octopi'
  62523. #
  62524. # inflect.uncountable "equipment"
  62525. # end
  62526. #
  62527. # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
  62528. # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
  62529. # already have been loaded.
  62530. class Inflections
  62531. include Singleton
  62532. attr_reader :plurals, :singulars, :uncountables
  62533. def initialize
  62534. @plurals, @singulars, @uncountables = [], [], []
  62535. end
  62536. # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
  62537. # The replacement should always be a string that may include references to the matched data from the rule.
  62538. def plural(rule, replacement)
  62539. @plurals.insert(0, [rule, replacement])
  62540. end
  62541. # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
  62542. # The replacement should always be a string that may include references to the matched data from the rule.
  62543. def singular(rule, replacement)
  62544. @singulars.insert(0, [rule, replacement])
  62545. end
  62546. # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
  62547. # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
  62548. #
  62549. # Examples:
  62550. # irregular 'octopus', 'octopi'
  62551. # irregular 'person', 'people'
  62552. def irregular(singular, plural)
  62553. plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
  62554. singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
  62555. end
  62556. # Add uncountable words that shouldn't be attempted inflected.
  62557. #
  62558. # Examples:
  62559. # uncountable "money"
  62560. # uncountable "money", "information"
  62561. # uncountable %w( money information rice )
  62562. def uncountable(*words)
  62563. (@uncountables << words).flatten!
  62564. end
  62565. # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
  62566. # the options are: :plurals, :singulars, :uncountables
  62567. #
  62568. # Examples:
  62569. # clear :all
  62570. # clear :plurals
  62571. def clear(scope = :all)
  62572. case scope
  62573. when :all
  62574. @plurals, @singulars, @uncountables = [], [], []
  62575. else
  62576. instance_variable_set "@#{scope}", []
  62577. end
  62578. end
  62579. end
  62580. extend self
  62581. def inflections
  62582. if block_given?
  62583. yield Inflections.instance
  62584. else
  62585. Inflections.instance
  62586. end
  62587. end
  62588. def pluralize(word)
  62589. result = word.to_s.dup
  62590. if inflections.uncountables.include?(result.downcase)
  62591. result
  62592. else
  62593. inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
  62594. result
  62595. end
  62596. end
  62597. def singularize(word)
  62598. result = word.to_s.dup
  62599. if inflections.uncountables.include?(result.downcase)
  62600. result
  62601. else
  62602. inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
  62603. result
  62604. end
  62605. end
  62606. def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
  62607. if first_letter_in_uppercase
  62608. lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
  62609. else
  62610. lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
  62611. end
  62612. end
  62613. def titleize(word)
  62614. humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize }
  62615. end
  62616. def underscore(camel_cased_word)
  62617. camel_cased_word.to_s.gsub(/::/, '/').
  62618. gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
  62619. gsub(/([a-z\d])([A-Z])/,'\1_\2').
  62620. tr("-", "_").
  62621. downcase
  62622. end
  62623. def dasherize(underscored_word)
  62624. underscored_word.gsub(/_/, '-')
  62625. end
  62626. def humanize(lower_case_and_underscored_word)
  62627. lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
  62628. end
  62629. def demodulize(class_name_in_module)
  62630. class_name_in_module.to_s.gsub(/^.*::/, '')
  62631. end
  62632. def tableize(class_name)
  62633. pluralize(underscore(class_name))
  62634. end
  62635. def classify(table_name)
  62636. camelize(singularize(table_name))
  62637. end
  62638. def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
  62639. underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
  62640. end
  62641. def constantize(camel_cased_word)
  62642. raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" unless
  62643. /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ camel_cased_word
  62644. camel_cased_word = "::#{camel_cased_word}" unless $1
  62645. Object.module_eval(camel_cased_word, __FILE__, __LINE__)
  62646. end
  62647. def ordinalize(number)
  62648. if (11..13).include?(number.to_i % 100)
  62649. "#{number}th"
  62650. else
  62651. case number.to_i % 10
  62652. when 1: "#{number}st"
  62653. when 2: "#{number}nd"
  62654. when 3: "#{number}rd"
  62655. else "#{number}th"
  62656. end
  62657. end
  62658. end
  62659. end
  62660. require File.dirname(__FILE__) + '/inflections'
  62661. module ActiveSupport
  62662. module JSON #:nodoc:
  62663. module Encoders #:nodoc:
  62664. define_encoder Object do |object|
  62665. object.instance_values.to_json
  62666. end
  62667. define_encoder TrueClass do
  62668. 'true'
  62669. end
  62670. define_encoder FalseClass do
  62671. 'false'
  62672. end
  62673. define_encoder NilClass do
  62674. 'null'
  62675. end
  62676. define_encoder String do |string|
  62677. returning value = '"' do
  62678. string.each_char do |char|
  62679. value << case
  62680. when char == "\010": '\b'
  62681. when char == "\f": '\f'
  62682. when char == "\n": '\n'
  62683. when char == "\r": '\r'
  62684. when char == "\t": '\t'
  62685. when char == '"': '\"'
  62686. when char == '\\': '\\\\'
  62687. when char.length > 1: "\\u#{'%04x' % char.unpack('U').first}"
  62688. else; char
  62689. end
  62690. end
  62691. value << '"'
  62692. end
  62693. end
  62694. define_encoder Numeric do |numeric|
  62695. numeric.to_s
  62696. end
  62697. define_encoder Symbol do |symbol|
  62698. symbol.to_s.to_json
  62699. end
  62700. define_encoder Enumerable do |enumerable|
  62701. "[#{enumerable.map { |value| value.to_json } * ', '}]"
  62702. end
  62703. define_encoder Hash do |hash|
  62704. returning result = '{' do
  62705. result << hash.map do |pair|
  62706. pair.map { |value| value.to_json } * ': '
  62707. end * ', '
  62708. result << '}'
  62709. end
  62710. end
  62711. define_encoder Regexp do |regexp|
  62712. regexp.inspect
  62713. end
  62714. end
  62715. end
  62716. end
  62717. module ActiveSupport
  62718. module JSON #:nodoc:
  62719. module Encoders
  62720. mattr_accessor :encoders
  62721. @@encoders = {}
  62722. class << self
  62723. def define_encoder(klass, &block)
  62724. encoders[klass] = block
  62725. end
  62726. def [](klass)
  62727. klass.ancestors.each do |k|
  62728. encoder = encoders[k]
  62729. return encoder if encoder
  62730. end
  62731. end
  62732. end
  62733. end
  62734. end
  62735. end
  62736. Dir[File.dirname(__FILE__) + '/encoders/*.rb'].each do |file|
  62737. require file[0..-4]
  62738. end
  62739. require 'active_support/json/encoders'
  62740. module ActiveSupport
  62741. module JSON #:nodoc:
  62742. class CircularReferenceError < StandardError #:nodoc:
  62743. end
  62744. # returns the literal string as its JSON encoded form. Useful for passing javascript variables into functions.
  62745. #
  62746. # page.call 'Element.show', ActiveSupport::JSON::Variable.new("$$(#items li)")
  62747. class Variable < String #:nodoc:
  62748. def to_json
  62749. self
  62750. end
  62751. end
  62752. class << self
  62753. REFERENCE_STACK_VARIABLE = :json_reference_stack
  62754. def encode(value)
  62755. raise_on_circular_reference(value) do
  62756. Encoders[value.class].call(value)
  62757. end
  62758. end
  62759. protected
  62760. def raise_on_circular_reference(value)
  62761. stack = Thread.current[REFERENCE_STACK_VARIABLE] ||= []
  62762. raise CircularReferenceError, 'object references itself' if
  62763. stack.include? value
  62764. stack << value
  62765. yield
  62766. ensure
  62767. stack.pop
  62768. end
  62769. end
  62770. end
  62771. end
  62772. module ActiveSupport
  62773. class OptionMerger #:nodoc:
  62774. instance_methods.each do |method|
  62775. undef_method(method) if method !~ /^(__|instance_eval)/
  62776. end
  62777. def initialize(context, options)
  62778. @context, @options = context, options
  62779. end
  62780. private
  62781. def method_missing(method, *arguments, &block)
  62782. merge_argument_options! arguments
  62783. @context.send(method, *arguments, &block)
  62784. end
  62785. def merge_argument_options!(arguments)
  62786. arguments << if arguments.last.respond_to? :merge!
  62787. arguments.pop.dup.merge!(@options)
  62788. else
  62789. @options.dup
  62790. end
  62791. end
  62792. end
  62793. end
  62794. class OrderedHash < Array #:nodoc:
  62795. def []=(key, value)
  62796. if pair = find_pair(key)
  62797. pair.pop
  62798. pair << value
  62799. else
  62800. self << [key, value]
  62801. end
  62802. end
  62803. def [](key)
  62804. pair = find_pair(key)
  62805. pair ? pair.last : nil
  62806. end
  62807. def keys
  62808. self.collect { |i| i.first }
  62809. end
  62810. private
  62811. def find_pair(key)
  62812. self.each { |i| return i if i.first == key }
  62813. return false
  62814. end
  62815. end
  62816. class OrderedOptions < OrderedHash #:nodoc:
  62817. def []=(key, value)
  62818. super(key.to_sym, value)
  62819. end
  62820. def [](key)
  62821. super(key.to_sym)
  62822. end
  62823. def method_missing(name, *args)
  62824. if name.to_s =~ /(.*)=$/
  62825. self[$1.to_sym] = args.first
  62826. else
  62827. self[name]
  62828. end
  62829. end
  62830. end# Classes that include this module will automatically be reloaded
  62831. # by the Rails dispatcher when Dependencies.mechanism = :load.
  62832. module Reloadable
  62833. class << self
  62834. def included(base) #nodoc:
  62835. raise TypeError, "Only Classes can be Reloadable!" unless base.is_a? Class
  62836. unless base.respond_to?(:reloadable?)
  62837. class << base
  62838. define_method(:reloadable?) { true }
  62839. end
  62840. end
  62841. end
  62842. def reloadable_classes
  62843. included_in_classes.select { |klass| klass.reloadable? }
  62844. end
  62845. end
  62846. # Captures the common pattern where a base class should not be reloaded,
  62847. # but its subclasses should be.
  62848. module Subclasses
  62849. def self.included(base) #nodoc:
  62850. base.send :include, Reloadable
  62851. (class << base; self; end).send(:define_method, :reloadable?) do
  62852. base != self
  62853. end
  62854. end
  62855. end
  62856. end# A value object representing a time zone. A time zone is simply a named
  62857. # offset (in seconds) from GMT. Note that two time zone objects are only
  62858. # equivalent if they have both the same offset, and the same name.
  62859. #
  62860. # A TimeZone instance may be used to convert a Time value to the corresponding
  62861. # time zone.
  62862. #
  62863. # The class also includes #all, which returns a list of all TimeZone objects.
  62864. class TimeZone
  62865. include Comparable
  62866. attr_reader :name, :utc_offset
  62867. # Create a new TimeZone object with the given name and offset. The offset is
  62868. # the number of seconds that this time zone is offset from UTC (GMT). Seconds
  62869. # were chosen as the offset unit because that is the unit that Ruby uses
  62870. # to represent time zone offsets (see Time#utc_offset).
  62871. def initialize(name, utc_offset)
  62872. @name = name
  62873. @utc_offset = utc_offset
  62874. end
  62875. # Returns the offset of this time zone as a formatted string, of the
  62876. # format "+HH:MM". If the offset is zero, this returns the empty
  62877. # string. If +colon+ is false, a colon will not be inserted into the
  62878. # result.
  62879. def formatted_offset( colon=true )
  62880. return "" if utc_offset == 0
  62881. sign = (utc_offset < 0 ? -1 : 1)
  62882. hours = utc_offset.abs / 3600
  62883. minutes = (utc_offset.abs % 3600) / 60
  62884. "%+03d%s%02d" % [ hours * sign, colon ? ":" : "", minutes ]
  62885. end
  62886. # Compute and return the current time, in the time zone represented by
  62887. # +self+.
  62888. def now
  62889. adjust(Time.now)
  62890. end
  62891. # Return the current date in this time zone.
  62892. def today
  62893. now.to_date
  62894. end
  62895. # Adjust the given time to the time zone represented by +self+.
  62896. def adjust(time)
  62897. time = time.to_time
  62898. time + utc_offset - time.utc_offset
  62899. end
  62900. # Reinterprets the given time value as a time in the current time
  62901. # zone, and then adjusts it to return the corresponding time in the
  62902. # local time zone.
  62903. def unadjust(time)
  62904. time = Time.local(*time.to_time.to_a)
  62905. time - utc_offset + time.utc_offset
  62906. end
  62907. # Compare this time zone to the parameter. The two are comapred first on
  62908. # their offsets, and then by name.
  62909. def <=>(zone)
  62910. result = (utc_offset <=> zone.utc_offset)
  62911. result = (name <=> zone.name) if result == 0
  62912. result
  62913. end
  62914. # Returns a textual representation of this time zone.
  62915. def to_s
  62916. "(GMT#{formatted_offset}) #{name}"
  62917. end
  62918. @@zones = nil
  62919. class << self
  62920. # Create a new TimeZone instance with the given name and offset.
  62921. def create(name, offset)
  62922. zone = allocate
  62923. zone.send :initialize, name, offset
  62924. zone
  62925. end
  62926. # Return a TimeZone instance with the given name, or +nil+ if no
  62927. # such TimeZone instance exists. (This exists to support the use of
  62928. # this class with the #composed_of macro.)
  62929. def new(name)
  62930. self[name]
  62931. end
  62932. # Return an array of all TimeZone objects. There are multiple TimeZone
  62933. # objects per time zone, in many cases, to make it easier for users to
  62934. # find their own time zone.
  62935. def all
  62936. unless @@zones
  62937. @@zones = []
  62938. [[-43_200, "International Date Line West" ],
  62939. [-39_600, "Midway Island", "Samoa" ],
  62940. [-36_000, "Hawaii" ],
  62941. [-32_400, "Alaska" ],
  62942. [-28_800, "Pacific Time (US & Canada)", "Tijuana" ],
  62943. [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "La Paz",
  62944. "Mazatlan", "Arizona" ],
  62945. [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara",
  62946. "Mexico City", "Monterrey", "Central America" ],
  62947. [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota",
  62948. "Lima", "Quito" ],
  62949. [-14_400, "Atlantic Time (Canada)", "Caracas", "La Paz", "Santiago" ],
  62950. [-12_600, "Newfoundland" ],
  62951. [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ],
  62952. [ -7_200, "Mid-Atlantic" ],
  62953. [ -3_600, "Azores", "Cape Verde Is." ],
  62954. [ 0, "Dublin", "Edinburgh", "Lisbon", "London", "Casablanca",
  62955. "Monrovia" ],
  62956. [ 3_600, "Belgrade", "Bratislava", "Budapest", "Ljubljana", "Prague",
  62957. "Sarajevo", "Skopje", "Warsaw", "Zagreb", "Brussels",
  62958. "Copenhagen", "Madrid", "Paris", "Amsterdam", "Berlin",
  62959. "Bern", "Rome", "Stockholm", "Vienna",
  62960. "West Central Africa" ],
  62961. [ 7_200, "Bucharest", "Cairo", "Helsinki", "Kyev", "Riga", "Sofia",
  62962. "Tallinn", "Vilnius", "Athens", "Istanbul", "Minsk",
  62963. "Jerusalem", "Harare", "Pretoria" ],
  62964. [ 10_800, "Moscow", "St. Petersburg", "Volgograd", "Kuwait", "Riyadh",
  62965. "Nairobi", "Baghdad" ],
  62966. [ 12_600, "Tehran" ],
  62967. [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ],
  62968. [ 16_200, "Kabul" ],
  62969. [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ],
  62970. [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi" ],
  62971. [ 20_700, "Kathmandu" ],
  62972. [ 21_600, "Astana", "Dhaka", "Sri Jayawardenepura", "Almaty",
  62973. "Novosibirsk" ],
  62974. [ 23_400, "Rangoon" ],
  62975. [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ],
  62976. [ 28_800, "Beijing", "Chongqing", "Hong Kong", "Urumqi",
  62977. "Kuala Lumpur", "Singapore", "Taipei", "Perth", "Irkutsk",
  62978. "Ulaan Bataar" ],
  62979. [ 32_400, "Seoul", "Osaka", "Sapporo", "Tokyo", "Yakutsk" ],
  62980. [ 34_200, "Darwin", "Adelaide" ],
  62981. [ 36_000, "Canberra", "Melbourne", "Sydney", "Brisbane", "Hobart",
  62982. "Vladivostok", "Guam", "Port Moresby" ],
  62983. [ 39_600, "Magadan", "Solomon Is.", "New Caledonia" ],
  62984. [ 43_200, "Fiji", "Kamchatka", "Marshall Is.", "Auckland",
  62985. "Wellington" ],
  62986. [ 46_800, "Nuku'alofa" ]].
  62987. each do |offset, *places|
  62988. places.each { |place| @@zones << create(place, offset).freeze }
  62989. end
  62990. @@zones.sort!
  62991. end
  62992. @@zones
  62993. end
  62994. # Locate a specific time zone object. If the argument is a string, it
  62995. # is interpreted to mean the name of the timezone to locate. If it is a
  62996. # numeric value it is either the hour offset, or the second offset, of the
  62997. # timezone to find. (The first one with that offset will be returned.)
  62998. # Returns +nil+ if no such time zone is known to the system.
  62999. def [](arg)
  63000. case arg
  63001. when String
  63002. all.find { |z| z.name == arg }
  63003. when Numeric
  63004. arg *= 3600 if arg.abs <= 13
  63005. all.find { |z| z.utc_offset == arg.to_i }
  63006. else
  63007. raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
  63008. end
  63009. end
  63010. # A regular expression that matches the names of all time zones in
  63011. # the USA.
  63012. US_ZONES = /US|Arizona|Indiana|Hawaii|Alaska/
  63013. # A convenience method for returning a collection of TimeZone objects
  63014. # for time zones in the USA.
  63015. def us_zones
  63016. all.find_all { |z| z.name =~ US_ZONES }
  63017. end
  63018. end
  63019. end
  63020. #!/usr/bin/env ruby
  63021. #--
  63022. # Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
  63023. # All rights reserved.
  63024. # Permission is granted for use, copying, modification, distribution,
  63025. # and distribution of modified versions of this work as long as the
  63026. # above copyright notice is included.
  63027. #++
  63028. module Builder #:nodoc:
  63029. # BlankSlate provides an abstract base class with no predefined
  63030. # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
  63031. # BlankSlate is useful as a base class when writing classes that
  63032. # depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
  63033. class BlankSlate #:nodoc:
  63034. class << self
  63035. def hide(name)
  63036. undef_method name if
  63037. instance_methods.include?(name.to_s) and
  63038. name !~ /^(__|instance_eval)/
  63039. end
  63040. end
  63041. instance_methods.each { |m| hide(m) }
  63042. end
  63043. end
  63044. # Since Ruby is very dynamic, methods added to the ancestors of
  63045. # BlankSlate <em>after BlankSlate is defined</em> will show up in the
  63046. # list of available BlankSlate methods. We handle this by defining a hook in the Object and Kernel classes that will hide any defined
  63047. module Kernel #:nodoc:
  63048. class << self
  63049. alias_method :blank_slate_method_added, :method_added
  63050. def method_added(name)
  63051. blank_slate_method_added(name)
  63052. return if self != Kernel
  63053. Builder::BlankSlate.hide(name)
  63054. end
  63055. end
  63056. end
  63057. class Object #:nodoc:
  63058. class << self
  63059. alias_method :blank_slate_method_added, :method_added
  63060. def method_added(name)
  63061. blank_slate_method_added(name)
  63062. return if self != Object
  63063. Builder::BlankSlate.hide(name)
  63064. end
  63065. end
  63066. end
  63067. #!/usr/bin/env ruby
  63068. require 'builder/blankslate'
  63069. module Builder #:nodoc:
  63070. # Generic error for builder
  63071. class IllegalBlockError < RuntimeError #:nodoc:
  63072. end
  63073. # XmlBase is a base class for building XML builders. See
  63074. # Builder::XmlMarkup and Builder::XmlEvents for examples.
  63075. class XmlBase < BlankSlate #:nodoc:
  63076. # Create an XML markup builder.
  63077. #
  63078. # out:: Object receiving the markup.1 +out+ must respond to
  63079. # <tt><<</tt>.
  63080. # indent:: Number of spaces used for indentation (0 implies no
  63081. # indentation and no line breaks).
  63082. # initial:: Level of initial indentation.
  63083. #
  63084. def initialize(indent=0, initial=0)
  63085. @indent = indent
  63086. @level = initial
  63087. end
  63088. # Create a tag named +sym+. Other than the first argument which
  63089. # is the tag name, the arguements are the same as the tags
  63090. # implemented via <tt>method_missing</tt>.
  63091. def tag!(sym, *args, &block)
  63092. self.__send__(sym, *args, &block)
  63093. end
  63094. # Create XML markup based on the name of the method. This method
  63095. # is never invoked directly, but is called for each markup method
  63096. # in the markup block.
  63097. def method_missing(sym, *args, &block)
  63098. text = nil
  63099. attrs = nil
  63100. sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol)
  63101. args.each do |arg|
  63102. case arg
  63103. when Hash
  63104. attrs ||= {}
  63105. attrs.merge!(arg)
  63106. else
  63107. text ||= ''
  63108. text << arg.to_s
  63109. end
  63110. end
  63111. if block
  63112. unless text.nil?
  63113. raise ArgumentError, "XmlMarkup cannot mix a text argument with a block"
  63114. end
  63115. _capture_outer_self(block) if @self.nil?
  63116. _indent
  63117. _start_tag(sym, attrs)
  63118. _newline
  63119. _nested_structures(block)
  63120. _indent
  63121. _end_tag(sym)
  63122. _newline
  63123. elsif text.nil?
  63124. _indent
  63125. _start_tag(sym, attrs, true)
  63126. _newline
  63127. else
  63128. _indent
  63129. _start_tag(sym, attrs)
  63130. text! text
  63131. _end_tag(sym)
  63132. _newline
  63133. end
  63134. @target
  63135. end
  63136. # Append text to the output target. Escape any markup. May be
  63137. # used within the markup brackets as:
  63138. #
  63139. # builder.p { br; text! "HI" } #=> <p><br/>HI</p>
  63140. def text!(text)
  63141. _text(_escape(text))
  63142. end
  63143. # Append text to the output target without escaping any markup.
  63144. # May be used within the markup brackets as:
  63145. #
  63146. # builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
  63147. #
  63148. # This is useful when using non-builder enabled software that
  63149. # generates strings. Just insert the string directly into the
  63150. # builder without changing the inserted markup.
  63151. #
  63152. # It is also useful for stacking builder objects. Builders only
  63153. # use <tt><<</tt> to append to the target, so by supporting this
  63154. # method/operation builders can use other builders as their
  63155. # targets.
  63156. def <<(text)
  63157. _text(text)
  63158. end
  63159. # For some reason, nil? is sent to the XmlMarkup object. If nil?
  63160. # is not defined and method_missing is invoked, some strange kind
  63161. # of recursion happens. Since nil? won't ever be an XML tag, it
  63162. # is pretty safe to define it here. (Note: this is an example of
  63163. # cargo cult programming,
  63164. # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
  63165. def nil?
  63166. false
  63167. end
  63168. private
  63169. def _escape(text)
  63170. text.
  63171. gsub(%r{&}, '&amp;').
  63172. gsub(%r{<}, '&lt;').
  63173. gsub(%r{>}, '&gt;')
  63174. end
  63175. def _capture_outer_self(block)
  63176. @self = eval("self", block)
  63177. end
  63178. def _newline
  63179. return if @indent == 0
  63180. text! "\n"
  63181. end
  63182. def _indent
  63183. return if @indent == 0 || @level == 0
  63184. text!(" " * (@level * @indent))
  63185. end
  63186. def _nested_structures(block)
  63187. @level += 1
  63188. block.call(self)
  63189. ensure
  63190. @level -= 1
  63191. end
  63192. end
  63193. end
  63194. #!/usr/bin/env ruby
  63195. #--
  63196. # Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
  63197. # All rights reserved.
  63198. # Permission is granted for use, copying, modification, distribution,
  63199. # and distribution of modified versions of this work as long as the
  63200. # above copyright notice is included.
  63201. #++
  63202. require 'builder/xmlmarkup'
  63203. module Builder
  63204. # Create a series of SAX-like XML events (e.g. start_tag, end_tag)
  63205. # from the markup code. XmlEvent objects are used in a way similar
  63206. # to XmlMarkup objects, except that a series of events are generated
  63207. # and passed to a handler rather than generating character-based
  63208. # markup.
  63209. #
  63210. # Usage:
  63211. # xe = Builder::XmlEvents.new(hander)
  63212. # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler.
  63213. #
  63214. # Indentation may also be selected by providing value for the
  63215. # indentation size and initial indentation level.
  63216. #
  63217. # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level)
  63218. #
  63219. # == XML Event Handler
  63220. #
  63221. # The handler object must expect the following events.
  63222. #
  63223. # [<tt>start_tag(tag, attrs)</tt>]
  63224. # Announces that a new tag has been found. +tag+ is the name of
  63225. # the tag and +attrs+ is a hash of attributes for the tag.
  63226. #
  63227. # [<tt>end_tag(tag)</tt>]
  63228. # Announces that an end tag for +tag+ has been found.
  63229. #
  63230. # [<tt>text(text)</tt>]
  63231. # Announces that a string of characters (+text+) has been found.
  63232. # A series of characters may be broken up into more than one
  63233. # +text+ call, so the client cannot assume that a single
  63234. # callback contains all the text data.
  63235. #
  63236. class XmlEvents < XmlMarkup #:nodoc:
  63237. def text!(text)
  63238. @target.text(text)
  63239. end
  63240. def _start_tag(sym, attrs, end_too=false)
  63241. @target.start_tag(sym, attrs)
  63242. _end_tag(sym) if end_too
  63243. end
  63244. def _end_tag(sym)
  63245. @target.end_tag(sym)
  63246. end
  63247. end
  63248. end
  63249. #!/usr/bin/env ruby
  63250. #--
  63251. # Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
  63252. # All rights reserved.
  63253. # Permission is granted for use, copying, modification, distribution,
  63254. # and distribution of modified versions of this work as long as the
  63255. # above copyright notice is included.
  63256. #++
  63257. # Provide a flexible and easy to use Builder for creating XML markup.
  63258. # See XmlBuilder for usage details.
  63259. require 'builder/xmlbase'
  63260. module Builder
  63261. # Create XML markup easily. All (well, almost all) methods sent to
  63262. # an XmlMarkup object will be translated to the equivalent XML
  63263. # markup. Any method with a block will be treated as an XML markup
  63264. # tag with nested markup in the block.
  63265. #
  63266. # Examples will demonstrate this easier than words. In the
  63267. # following, +xm+ is an +XmlMarkup+ object.
  63268. #
  63269. # xm.em("emphasized") # => <em>emphasized</em>
  63270. # xm.em { xmm.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
  63271. # xm.a("A Link", "href"=>"http://onestepback.org")
  63272. # # => <a href="http://onestepback.org">A Link</a>
  63273. # xm.div { br } # => <div><br/></div>
  63274. # xm.target("name"=>"compile", "option"=>"fast")
  63275. # # => <target option="fast" name="compile"\>
  63276. # # NOTE: order of attributes is not specified.
  63277. #
  63278. # xm.instruct! # <?xml version="1.0" encoding="UTF-8"?>
  63279. # xm.html { # <html>
  63280. # xm.head { # <head>
  63281. # xm.title("History") # <title>History</title>
  63282. # } # </head>
  63283. # xm.body { # <body>
  63284. # xm.comment! "HI" # <!-- HI -->
  63285. # xm.h1("Header") # <h1>Header</h1>
  63286. # xm.p("paragraph") # <p>paragraph</p>
  63287. # } # </body>
  63288. # } # </html>
  63289. #
  63290. # == Notes:
  63291. #
  63292. # * The order that attributes are inserted in markup tags is
  63293. # undefined.
  63294. #
  63295. # * Sometimes you wish to insert text without enclosing tags. Use
  63296. # the <tt>text!</tt> method to accomplish this.
  63297. #
  63298. # Example:
  63299. #
  63300. # xm.div { # <div>
  63301. # xm.text! "line"; xm.br # line<br/>
  63302. # xm.text! "another line"; xmbr # another line<br/>
  63303. # } # </div>
  63304. #
  63305. # * The special XML characters <, >, and & are converted to &lt;,
  63306. # &gt; and &amp; automatically. Use the <tt><<</tt> operation to
  63307. # insert text without modification.
  63308. #
  63309. # * Sometimes tags use special characters not allowed in ruby
  63310. # identifiers. Use the <tt>tag!</tt> method to handle these
  63311. # cases.
  63312. #
  63313. # Example:
  63314. #
  63315. # xml.tag!("SOAP:Envelope") { ... }
  63316. #
  63317. # will produce ...
  63318. #
  63319. # <SOAP:Envelope> ... </SOAP:Envelope>"
  63320. #
  63321. # <tt>tag!</tt> will also take text and attribute arguments (after
  63322. # the tag name) like normal markup methods. (But see the next
  63323. # bullet item for a better way to handle XML namespaces).
  63324. #
  63325. # * Direct support for XML namespaces is now available. If the
  63326. # first argument to a tag call is a symbol, it will be joined to
  63327. # the tag to produce a namespace:tag combination. It is easier to
  63328. # show this than describe it.
  63329. #
  63330. # xml.SOAP :Envelope do ... end
  63331. #
  63332. # Just put a space before the colon in a namespace to produce the
  63333. # right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
  63334. # "<tt>xml.SOAP :Envelope</tt>")
  63335. #
  63336. # * XmlMarkup builds the markup in any object (called a _target_)
  63337. # that accepts the <tt><<</tt> method. If no target is given,
  63338. # then XmlMarkup defaults to a string target.
  63339. #
  63340. # Examples:
  63341. #
  63342. # xm = Builder::XmlMarkup.new
  63343. # result = xm.title("yada")
  63344. # # result is a string containing the markup.
  63345. #
  63346. # buffer = ""
  63347. # xm = Builder::XmlMarkup.new(buffer)
  63348. # # The markup is appended to buffer (using <<)
  63349. #
  63350. # xm = Builder::XmlMarkup.new(STDOUT)
  63351. # # The markup is written to STDOUT (using <<)
  63352. #
  63353. # xm = Builder::XmlMarkup.new
  63354. # x2 = Builder::XmlMarkup.new(:target=>xm)
  63355. # # Markup written to +x2+ will be send to +xm+.
  63356. #
  63357. # * Indentation is enabled by providing the number of spaces to
  63358. # indent for each level as a second argument to XmlBuilder.new.
  63359. # Initial indentation may be specified using a third parameter.
  63360. #
  63361. # Example:
  63362. #
  63363. # xm = Builder.new(:ident=>2)
  63364. # # xm will produce nicely formatted and indented XML.
  63365. #
  63366. # xm = Builder.new(:indent=>2, :margin=>4)
  63367. # # xm will produce nicely formatted and indented XML with 2
  63368. # # spaces per indent and an over all indentation level of 4.
  63369. #
  63370. # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
  63371. # builder.name { |b| b.first("Jim"); b.last("Weirich) }
  63372. # # prints:
  63373. # # <name>
  63374. # # <first>Jim</first>
  63375. # # <last>Weirich</last>
  63376. # # </name>
  63377. #
  63378. # * The instance_eval implementation which forces self to refer to
  63379. # the message receiver as self is now obsolete. We now use normal
  63380. # block calls to execute the markup block. This means that all
  63381. # markup methods must now be explicitly send to the xml builder.
  63382. # For instance, instead of
  63383. #
  63384. # xml.div { strong("text") }
  63385. #
  63386. # you need to write:
  63387. #
  63388. # xml.div { xml.strong("text") }
  63389. #
  63390. # Although more verbose, the subtle change in semantics within the
  63391. # block was found to be prone to error. To make this change a
  63392. # little less cumbersome, the markup block now gets the markup
  63393. # object sent as an argument, allowing you to use a shorter alias
  63394. # within the block.
  63395. #
  63396. # For example:
  63397. #
  63398. # xml_builder = Builder::XmlMarkup.new
  63399. # xml_builder.div { |xml|
  63400. # xml.stong("text")
  63401. # }
  63402. #
  63403. class XmlMarkup < XmlBase
  63404. # Create an XML markup builder. Parameters are specified by an
  63405. # option hash.
  63406. #
  63407. # :target=><em>target_object</em>::
  63408. # Object receiving the markup. +out+ must respond to the
  63409. # <tt><<</tt> operator. The default is a plain string target.
  63410. # :indent=><em>indentation</em>::
  63411. # Number of spaces used for indentation. The default is no
  63412. # indentation and no line breaks.
  63413. # :margin=><em>initial_indentation_level</em>::
  63414. # Amount of initial indentation (specified in levels, not
  63415. # spaces).
  63416. #
  63417. def initialize(options={})
  63418. indent = options[:indent] || 0
  63419. margin = options[:margin] || 0
  63420. super(indent, margin)
  63421. @target = options[:target] || ""
  63422. end
  63423. # Return the target of the builder.
  63424. def target!
  63425. @target
  63426. end
  63427. def comment!(comment_text)
  63428. _ensure_no_block block_given?
  63429. _special("<!-- ", " -->", comment_text, nil)
  63430. end
  63431. # Insert an XML declaration into the XML markup.
  63432. #
  63433. # For example:
  63434. #
  63435. # xml.declare! :ELEMENT, :blah, "yada"
  63436. # # => <!ELEMENT blah "yada">
  63437. def declare!(inst, *args, &block)
  63438. _indent
  63439. @target << "<!#{inst}"
  63440. args.each do |arg|
  63441. case arg
  63442. when String
  63443. @target << %{ "#{arg}"}
  63444. when Symbol
  63445. @target << " #{arg}"
  63446. end
  63447. end
  63448. if block_given?
  63449. @target << " ["
  63450. _newline
  63451. _nested_structures(block)
  63452. @target << "]"
  63453. end
  63454. @target << ">"
  63455. _newline
  63456. end
  63457. # Insert a processing instruction into the XML markup. E.g.
  63458. #
  63459. # For example:
  63460. #
  63461. # xml.instruct!
  63462. # #=> <?xml version="1.0" encoding="UTF-8"?>
  63463. # xml.instruct! :aaa, :bbb=>"ccc"
  63464. # #=> <?aaa bbb="ccc"?>
  63465. #
  63466. def instruct!(directive_tag=:xml, attrs={})
  63467. _ensure_no_block block_given?
  63468. if directive_tag == :xml
  63469. a = { :version=>"1.0", :encoding=>"UTF-8" }
  63470. attrs = a.merge attrs
  63471. end
  63472. _special(
  63473. "<?#{directive_tag}",
  63474. "?>",
  63475. nil,
  63476. attrs,
  63477. [:version, :encoding, :standalone])
  63478. end
  63479. # Surrounds the given text with a CDATA tag
  63480. #
  63481. # For example:
  63482. #
  63483. # xml.cdata! "blah blah blah"
  63484. # # => <![CDATA[blah blah blah]]>
  63485. def cdata!(text)
  63486. _ensure_no_block block_given?
  63487. _special("<![CDATA[", "]]>", text, nil)
  63488. end
  63489. private
  63490. # NOTE: All private methods of a builder object are prefixed when
  63491. # a "_" character to avoid possible conflict with XML tag names.
  63492. # Insert text directly in to the builder's target.
  63493. def _text(text)
  63494. @target << text
  63495. end
  63496. # Insert special instruction.
  63497. def _special(open, close, data=nil, attrs=nil, order=[])
  63498. _indent
  63499. @target << open
  63500. @target << data if data
  63501. _insert_attributes(attrs, order) if attrs
  63502. @target << close
  63503. _newline
  63504. end
  63505. # Start an XML tag. If <tt>end_too</tt> is true, then the start
  63506. # tag is also the end tag (e.g. <br/>
  63507. def _start_tag(sym, attrs, end_too=false)
  63508. @target << "<#{sym}"
  63509. _insert_attributes(attrs)
  63510. @target << "/" if end_too
  63511. @target << ">"
  63512. end
  63513. # Insert an ending tag.
  63514. def _end_tag(sym)
  63515. @target << "</#{sym}>"
  63516. end
  63517. # Insert the attributes (given in the hash).
  63518. def _insert_attributes(attrs, order=[])
  63519. return if attrs.nil?
  63520. order.each do |k|
  63521. v = attrs[k]
  63522. @target << %{ #{k}="#{v}"} if v
  63523. end
  63524. attrs.each do |k, v|
  63525. @target << %{ #{k}="#{v}"} unless order.member?(k)
  63526. end
  63527. end
  63528. def _ensure_no_block(got_block)
  63529. if got_block
  63530. fail IllegalBlockError,
  63531. "Blocks are not allowed on XML instructions"
  63532. end
  63533. end
  63534. end
  63535. end
  63536. #!/usr/bin/env ruby
  63537. #--
  63538. # Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
  63539. # All rights reserved.
  63540. # Permission is granted for use, copying, modification, distribution,
  63541. # and distribution of modified versions of this work as long as the
  63542. # above copyright notice is included.
  63543. #++
  63544. require 'builder/xmlmarkup'
  63545. require 'builder/xmlevents'
  63546. module ActiveSupport
  63547. module VERSION #:nodoc:
  63548. MAJOR = 1
  63549. MINOR = 3
  63550. TINY = 1
  63551. STRING = [MAJOR, MINOR, TINY].join('.')
  63552. end
  63553. end
  63554. # Extensions to nil which allow for more helpful error messages for
  63555. # people who are new to rails.
  63556. #
  63557. # The aim is to ensure that when users pass nil to methods where that isn't
  63558. # appropriate, instead of NoMethodError and the name of some method used
  63559. # by the framework users will see a message explaining what type of object
  63560. # was expected.
  63561. class NilClass
  63562. WHINERS = [ ::ActiveRecord::Base, ::Array ]
  63563. @@method_class_map = Hash.new
  63564. WHINERS.each do |klass|
  63565. methods = klass.public_instance_methods - public_instance_methods
  63566. methods.each do |method|
  63567. @@method_class_map[method.to_sym] = klass
  63568. end
  63569. end
  63570. def id
  63571. raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller
  63572. end
  63573. private
  63574. def method_missing(method, *args, &block)
  63575. raise_nil_warning_for @@method_class_map[method], method, caller
  63576. end
  63577. def raise_nil_warning_for(klass = nil, selector = nil, with_caller = nil)
  63578. message = "You have a nil object when you didn't expect it!"
  63579. message << "\nYou might have expected an instance of #{klass}." if klass
  63580. message << "\nThe error occured while evaluating nil.#{selector}" if selector
  63581. raise NoMethodError, message, with_caller || caller
  63582. end
  63583. end
  63584. #--
  63585. # Copyright (c) 2005 David Heinemeier Hansson
  63586. #
  63587. # Permission is hereby granted, free of charge, to any person obtaining
  63588. # a copy of this software and associated documentation files (the
  63589. # "Software"), to deal in the Software without restriction, including
  63590. # without limitation the rights to use, copy, modify, merge, publish,
  63591. # distribute, sublicense, and/or sell copies of the Software, and to
  63592. # permit persons to whom the Software is furnished to do so, subject to
  63593. # the following conditions:
  63594. #
  63595. # The above copyright notice and this permission notice shall be
  63596. # included in all copies or substantial portions of the Software.
  63597. #
  63598. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  63599. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  63600. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  63601. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  63602. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  63603. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  63604. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  63605. #++
  63606. $:.unshift(File.dirname(__FILE__))
  63607. $:.unshift(File.dirname(__FILE__) + "/active_support/vendor")
  63608. require 'builder'
  63609. require 'active_support/inflector'
  63610. require 'active_support/core_ext'
  63611. require 'active_support/clean_logger'
  63612. require 'active_support/dependencies'
  63613. require 'active_support/reloadable'
  63614. require 'active_support/ordered_options'
  63615. require 'active_support/option_merger'
  63616. require 'active_support/values/time_zone'
  63617. require 'active_support/json'