提交 e2fd64fe 编写于 作者: A Aaron Patterson

Merge branch 'master' into preload

* master: (62 commits)
  Getting Started Guide: Hello Rails! -> Hello, Rails! and wrap code tag
  Add CHANGELOG entry for #12344
  Add regression test to #12343
  Fix typo in number_to_human docs: you -> your
  [Documentation] Add a missing validation to I18n docs
  Use the given name in html_options for the hidden field in collection_check_boxes
  assign_attributes should return if argument is blank.
  No need the else clause
  Use join to concat the both side of the AST
  Add a CHANGELOG entry about Web Console inclusion
  added column type to example in section 2.3
  Include web-console in new projects Gemfile
  ActiveRecord::ConnectionAdapters::Column.string_to_time method respects string with timezone. Closes #12278.
  add test_scoped_root_as_name
  Getting Started Guide: update RubyGems Guides link [ci skip]
  Deprecate unused quoted_locking_column method.
  Update references to wycats/thor to erikhuda/thor.
  bcrypt-ruby v3.1.2 supports Ruby 2.0 on Windows
  Fix the model name in the association basics guides
  We shouldn't override PostgreSQLAdapter's superclass inheritance while monkeypatching
  ...
......@@ -4,7 +4,7 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
- jruby-19mode
- jruby-head
- rbx-19mode
env:
- "GEM=railties"
......@@ -15,7 +15,7 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
- rvm: jruby-19mode
- rvm: jruby-head
- rvm: rbx-19mode
notifications:
email: false
......
......@@ -8,7 +8,7 @@ gemspec
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.1.0'
gem 'bcrypt-ruby', '~> 3.1.2'
gem 'jquery-rails', '~> 2.2.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
......
* Fix regex used to detect URI schemes in `redirect_to` to be consistent with
RFC 3986.
*Derek Prior*
* Fix incorrect `assert_redirected_to` failure message for protocol-relative
URLs.
*Derek Prior*
* Fix an issue where router can't recognize downcased url encoding path.
Fixes #12269
......
......@@ -71,6 +71,26 @@ def redirect_to(options = {}, response_status = {}) #:doc:
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
end
def _compute_redirect_to_location(options) #:nodoc:
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
# See http://tools.ietf.org/html/rfc3986#section-3.1
# The protocol relative scheme starts with a double slash "//".
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
options
when String
request.protocol + request.host_with_port + options
when :back
request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location options.call
else
url_for(options)
end.delete("\0\r\n")
end
private
def _extract_redirect_to_status(options, response_status)
if options.is_a?(Hash) && options.key?(:status)
......@@ -81,24 +101,5 @@ def _extract_redirect_to_status(options, response_status)
302
end
end
def _compute_redirect_to_location(options)
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
# The protocol relative scheme starts with a double slash "//"
when %r{\A(\w[\w+.-]*:|//).*}
options
when String
request.protocol + request.host_with_port + options
when :back
request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location options.call
else
url_for(options)
end.delete("\0\r\n")
end
end
end
......@@ -124,6 +124,9 @@ def initialize(env)
@loaded = true
end
# no-op
def destroy; end
def exists?
true
end
......
......@@ -7,11 +7,13 @@ class Utils # :nodoc:
# Normalizes URI path.
#
# Strips off trailing slash and ensures there is a leading slash.
# Also converts downcase url encoded string to uppercase.
#
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
# normalize_path("foo") # => "/foo"
# normalize_path("") # => "/"
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/')
......@@ -36,7 +38,7 @@ module UriEscape # :nodoc:
UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
end
Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
Parser = URI::Parser.new
def self.escape_path(path)
Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
......
......@@ -84,44 +84,43 @@ def visit_GROUP(node)
# Used for formatting urls (url_for)
class Formatter < Visitor # :nodoc:
attr_reader :options, :consumed
attr_reader :options
def initialize(options)
@options = options
@consumed = {}
end
private
def visit_GROUP(node)
if consumed == options
nil
else
route = visit(node.left)
route.include?("\0") ? nil : route
def visit(node, optional = false)
case node.type
when :LITERAL, :SLASH, :DOT
node.left
when :STAR
visit(node.left)
when :GROUP
visit(node.left, true)
when :CAT
visit_CAT(node, optional)
when :SYMBOL
visit_SYMBOL(node)
end
end
def terminal(node)
node.left
end
def binary(node)
[visit(node.left), visit(node.right)].join
end
def visit_CAT(node, optional)
left = visit(node.left, optional)
right = visit(node.right, optional)
def nary(node)
node.children.map { |c| visit(c) }.join
if optional && !(right && left)
""
else
[left, right].join
end
end
def visit_SYMBOL(node)
key = node.to_sym
if value = options[key]
consumed[key] = value
if value = options[node.to_sym]
Router::Utils.escape_path(value)
else
"\0"
end
end
end
......
......@@ -74,6 +74,19 @@ module PolymorphicRoutes
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
# Also includes all the options from <tt>url_for</tt>. These include such
# things as <tt>:anchor</tt> or <tt>:trailing_slash</tt>. Example usage
# is given below:
#
# polymorphic_url([blog, post], anchor: 'my_anchor')
# # => "http://example.com/blogs/1/posts/1#my_anchor"
# polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app")
# # => "http://example.com/my_app/blogs/1/posts/1#my_anchor"
#
# For all of these options, see the documentation for <tt>url_for</tt>.
#
# ==== Functionality
#
# # an Article record
# polymorphic_url(record) # same as article_url(record)
#
......
......@@ -67,21 +67,11 @@ def parameterize(value)
end
def normalize_argument_to_redirection(fragment)
normalized = case fragment
when Regexp
fragment
when %r{^\w[A-Za-z\d+.-]*:.*}
fragment
when String
@request.protocol + @request.host_with_port + fragment
when :back
raise RedirectBackError unless refer = @request.headers["Referer"]
refer
else
@controller.url_for(fragment)
end
normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
if Regexp === fragment
fragment
else
@controller._compute_redirect_to_location(fragment)
end
end
end
end
......
......@@ -39,6 +39,8 @@ def redirect_to_named_route() redirect_to route_one_url end
def redirect_external() redirect_to "http://www.rubyonrails.org"; end
def redirect_external_protocol_relative() redirect_to "//www.rubyonrails.org"; end
def response404() head '404 AWOL' end
def response500() head '500 Sorry' end
......@@ -258,6 +260,19 @@ def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in
end
end
def test_assert_redirect_failure_message_with_protocol_relative_url
begin
process :redirect_external_protocol_relative
assert_redirected_to "/foo"
rescue ActiveSupport::TestCase::Assertion => ex
assert_no_match(
/#{request.protocol}#{request.host}\/\/www.rubyonrails.org/,
ex.message,
'protocol relative url was incorrectly normalized'
)
end
end
def test_template_objects_exist
process :assign_this
assert !@controller.instance_variable_defined?(:"@hi")
......@@ -309,6 +324,9 @@ def test_redirection_location
process :redirect_external
assert_equal 'http://www.rubyonrails.org', @response.redirect_url
process :redirect_external_protocol_relative
assert_equal '//www.rubyonrails.org', @response.redirect_url
end
def test_no_redirect_url
......
......@@ -78,6 +78,11 @@ def encrypted
cookies.encrypted[:foo] = 'bar'
render :nothing => true
end
def try_to_reset_session
reset_session
render :nothing => true
end
end
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
......@@ -320,6 +325,11 @@ def setup
post :encrypted
assert_response :ok
end
test 'should allow reset_session' do
post :try_to_reset_session
assert_response :ok
end
end
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
......
......@@ -1102,6 +1102,19 @@ def test_scoped_root
assert_equal 'projects#index', @response.body
end
def test_scoped_root_as_name
draw do
scope '(:locale)', :locale => /en|pl/ do
root :to => 'projects#index', :as => 'projects'
end
end
assert_equal '/en', projects_path(:locale => 'en')
assert_equal '/', projects_path
get '/en'
assert_equal 'projects#index', @response.body
end
def test_scope_with_format_option
draw do
get "direct/index", as: :no_format_direct, format: false
......
* Fix `collection_check_boxes` generated hidden input to use the name attribute provided
in the options hash.
*Angel N. Sciortino*
* Fix some edge cases for AV `select` helper with `:selected` option
*Bogdan Gusiev*
* Ability to pass block to `select` helper
<%= select(report, "campaign_ids") do %>
<% available_campaigns.each do |c| -%>
<%= content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json }) %>
<% end -%>
<% end -%>
*Bogdan Gusiev*
* Handle `:namespace` form option in collection labels
*Vasiliy Ermolovich*
* Fix `form_for` when both `namespace` and `as` options are present
`as` option no longer overwrites `namespace` option when generating
......@@ -13,6 +36,12 @@
*Josh Lauer*, *Justin Ridgewell*
* Fixed a bug where the lookup details were not being taken into account
when caching the digest of a template - changes to the details now
cause a different cache key to be used.
*Daniel Schierbeck*
* Added an `extname` hash option for `javascript_include_tag` method.
Before:
......
......@@ -10,7 +10,10 @@ class Digestor
class << self
def digest(name, format, finder, options = {})
cache_key = ([name, format] + Array.wrap(options[:dependencies])).join('.')
details_key = finder.details_key.hash
dependencies = Array.wrap(options[:dependencies])
cache_key = ([name, details_key, format] + dependencies).join('.')
# this is a correctly done double-checked locking idiom
# (ThreadSafe::Cache's lookups have volatile semantics)
@@cache[cache_key] || @@digest_monitor.synchronize do
......
......@@ -128,6 +128,15 @@ module FormOptionsHelper
# or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
#
# A block can be passed to +select+ to customize how the options tags will be rendered. This
# is useful when the options tag has complex attributes.
#
# select(report, "campaign_ids") do
# available_campaigns.each do |c|
# content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json })
# end
# end
#
# ==== Gotcha
#
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
......@@ -152,8 +161,8 @@ module FormOptionsHelper
# In case if you don't want the helper to generate this hidden field you can specify
# <tt>include_hidden: false</tt> option.
#
def select(object, method, choices, options = {}, html_options = {})
Tags::Select.new(object, method, self, choices, options, html_options).render
def select(object, method, choices = nil, options = {}, html_options = {}, &block)
Tags::Select.new(object, method, self, choices, options, html_options, &block).render
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
......@@ -766,8 +775,8 @@ class FormBuilder
# <% end %>
#
# Please refer to the documentation of the base helper for details.
def select(method, choices, options = {}, html_options = {})
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
def select(method, choices = nil, options = {}, html_options = {}, &block)
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
......
......@@ -119,7 +119,8 @@ def select_content_tag(option_tags, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
value = options.fetch(:selected) { value(object) }
select = content_tag("select", add_options(option_tags, options, value), html_options)
if html_options["multiple"] && options.fetch(:include_hidden, true)
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
......
......@@ -27,7 +27,8 @@ def render(&block)
# Append a hidden field to make sure something will be sent back to the
# server if all check boxes are unchecked.
hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil)
hidden_name = @html_options[:name] || "#{tag_name}[]"
hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil)
rendered_collection + hidden
end
......
......@@ -18,7 +18,8 @@ def initialize(template_object, object_name, method_name, object,
end
def label(label_html_options={}, &block)
@template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
html_options = label_html_options.merge(@input_html_options)
@template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
end
end
......
......@@ -30,6 +30,7 @@ def render(&block)
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options.delete("namespace")
options.delete("multiple")
options["for"] = name_and_id["id"] unless options.key?("for")
if block_given?
......
......@@ -3,8 +3,9 @@ module Helpers
module Tags # :nodoc:
class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
@choices = choices
@choices = block_given? ? template_object.capture { yield } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
@html_options = html_options
super(object_name, method_name, template_object, options)
......
......@@ -92,8 +92,9 @@ def _back_url # :nodoc:
# ==== Data attributes
#
# * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript
# driver to prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
# driver to prompt with the question specified (in this case, the
# resulting text would be <tt>question?</tt>. If the user accepts, the
# link is processed normally, otherwise no action is taken.
# * <tt>:disable_with</tt> - Value of this parameter will be
# used as the value for a disabled version of the submit
# button when the form is submitted. This feature is provided
......
module Fun
module GamesHelper
def stratego() "Iz guuut!" end
end
end
\ No newline at end of file
module Fun
module PdfHelper
def foobar() 'baz' end
end
end
module JustMeHelper
def me() "mine!" end
end
\ No newline at end of file
module MeTooHelper
def me() "me too!" end
end
\ No newline at end of file
......@@ -15,6 +15,16 @@ def initialize(template_path)
class FixtureFinder
FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
attr_reader :details
def initialize
@details = {}
end
def details_key
details.hash
end
def find(logical_name, keys, partial, options)
FixtureTemplate.new("digestor/#{partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name}.#{options[:formats].first}.erb")
end
......@@ -140,6 +150,20 @@ def test_collection_derived_from_record_dependency
end
end
def test_details_are_included_in_cache_key
# Cache the template digest.
old_digest = digest("events/_event")
# Change the template; the cached digest remains unchanged.
change_template("events/_event")
# The details are changed, so a new cache key is generated.
finder.details[:foo] = "bar"
# The cache is busted.
assert_not_equal old_digest, digest("events/_event")
end
def test_extra_whitespace_in_render_partial
assert_digest_difference("messages/edit") do
change_template("messages/_form")
......@@ -220,7 +244,11 @@ def assert_digest_difference(template_name, persistent = false)
end
def digest(template_name, options={})
ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options)
ActionView::Digestor.digest(template_name, :html, finder, options)
end
def finder
@finder ||= FixtureFinder.new
end
def change_template(template_name)
......
......@@ -179,6 +179,13 @@ def with_collection_check_boxes(*args, &block)
assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1
end
test 'collection check boxes generates a hidden field using the given :name in :html_options' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, {name: "user[other_category_ids][]"}
assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1
end
test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_check_boxes :user, :category_ids, collection, :id, :name
......
......@@ -1282,6 +1282,24 @@ def post.id; 1; end
assert_dom_equal expected, output_buffer
end
def test_form_with_namespace_and_with_collection_radio_buttons
post = Post.new
def post.active; false; end
form_for(post, namespace: 'foo') do |f|
concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
"<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" +
"<label for='foo_post_active_true'>true</label>" +
"<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" +
"<label for='foo_post_active_false'>false</label>"
end
assert_dom_equal expected, output_buffer
end
def test_form_for_with_collection_check_boxes
post = Post.new
def post.tag_ids; [1, 3]; end
......@@ -1361,6 +1379,24 @@ def post.id; 1; end
assert_dom_equal expected, output_buffer
end
def test_form_with_namespace_and_with_collection_check_boxes
post = Post.new
def post.tag_ids; [1]; end
collection = [[1, "Tag 1"]]
form_for(post, namespace: 'foo') do |f|
concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
"<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"<label for='foo_post_tag_ids_1'>Tag 1</label>" +
"<input name='post[tag_ids][]' type='hidden' value='' />"
end
assert_dom_equal expected, output_buffer
end
def test_form_for_with_file_field_generate_multipart
Post.send :attr_accessor, :file
......
......@@ -556,6 +556,21 @@ def test_select_under_fields_for_with_string_and_given_prompt
)
end
def test_select_under_fields_for_with_block
@post = Post.new
output_buffer = fields_for :post, @post do |f|
concat(f.select(:category) do
concat content_tag(:option, "hello world")
end)
end
assert_dom_equal(
"<select id=\"post_category\" name=\"post[category]\"><option>hello world</option></select>",
output_buffer
)
end
def test_select_with_multiple_to_add_hidden_input
output_buffer = select(:post, :category, "", {}, :multiple => true)
assert_dom_equal(
......@@ -783,6 +798,22 @@ def test_select_with_disabled_value
)
end
def test_select_not_existing_method_with_selected_value
@post = Post.new
assert_dom_equal(
"<select id=\"post_locale\" name=\"post[locale]\"><option value=\"en\">en</option>\n<option value=\"ru\" selected=\"selected\">ru</option></select>",
select("post", "locale", %w( en ru ), :selected => 'ru')
)
end
def test_select_with_prompt_and_selected_value
@post = Post.new
assert_dom_equal(
"<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option selected=\"selected\" value=\"two\">two</option></select>",
select("post", "category", %w( one two ), :selected => 'two', :prompt => true)
)
end
def test_select_with_disabled_array
@post = Post.new
@post.category = "<mus>"
......
* Added new API methods `reset_changes` and `changed_applied` to `ActiveModel::Dirty`
that control changes state. Previsously you needed to update internal
instance variables, but now API methods are available.
*Bogdan Gusiev*
* Fix has_secure_password. `password_confirmation` validations are triggered
even if no `password_confirmation` is set.
......
......@@ -14,13 +14,9 @@ module ActiveModel
# track.
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
# attribute.
#
# If you wish to also track previous changes on save or update, you need to
# add:
#
# @previously_changed = changes
#
# inside of your save or update method.
# * Call <tt>changes_applied</tt> after the changes are persisted.
# * Call <tt>reset_changes</tt> when you want to reset the changes
# information.
#
# A minimal implementation could be:
#
......@@ -39,8 +35,12 @@ module ActiveModel
# end
#
# def save
# @previously_changed = changes
# @changed_attributes.clear
# # do persistence work
# changes_applied
# end
#
# def reload!
# reset_changes
# end
# end
#
......@@ -65,6 +65,12 @@ module ActiveModel
# person.changed? # => false
# person.name_changed? # => false
#
# Reset the changes:
#
# person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
# person.reload!
# person.previous_changes # => {}
#
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
......@@ -129,7 +135,7 @@ def changes
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
@previously_changed
@previously_changed ||= {}
end
# Returns a hash of the attributes with unsaved changes indicating their original
......@@ -154,6 +160,18 @@ def attribute_was(attr)
private
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied
@previously_changed = changes
@changed_attributes = {}
end
# Removes all dirty data: current changes and previous changes
def reset_changes
@previously_changed = {}
@changed_attributes = {}
end
# Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
......
......@@ -20,9 +20,9 @@ module ClassMethods
# value to the password_confirmation attribute and the validation
# will not be triggered.
#
# You need to add bcrypt-ruby (~> 3.1.0) to Gemfile to use #has_secure_password:
# You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password:
#
# gem 'bcrypt-ruby', '~> 3.1.0'
# gem 'bcrypt-ruby', '~> 3.1.2'
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
......@@ -46,7 +46,7 @@ def has_secure_password(options = {})
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
begin
gem 'bcrypt-ruby', '~> 3.1.0'
gem 'bcrypt-ruby', '~> 3.1.2'
require 'bcrypt'
rescue LoadError
$stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
......
......@@ -29,8 +29,7 @@ def color=(val)
end
def save
@previously_changed = changes
@changed_attributes.clear
changes_applied
end
end
......
* `ActiveRecord::ConnectionAdapters.string_to_time` respects
string with timezone (e.g. Wed, 04 Sep 2013 20:30:00 JST).
Fixes: #12278
*kennyj*
* Calling `update_attributes` will now throw an `ArgumentError` whenever it
gets a `nil` argument. More specifically, it will throw an error if the
argument that it gets passed does not respond to to `stringify_keys`.
Example:
@my_comment.update_attributes(nil) # => raises ArgumentError
*John Wang*
* Deprecate `quoted_locking_column` method, which isn't used anywhere.
*kennyj*
* Migration dump UUID default functions to schema.rb.
Fixes #10751.
*kennyj*
* Fixed a bug in `ActiveRecord::Associations::CollectionAssociation#find_by_scan`
when using `has_many` association with `:inverse_of` option and UUID primary key.
Fixes #10450.
*kennyj*
* ActiveRecord::Base#<=> has been removed. Primary keys may not be in order,
or even be numbers, so sorting by id doesn't make sense. Please use `sort_by`
and specify the attribute you wish to sort with. For example, change:
......@@ -8,6 +42,8 @@
Post.all.to_a.sort_by(&:id)
*Aaron Patterson*
* Fix: joins association, with defined in the scope block constraints by using several
where constraints and at least of them is not `Arel::Nodes::Equality`,
generates invalid SQL expression.
......
......@@ -554,14 +554,14 @@ def include_in_memory?(record)
# specified, then #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
if ids.size == 1
id = ids.first
record = load_target.detect { |r| id == r.id }
record = load_target.detect { |r| id == r.id.to_s }
expects_array ? [ record ] : record
else
load_target.select { |r| ids.include?(r.id) }
load_target.select { |r| ids.include?(r.id.to_s) }
end
end
......
......@@ -12,6 +12,9 @@ module AttributeAssignment
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
return if new_attributes.blank?
attributes = new_attributes.stringify_keys
......
......@@ -19,8 +19,7 @@ module Dirty # :nodoc:
# Attempts to +save+ the record and clears changed attributes if successful.
def save(*)
if status = super
@previously_changed = changes
@changed_attributes.clear
changes_applied
end
status
end
......@@ -28,16 +27,14 @@ def save(*)
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
def save!(*)
super.tap do
@previously_changed = changes
@changed_attributes.clear
changes_applied
end
end
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
@previously_changed.clear
@changed_attributes.clear
reset_changes
end
end
......@@ -48,11 +45,11 @@ def write_attribute(attr, value)
# The attribute already has an unsaved change.
if attribute_changed?(attr)
old = @changed_attributes[attr]
@changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
old = changed_attributes[attr]
changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
else
old = clone_attribute_value(:read_attribute, attr)
@changed_attributes[attr] = old if _field_changed?(attr, old, value)
changed_attributes[attr] = old if _field_changed?(attr, old, value)
end
# Carry on.
......
......@@ -203,11 +203,19 @@ def new_date(year, mon, mday)
end
end
def new_time(year, mon, mday, hour, min, sec, microsec)
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
# Treat 0000-00-00 00:00:00 as nil.
return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
if offset
time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
return nil unless time
time -= offset
Base.default_timezone == :utc ? time : time.getlocal
else
Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
end
end
def fast_string_to_date(string)
......@@ -232,7 +240,7 @@ def fallback_string_to_time(string)
time_hash = Date._parse(string)
time_hash[:sec_fraction] = microseconds(time_hash)
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
end
......
......@@ -45,16 +45,18 @@ def postgresql_connection(config)
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
attr_accessor :array
attr_accessor :array, :default_function
# Instantiates a new PostgreSQL column definition in a table.
def initialize(name, default, oid_type, sql_type = nil, null = true)
@oid_type = oid_type
default_value = self.class.extract_value_from_default(default)
@default_function = default if !default_value && default && default =~ /.+\(.*\)/
if sql_type =~ /\[\]$/
@array = true
super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
super(name, default_value, sql_type[0..sql_type.length - 3], null)
else
@array = false
super(name, self.class.extract_value_from_default(default), sql_type, null)
super(name, default_value, sql_type, null)
end
end
......@@ -439,6 +441,7 @@ def adapter_name
def prepare_column_options(column, types)
spec = super
spec[:array] = 'true' if column.respond_to?(:array) && column.array
spec[:default] = "\"#{column.default_function}\"" if column.respond_to?(:default_function) && column.default_function
spec
end
......
......@@ -163,7 +163,7 @@ def relation #:nodoc:
# ==== Example:
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil)
def initialize(attributes = nil, options = {})
defaults = self.class.column_defaults.dup
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
......@@ -176,7 +176,9 @@ def initialize(attributes = nil)
ensure_proper_type
populate_with_current_scope_attributes
assign_attributes(attributes) if attributes
# +options+ argument is only needed to make protected_attributes gem easier to hook.
# Remove it when we drop support to this gem.
init_attributes(attributes, options) if attributes
yield self if block_given?
run_callbacks :initialize unless _initialize_callbacks.empty?
......@@ -411,8 +413,6 @@ def init_internals
@aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@previously_changed = {}
@changed_attributes = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
......@@ -429,8 +429,14 @@ def init_changed_attributes
# optimistic locking) won't get written unless they get marked as changed
self.class.columns.each do |c|
attr, orig_value = c.name, c.default
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
end
end
# This method is needed to make protected_attributes gem easier to hook.
# Remove it when we drop support to this gem.
def init_attributes(attributes, options)
assign_attributes(attributes)
end
end
end
......@@ -150,6 +150,7 @@ def locking_column
# Quote the column name used for optimistic locking.
def quoted_locking_column
ActiveSupport::Deprecation.warn "ActiveRecord::Base.quoted_locking_column is deprecated and will be removed in Rails 4.2 or later."
connection.quote_column_name(locking_column)
end
......
......@@ -434,7 +434,7 @@ def touch(name = nil)
changes[self.class.locking_column] = increment_lock if locking_enabled?
@changed_attributes.except!(*changes.keys)
changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
end
......
......@@ -17,9 +17,19 @@ class SchemaDumper #:nodoc:
cattr_accessor :ignore_tables
@@ignore_tables = []
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
new(connection).dump(stream)
stream
class << self
def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
new(connection, generate_options(config)).dump(stream)
stream
end
private
def generate_options(config)
{
table_name_prefix: config.table_name_prefix,
table_name_suffix: config.table_name_suffix
}
end
end
def dump(stream)
......@@ -32,10 +42,11 @@ def dump(stream)
private
def initialize(connection)
def initialize(connection, options = {})
@connection = connection
@types = @connection.native_database_types
@version = Migrator::current_version rescue nil
@options = options
end
def header(stream)
......@@ -201,7 +212,7 @@ def indexes(table, stream)
end
def remove_prefix_and_suffix(table)
table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
end
end
end
require 'cases/helper'
module ActiveRecord::ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
class InactivePGconn
def query(*args)
raise PGError
end
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
class InactivePGconn
def query(*args)
raise PGError
end
def status
PGconn::CONNECTION_BAD
def status
PGconn::CONNECTION_BAD
end
end
end
class StatementPoolTest < ActiveRecord::TestCase
def test_cache_is_per_pid
return skip('must support fork') unless Process.respond_to?(:fork)
class StatementPoolTest < ActiveRecord::TestCase
def test_cache_is_per_pid
return skip('must support fork') unless Process.respond_to?(:fork)
cache = StatementPool.new nil, 10
cache['foo'] = 'bar'
assert_equal 'bar', cache['foo']
cache = StatementPool.new nil, 10
cache['foo'] = 'bar'
assert_equal 'bar', cache['foo']
pid = fork {
lookup = cache['foo'];
exit!(!lookup)
}
pid = fork {
lookup = cache['foo'];
exit!(!lookup)
}
Process.waitpid pid
assert $?.success?, 'process should exit successfully'
end
Process.waitpid pid
assert $?.success?, 'process should exit successfully'
end
def test_dealloc_does_not_raise_on_inactive_connection
cache = StatementPool.new InactivePGconn.new, 10
cache['foo'] = 'bar'
assert_nothing_raised { cache.clear }
def test_dealloc_does_not_raise_on_inactive_connection
cache = StatementPool.new InactivePGconn.new, 10
cache['foo'] = 'bar'
assert_nothing_raised { cache.clear }
end
end
end
end
......
......@@ -61,6 +61,7 @@ def test_schema_dumper_for_uuid_primary_key
schema = StringIO.new
ActiveRecord::SchemaDumper.dump(@connection, schema)
assert_match(/\bcreate_table "pg_uuids", id: :uuid\b/, schema.string)
assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string)
end
end
......@@ -93,3 +94,43 @@ def test_id_allows_default_override_via_nil
assert_nil col_desc["default"]
end
end
class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
class UuidPost < ActiveRecord::Base
self.table_name = 'pg_uuid_posts'
has_many :uuid_comments, inverse_of: :uuid_post
end
class UuidComment < ActiveRecord::Base
self.table_name = 'pg_uuid_comments'
belongs_to :uuid_post
end
def setup
@connection = ActiveRecord::Base.connection
@connection.reconnect!
@connection.transaction do
@connection.create_table('pg_uuid_posts', id: :uuid) do |t|
t.string 'title'
end
@connection.create_table('pg_uuid_comments', id: :uuid) do |t|
t.uuid :uuid_post_id, default: 'uuid_generate_v4()'
t.string 'content'
end
end
end
def teardown
@connection.transaction do
@connection.execute 'drop table if exists pg_uuid_comments'
@connection.execute 'drop table if exists pg_uuid_posts'
end
end
def test_collection_association_with_uuid
post = UuidPost.create!
comment = post.uuid_comments.create!
assert post.uuid_comments.find(comment.id)
end
end
......@@ -92,7 +92,7 @@ def test_set_attributes
def test_set_attributes_without_hash
topic = Topic.new
assert_nothing_raised { topic.attributes = '' }
assert_raise(ArgumentError) { topic.attributes = '' }
end
def test_integers_as_nil
......
......@@ -110,6 +110,16 @@ def test_type_cast_duration_to_integer
assert_equal 1800, column.type_cast(30.minutes)
assert_equal 7200, column.type_cast(2.hours)
end
def test_string_to_time_with_timezone
old = ActiveRecord::Base.default_timezone
[:utc, :local].each do |zone|
ActiveRecord::Base.default_timezone = zone
assert_equal Time.utc(2013, 9, 4, 0, 0, 0), Column.string_to_time("Wed, 04 Sep 2013 03:00:00 EAT")
end
rescue
ActiveRecord::Base.default_timezone = old
end
end
end
end
......@@ -61,4 +61,9 @@ def test_regular_hash_should_still_be_used_for_mass_assignment
assert_equal 'Guille', person.first_name
assert_equal 'm', person.gender
end
def test_blank_attributes_should_not_raise
person = Person.new
assert_nil person.assign_attributes(ProtectedParams.new({}))
end
end
......@@ -272,6 +272,10 @@ def test_removing_has_and_belongs_to_many_associations_upon_destroy
assert p.treasures.empty?
assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
end
def test_quoted_locking_column_is_deprecated
assert_deprecated { ActiveRecord::Base.quoted_locking_column }
end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
......
......@@ -419,10 +419,6 @@ def test_update_attribute
assert !Topic.find(1).approved?
end
def test_update_attribute_does_not_choke_on_nil
assert Topic.find(1).update(nil)
end
def test_update_attribute_for_readonly_attribute
minivan = Minivan.find('m1')
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
......@@ -701,6 +697,17 @@ def test_update_attributes
assert_equal topic.title, Topic.find(1234).title
end
def test_update_attributes_parameters
topic = Topic.find(1)
assert_nothing_raised do
topic.update_attributes({})
end
assert_raises(ArgumentError) do
topic.update_attributes(nil)
end
end
def test_update!
Reply.validates_presence_of(:title)
reply = Reply.find(2)
......
......@@ -299,7 +299,7 @@ def test_schema_dump_includes_macaddr_shorthand_definition
def test_schema_dump_includes_uuid_shorthand_definition
output = standard_dump
if %r{create_table "poistgresql_uuids"} =~ output
if %r{create_table "postgresql_uuids"} =~ output
assert_match %r{t.uuid "guid"}, output
end
end
......
* Disable the ability to iterate over Range of AS::TimeWithZone
due to significant performance issues.
*Bogdan Gusiev*
* Allow attaching event subscribers to ActiveSupport::Notifications namespaces
before they're defined. Essentially, this means instead of this:
class JokeSubscriber < ActiveSupport::Subscriber
def sql(event)
puts "A rabbi and a priest walk into a bar..."
end
# This call needs to happen *after* defining the methods.
attach_to "active_record"
end
You can do this:
class JokeSubscriber < ActiveSupport::Subscriber
# This is much easier to read!
attach_to "active_record"
def sql(event)
puts "A rabbi and a priest walk into a bar..."
end
end
This should make it easier to read and understand these subscribers.
*Daniel Schierbeck*
* Add `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` methods.
Also added `midday`, `noon`, `at_midday`, `at_noon` and `at_middle_of_day` as aliases.
......
......@@ -28,12 +28,6 @@ module ActiveSupport
def initialize
@ucd = Unicode::UnicodeDatabase.new
default = Codepoint.new
default.combining_class = 0
default.uppercase_mapping = 0
default.lowercase_mapping = 0
@ucd.codepoints = Hash.new(default)
end
def parse_codepoints(line)
......
require 'active_support/core_ext/range/conversions'
require 'active_support/core_ext/range/include_range'
require 'active_support/core_ext/range/overlaps'
require 'active_support/core_ext/range/each'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/object/acts_like'
class Range #:nodoc:
def each_with_time_with_zone(&block)
ensure_iteration_allowed
each_without_time_with_zone(&block)
end
alias_method_chain :each, :time_with_zone
def step_with_time_with_zone(n = 1, &block)
ensure_iteration_allowed
step_without_time_with_zone(n, &block)
end
alias_method_chain :step, :time_with_zone
private
def ensure_iteration_allowed
if first.acts_like?(:time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
......@@ -56,11 +56,10 @@ def initialize(string)
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
result = @wrapped_string.__send__(method, *args, &block)
if method.to_s =~ /!$/
result = @wrapped_string.__send__(method, *args, &block)
self if result
else
result = @wrapped_string.__send__(method, *args, &block)
result.kind_of?(String) ? chars(result) : result
end
end
......
......@@ -287,6 +287,13 @@ def swapcase(string)
class Codepoint
attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
# Initializing Codepoint object with default values
def initialize
@combining_class = 0
@uppercase_mapping = 0
@lowercase_mapping = 0
end
def swapcase_mapping
uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
end
......
......@@ -460,7 +460,7 @@ def number_to_human_size(number, options = {})
# See <tt>number_to_human_size</tt> if you want to print a file
# size.
#
# You can also define you own unit-quantifier names if you want
# You can also define your own unit-quantifier names if you want
# to use other decimal units (eg.: 1500 becomes "1.5
# kilometers", 0.150 becomes "150 milliliters", etc). You may
# define a wide range of unit quantifiers, even fractional ones
......
......@@ -31,18 +31,41 @@ class << self
# Attach the subscriber to a namespace.
def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
@namespace = namespace
@subscriber = subscriber
@notifier = notifier
subscribers << subscriber
# Add event subscribers for all existing methods on the class.
subscriber.public_methods(false).each do |event|
next if %w{ start finish }.include?(event.to_s)
add_event_subscriber(event)
end
end
notifier.subscribe("#{event}.#{namespace}", subscriber)
# Adds event subscribers for all new methods added to the class.
def method_added(event)
# Only public methods are added as subscribers, and only if a notifier
# has been set up. This means that subscribers will only be set up for
# classes that call #attach_to.
if public_method_defined?(event) && notifier
add_event_subscriber(event)
end
end
def subscribers
@@subscribers ||= []
end
protected
attr_reader :subscriber, :notifier, :namespace
def add_event_subscriber(event)
return if %w{ start finish }.include?(event.to_s)
notifier.subscribe("#{event}.#{namespace}", subscriber)
end
end
def initialize
......
......@@ -90,4 +90,26 @@ def test_no_overlaps_on_time
time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
assert !time_range_1.overlaps?(time_range_2)
end
def test_each_on_time_with_zone
twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30))
assert_raises TypeError do
((twz - 1.hour)..twz).each {}
end
end
def test_step_on_time_with_zone
twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30))
assert_raises TypeError do
((twz - 1.hour)..twz).step(1) {}
end
end
def test_include_on_time_with_zone
twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30))
assert_raises TypeError do
((twz - 1.hour)..twz).include?(twz)
end
end
end
require 'abstract_unit'
require 'active_support/subscriber'
class TestSubscriber < ActiveSupport::Subscriber
attach_to :doodle
cattr_reader :event
def self.clear
@@event = nil
end
def open_party(event)
@@event = event
end
private
def private_party(event)
@@event = event
end
end
class SubscriberTest < ActiveSupport::TestCase
def setup
TestSubscriber.clear
end
def test_attaches_subscribers
ActiveSupport::Notifications.instrument("open_party.doodle")
assert_equal "open_party.doodle", TestSubscriber.event.name
end
def test_does_not_attach_private_methods
ActiveSupport::Notifications.instrument("private_party.doodle")
assert_nil TestSubscriber.event
end
end
......@@ -31,7 +31,7 @@ end
gem 'jbuilder', '~> 1.2'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.1.0'
# gem 'bcrypt-ruby', '~> 3.1.2'
# Use unicorn as the app server
# gem 'unicorn'
......
......@@ -73,8 +73,6 @@ You can see an example of how that works at [Rails Upgrade is now an Official Pl
Aside from Rails Upgrade tool, if you need more help, there are people on IRC and [rubyonrails-talk](http://groups.google.com/group/rubyonrails-talk) that are probably doing the same thing, possibly hitting the same issues. Be sure to blog your own experiences when upgrading so others can benefit from your knowledge!
More information - [The Path to Rails 3: Approaching the upgrade](http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade)
Creating a Rails 3.0 application
--------------------------------
......
......@@ -120,8 +120,8 @@ class Person
end
def save
@previously_changed = changes
# do save work...
changes_applied
end
end
```
......
......@@ -261,7 +261,10 @@ With `through: :sections` specified, Rails will now understand:
### The `has_one :through` Association
A `has_one :through` association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this:
A `has_one :through` association sets up a one-to-one connection with another model. This association indicates
that the declaring model can be matched with one instance of another model by proceeding _through_ a third model.
For example, if each supplier has one account, and each account is associated with one account history, then the
supplier model could look like this:
```ruby
class Supplier < ActiveRecord::Base
......
The Rails Command Line
======================
Rails comes with every command line tool you'll need to
After reading this guide, you will know:
* How to create a Rails application.
......
......@@ -759,4 +759,15 @@ Since the connection pooling is handled inside of Active Record by default, all
Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
If you try to use more connections than are available, Active Record will block
and wait for a connection from the pool. When it cannot get connection, a timeout
error similar to given below will be thrown.
```ruby
ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it:
```
If you get the above error, you might want to increase the size of connection
pool by incrementing the `pool` option in `database.yml`
NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
......@@ -307,7 +307,11 @@ create test/models/blorgh/comment_test.rb
create test/fixtures/blorgh/comments.yml
```
This generator call will generate just the necessary model files it needs, namespacing the files under a `blorgh` directory and creating a model class called `Blorgh::Comment`.
This generator call will generate just the necessary model files it needs, namespacing the files under a `blorgh` directory and creating a model class called `Blorgh::Comment`. Now run the migration to create our blorgh_comments table:
```bash
$ rake db:migrate
```
To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and add this line before the "Edit" link:
......@@ -950,7 +954,7 @@ INFO. Remember that in order to use languages like Sass or CoffeeScript, you sho
There are some situations where your engine's assets are not required by the host application. For example, say that you've created
an admin functionality that only exists for your engine. In this case, the host application doesn't need to require `admin.css`
or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorg/admin.css"` in it's stylesheets. In this situation, you should explicitly define these assets for precompilation.
or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorgh/admin.css"` in it's stylesheets. In this situation, you should explicitly define these assets for precompilation.
This tells sprockets to add your engine assets when `rake assets:precompile` is ran.
You can define assets for precompilation in `engine.rb`
......
......@@ -35,7 +35,7 @@ $ rails generate helper --help
Creating Your First Generator
-----------------------------
Since Rails 3.0, generators are built on top of [Thor](https://github.com/wycats/thor). Thor provides powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named `initializer.rb` inside `config/initializers`.
Since Rails 3.0, generators are built on top of [Thor](https://github.com/erikhuda/thor). Thor provides powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named `initializer.rb` inside `config/initializers`.
The first step is to create a file at `lib/generators/initializer_generator.rb` with the following content:
......@@ -47,7 +47,7 @@ class InitializerGenerator < Rails::Generators::Base
end
```
NOTE: `create_file` is a method provided by `Thor::Actions`. Documentation for `create_file` and other Thor methods can be found in [Thor's documentation](http://rdoc.info/github/wycats/thor/master/Thor/Actions.html)
NOTE: `create_file` is a method provided by `Thor::Actions`. Documentation for `create_file` and other Thor methods can be found in [Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html)
Our new generator is quite simple: it inherits from `Rails::Generators::Base` and has one method definition. When a generator is invoked, each public method in the generator is executed sequentially in the order that it is defined. Finally, we invoke the `create_file` method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API.
......@@ -422,7 +422,7 @@ Generator methods
The following are methods available for both generators and templates for Rails.
NOTE: Methods provided by Thor are not covered this guide and can be found in [Thor's documentation](http://rdoc.info/github/wycats/thor/master/Thor/Actions.html)
NOTE: Methods provided by Thor are not covered this guide and can be found in [Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html)
### `gem`
......
......@@ -22,8 +22,8 @@ with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer
* The [RubyGems](http://rubygems.org/) packaging system
* To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1)
* The [RubyGems](http://rubygems.org) packaging system
* To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org)
* A working installation of the [SQLite3 Database](http://www.sqlite.org)
Rails is a web application framework running on the Ruby programming language.
......@@ -262,7 +262,7 @@ of code:
### Setting the Application Home Page
Now that we have made the controller and view, we need to tell Rails when we
want Hello Rails! to show up. In our case, we want it to show up when we
want `Hello, Rails!` to show up. In our case, we want it to show up when we
navigate to the root URL of our site, <http://localhost:3000>. At the moment,
"Welcome Aboard" is occupying that spot.
......
......@@ -843,6 +843,7 @@ So, for example, instead of the default error message `"can not be blank"` you c
| numericality | :equal_to | :equal_to | count |
| numericality | :less_than | :less_than | count |
| numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
| numericality | :only_integer | :not_an_integer | - |
| numericality | :odd | :odd | - |
| numericality | :even | :even | - |
......
......@@ -301,6 +301,7 @@ braces. You can use the following modifiers:
* `precision` Defines the precision for the `decimal` fields
* `scale` Defines the scale for the `decimal` fields
* `polymorphic` Adds a `type` column for `belongs_to` associations
* `null` Allows or disallows `NULL` values in the column.
For instance, running
......@@ -313,7 +314,7 @@ will produce a migration that looks like this
```ruby
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :price, precision: 5, scale: 2
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true, index: true
end
end
......
......@@ -438,10 +438,12 @@ Now that we have used Rails scaffold generator for our `Post` resource, it has a
Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`.
```ruby
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:posts)
class PostsControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:posts)
end
end
```
......
* Include `web-console` into newly generated applications' Gemfile.
*Genadi Samokovarov*
* `rails server` will only extend the logger to output to STDOUT
in development environment.
*Richard Schneeman*
* Don't require passing path to app before options in `rails new`
and `rails plugin new`
......
......@@ -32,7 +32,8 @@ def parse!(args)
opt_parser.parse! args
options[:server] = args.shift
options[:log_stdout] = options[:daemonize].blank? && options[:environment] == "development"
options[:server] = args.shift
options
end
end
......@@ -74,7 +75,7 @@ def start
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
unless options[:daemonize]
if options[:log_stdout]
wrapped_app # touch the app so the logger is set up
console = ActiveSupport::Logger.new($stdout)
......
......@@ -335,7 +335,7 @@ def handle_invalid_command!(argument)
def handle_rails_rc!
unless argv.delete("--no-rc")
insert_railsrc(railsrc)
insert_railsrc_into_argv!(railsrc)
end
end
......@@ -347,7 +347,7 @@ def railsrc
end
end
def insert_railsrc(railsrc)
def insert_railsrc_into_argv!(railsrc)
if File.exist?(railsrc)
extra_args_string = File.read(railsrc)
extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
......
......@@ -10,13 +10,16 @@ source 'https://rubygems.org'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.2'
# Run `rails console` in the browser. Read more: https://github.com/rails/web-console
gem 'web-console', group: :development
group :doc do
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', require: false
end
# Use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.1.0'
# gem 'bcrypt-ruby', '~> 3.1.2'
# Use unicorn as the app server
# gem 'unicorn'
......
......@@ -55,6 +55,8 @@ production:
adapter: postgresql
encoding: unicode
database: <%= app_name %>_production
# For details on connection pooling, see rails configration guide
# http://guides.rubyonrails.org/configuring.html#database-pooling
pool: 5
username: <%= app_name %>
password:
......@@ -39,4 +39,14 @@ def test_environment_with_rack_env
assert_equal 'production', server.options[:environment]
end
end
def test_log_stdout
args = ["-e", "development"]
options = Rails::Server::Options.new.parse!(args)
assert_equal true, options[:log_stdout]
args = ["-e", "production"]
options = Rails::Server::Options.new.parse!(args)
assert_equal false, options[:log_stdout]
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册