From 44b752bea148b9df8f4d806e410e30fff26f680e Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 9 Jul 2010 16:39:34 -0400 Subject: expanding on :uniq option in has_many --- activerecord/lib/active_record/associations.rb | 2 +- railties/guides/source/association_basics.textile | 42 ++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 65daa8ffbe..11555b88e0 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -889,7 +889,7 @@ module ActiveRecord # Specifies type of the source association used by has_many :through queries where the source # association is a polymorphic +belongs_to+. # [:uniq] - # If true, duplicates will be omitted from the collection. Useful in conjunction with :through. + # If true, duplicates will be omitted from the collection. Works only in conjunction with :through. # [:readonly] # If true, all the associated objects are readonly through the association. # [:validate] diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index c69f2ae8c9..bc08874a30 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1371,7 +1371,47 @@ The +:through+ option specifies a join model through which to perform the query. h6(#has_many-uniq). +:uniq+ -Specify the +:uniq => true+ option to remove duplicates from the collection. This is most useful in conjunction with the +:through+ option. +Specify the +:uniq => true+ option to remove duplicates from the collection. It only works with +:through+ option. + + +class Person < ActiveRecord::Base + has_many :readers + has_many :posts, :through => :readers + + def self.lab + person = Person.create(:name => 'john') + p = Post.create(:name => 'a1') + person.posts << p + person.posts << p + person.reload + puts person.posts.inspect #=> [#, #] + puts Reader.all.inspect #=> [#, #] + end +end + + +In the above case +readers+ table has two records and +person.posts+ brings out both of these records even though these records are basically pointing to the same +post+ record. + +Now let's add +uniq => true+ option. + + +class Person + has_many :readers + has_many :posts, :through => :readers, :uniq => true + + def self.experiment + person = Person.create(:name => 'honda') + p = Post.create(:name => 'a1') + person.posts << p + person.posts << p + person.reload + puts person.posts.inspect #=> [#] + puts Reader.all.inspect #=> [#, #] + end +end + + +In the above case +readers+ table still has two records. However +person.posts+ will show only one +post+ record because collection will load only +unique+ records. h6(#has_many-validate). +:validate+ -- cgit v1.2.3 From 86d5c728fbbce6ba8ae73f8926084f5c0eee9a41 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 10 Jul 2010 00:50:59 +0200 Subject: revises recent commit related to :uniq => true --- activerecord/lib/active_record/associations.rb | 2 +- railties/guides/source/association_basics.textile | 50 ++++++++++------------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 11555b88e0..65daa8ffbe 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -889,7 +889,7 @@ module ActiveRecord # Specifies type of the source association used by has_many :through queries where the source # association is a polymorphic +belongs_to+. # [:uniq] - # If true, duplicates will be omitted from the collection. Works only in conjunction with :through. + # If true, duplicates will be omitted from the collection. Useful in conjunction with :through. # [:readonly] # If true, all the associated objects are readonly through the association. # [:validate] diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index bc08874a30..aec1b0732d 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1371,47 +1371,41 @@ The +:through+ option specifies a join model through which to perform the query. h6(#has_many-uniq). +:uniq+ -Specify the +:uniq => true+ option to remove duplicates from the collection. It only works with +:through+ option. +Set the +:uniq+ option to true to keep the collection free of duplicates. This is mostly useful together with the +:through+ option. class Person < ActiveRecord::Base - has_many :readers - has_many :posts, :through => :readers - - def self.lab - person = Person.create(:name => 'john') - p = Post.create(:name => 'a1') - person.posts << p - person.posts << p - person.reload - puts person.posts.inspect #=> [#, #] - puts Reader.all.inspect #=> [#, #] - end + has_many :readings + has_many :posts, :through => :readings end + +person = Person.create(:name => 'john') +post = Post.create(:name => 'a1') +person.posts << post +person.posts << post +person.posts.inspect # => [#, #] +Reading.all.inspect # => [#, #] -In the above case +readers+ table has two records and +person.posts+ brings out both of these records even though these records are basically pointing to the same +post+ record. +In the above case there are two readings and +person.posts+ brings out both of them even though these records are pointing to the same post. -Now let's add +uniq => true+ option. +Now let's set +:uniq+ to true: class Person - has_many :readers - has_many :posts, :through => :readers, :uniq => true - - def self.experiment - person = Person.create(:name => 'honda') - p = Post.create(:name => 'a1') - person.posts << p - person.posts << p - person.reload - puts person.posts.inspect #=> [#] - puts Reader.all.inspect #=> [#, #] - end + has_many :readings + has_many :posts, :through => :readings, :uniq => true end + +person = Person.create(:name => 'honda') +post = Post.create(:name => 'a1') +person.posts << post +person.posts << post +person.posts.inspect # => [#] +Reading.all.inspect # => [#, #] -In the above case +readers+ table still has two records. However +person.posts+ will show only one +post+ record because collection will load only +unique+ records. +In the above case there are still two readings. However +person.posts+ shows only one post because collection loads only unique records. h6(#has_many-validate). +:validate+ -- cgit v1.2.3 From c81e476d6d4cb88e7e29d6e18bb3e5acce92fb8f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 10 Jul 2010 00:56:17 +0200 Subject: missing article, only seen in github's colored diff by the beard of the prophet --- railties/guides/source/association_basics.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index aec1b0732d..b1ee4b8be4 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1405,7 +1405,7 @@ person.posts.inspect # => [#] Reading.all.inspect # => [#, #] -In the above case there are still two readings. However +person.posts+ shows only one post because collection loads only unique records. +In the above case there are still two readings. However +person.posts+ shows only one post because the collection loads only unique records. h6(#has_many-validate). +:validate+ -- cgit v1.2.3 From 51783c0298d3789d49682f2ea94039ff2c1f1633 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 10 Jul 2010 13:21:56 -0700 Subject: behavioral tests for sqlite adapter --- .../cases/adapters/sqlite/sqlite_adapter_test.rb | 81 ++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb index 2505372b7e..389bcdfa8e 100644 --- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb @@ -103,17 +103,90 @@ module ActiveRecord end end + def test_columns + columns = @ctx.columns('items').sort_by { |x| x.name } + assert_equal 2, columns.length + assert_equal %w{ id number }.sort, columns.map { |x| x.name } + assert_equal [nil, nil], columns.map { |x| x.default } + assert_equal [true, true], columns.map { |x| x.null } + end + + def test_columns_with_default + @ctx.execute <<-eosql + CREATE TABLE columns_with_default ( + id integer PRIMARY KEY AUTOINCREMENT, + number integer default 10 + ) + eosql + column = @ctx.columns('columns_with_default').find { |x| + x.name == 'number' + } + assert_equal 10, column.default + end + + def test_columns_with_not_null + @ctx.execute <<-eosql + CREATE TABLE columns_with_default ( + id integer PRIMARY KEY AUTOINCREMENT, + number integer not null + ) + eosql + column = @ctx.columns('columns_with_default').find { |x| + x.name == 'number' + } + assert !column.null, "column should not be null" + end + + def test_indexes_logs + intercept_logs_on @ctx + assert_difference('@ctx.logged.length') do + @ctx.indexes('items') + end + assert_match(/items/, @ctx.logged.last.first) + end + + def test_no_indexes + assert_equal [], @ctx.indexes('items') + end + + def test_index + @ctx.add_index 'items', 'id', :unique => true, :name => 'fun' + index = @ctx.indexes('items').find { |idx| idx.name == 'fun' } + + assert_equal 'items', index.table + assert index.unique, 'index is unique' + assert_equal ['id'], index.columns + end + + def test_non_unique_index + @ctx.add_index 'items', 'id', :name => 'fun' + index = @ctx.indexes('items').find { |idx| idx.name == 'fun' } + assert !index.unique, 'index is not unique' + end + + def test_compound_index + @ctx.add_index 'items', %w{ id number }, :name => 'fun' + index = @ctx.indexes('items').find { |idx| idx.name == 'fun' } + assert_equal %w{ id number }.sort, index.columns.sort + end + + private + def assert_logged logs + intercept_logs_on @ctx + yield + assert_equal logs, @ctx.logged + end + + def intercept_logs_on ctx @ctx.extend(Module.new { - attr_reader :logged + attr_accessor :logged def log sql, name - @logged ||= [] @logged << [sql, name] yield end }) - yield - assert_equal logs, @ctx.logged + @ctx.logged = [] end end end -- cgit v1.2.3 From 0bac4c7e0287faa81461c94e6f3a38041b4181ee Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 10 Jul 2010 13:29:12 -0700 Subject: primary key behavioral tests --- .../test/cases/adapters/sqlite/sqlite_adapter_test.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb index 389bcdfa8e..ce0b2f5f5b 100644 --- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb @@ -170,6 +170,23 @@ module ActiveRecord assert_equal %w{ id number }.sort, index.columns.sort end + def test_primary_key + assert_equal 'id', @ctx.primary_key('items') + + @ctx.execute <<-eosql + CREATE TABLE foos ( + internet integer PRIMARY KEY AUTOINCREMENT, + number integer not null + ) + eosql + assert_equal 'internet', @ctx.primary_key('foos') + end + + def test_no_primary_key + @ctx.execute 'CREATE TABLE failboat (number integer not null)' + assert_nil @ctx.primary_key('failboat') + end + private def assert_logged logs -- cgit v1.2.3 From f33ee69d59d2b06da3c3c1b24fc9bb640bf6ed25 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 10 Jul 2010 13:30:49 -0700 Subject: reducing funcalls, cleaning up primary key methods for sqlite adapter --- .../active_record/connection_adapters/sqlite_adapter.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 117cf447df..b7f730d06a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -190,16 +190,21 @@ module ActiveRecord def indexes(table_name, name = nil) #:nodoc: execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| - index = IndexDefinition.new(table_name, row['name']) - index.unique = row['unique'].to_i != 0 - index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] } - index + IndexDefinition.new( + table_name, + row['name'], + row['unique'].to_i != 0, + execute("PRAGMA index_info('#{row['name']}')").map { |col| + col['name'] + }) end end def primary_key(table_name) #:nodoc: - column = table_structure(table_name).find {|field| field['pk'].to_i == 1} - column ? column['name'] : nil + column = table_structure(table_name).find { |field| + field['pk'].to_i == 1 + } + column && column['name'] end def remove_index!(table_name, index_name) #:nodoc: -- cgit v1.2.3 From 16c14f7465530df51e938d00f26141dd0ecf46bc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 10 Jul 2010 19:53:39 -0300 Subject: Avoid interpreted as grouped expression warnings --- activemodel/test/cases/mass_assignment_security/sanitizer_test.rb | 2 +- activemodel/test/cases/mass_assignment_security_test.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb index 367207aab3..015153ec7c 100644 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb @@ -31,7 +31,7 @@ class SanitizerTest < ActiveModel::TestCase log = StringIO.new @sanitizer.logger = Logger.new(log) @sanitizer.sanitize(original_attributes) - assert (log.string =~ /admin/), "Should log removed attributes: #{log.string}" + assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}") end end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index 0f7a38b0bc..c25b0fdf00 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -35,10 +35,10 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections' - assert (TightPerson.protected_attributes - TightPerson.attributes_protected_by_default).blank? + assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes - assert (TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default).blank? + assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes end @@ -49,4 +49,4 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal sanitized, { } end -end \ No newline at end of file +end -- cgit v1.2.3 From 060ca6abce737b1c2ad53f2427ccaee7142d7cf9 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 11 Jul 2010 00:46:34 -0300 Subject: Add missing require to use Set --- .../lib/active_model/mass_assignment_security/permission_set.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb index 7c48472799..9fcb94d48a 100644 --- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb +++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb @@ -1,3 +1,4 @@ +require 'set' require 'active_model/mass_assignment_security/sanitizer' module ActiveModel @@ -36,4 +37,4 @@ module ActiveModel end end end -end \ No newline at end of file +end -- cgit v1.2.3 From b0fab0c5c400bc5ca10908643e8b543767e50a29 Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Sun, 11 Jul 2010 19:17:36 +0200 Subject: Getting started guide: rephrase the paragraph about the root route for better understanding --- railties/guides/source/getting_started.textile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index f547f29087..8e018437fd 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -322,16 +322,15 @@ $ rm public/index.html We need to do this as Rails will deliver any static file in the +public+ directory in preference to any dynamic contact we generate from the controllers. -Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. There are only comments in this file, so we need to add at the top the following: +Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +:root to+, uncomment it and change it like the following: Blog::Application.routes.draw do |map| - root :to => "home#index" - - # The priority is based upon order of creation: - # first created -> highest priority. #... + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + root :to => "home#index" The +root :to => "home#index"+ tells Rails to map the root action to the home controller's index action. -- cgit v1.2.3 From 1dddc79fee290702ccef75834ab85fddf3a5fd8f Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Sun, 11 Jul 2010 21:47:31 +0200 Subject: Getting started guide: Post validation example is clearer if we do p=Post.new;p.save better than p=Post.create;p.save --- railties/guides/source/getting_started.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 8e018437fd..7a5266ce2c 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -474,7 +474,7 @@ $ rails console After the console loads, you can use it to work with your application's models: ->> p = Post.create(:content => "A new post") +>> p = Post.new(:content => "A new post") => # -- cgit v1.2.3 From 913aeaef818ca7729064c60bf0a877afbb156d61 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 11 Jul 2010 18:02:49 -0300 Subject: We need to wait for the final release of bundler for this to make sense --- ci/ci_build.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/ci_build.rb b/ci/ci_build.rb index ee6c357519..17f4ba5d51 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -19,9 +19,9 @@ puts "[CruiseControl] Rails build" build_results = {} # Install required version of bundler. -bundler_install_cmd = "gem install bundler --no-ri --no-rdoc" -puts "Running command: #{bundler_install_cmd}" -build_results[:install_bundler] = system bundler_install_cmd +#bundler_install_cmd = "gem install bundler --no-ri --no-rdoc" +#puts "Running command: #{bundler_install_cmd}" +#build_results[:install_bundler] = system bundler_install_cmd cd root_dir do puts -- cgit v1.2.3 From 725090942f698f334b42c332036c571640697a52 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 12 Jul 2010 09:51:37 +0800 Subject: We shouldn't rely on float comparison, delta added just in case float representation of this values aren't equal --- activesupport/test/notifications_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index e11de5f67a..3e16e01d89 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -198,9 +198,9 @@ module Notifications time = Time.now event = event(:foo, time, time + 0.01, random_id, {}) - assert_equal :foo, event.name - assert_equal time, event.time - assert_equal 10.0, event.duration + assert_equal :foo, event.name + assert_equal time, event.time + assert_in_delta 10.0, event.duration, 0.00000000000001 end def test_events_consumes_information_given_as_payload -- cgit v1.2.3 From 1a35b6215fe4fb1630e2a635789038c5e6e56c63 Mon Sep 17 00:00:00 2001 From: Steven Hancock Date: Sun, 11 Jul 2010 23:57:26 -0700 Subject: Removed deprecated |map| block argument from routing docs since it is no longer generated in edge Rails config/routes.rb Didn't touch plugins guide since I'm not too clear on how routes work in plugins. --- actionpack/lib/action_dispatch/routing.rb | 2 +- actionpack/lib/action_view/helpers/atom_feed_helper.rb | 2 +- railties/guides/source/getting_started.textile | 2 +- railties/guides/source/i18n.textile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index c664fb0bc2..da62b14f9b 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -31,7 +31,7 @@ module ActionDispatch # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # AppName::Application.routes.draw do |map| + # AppName::Application.routes.draw do # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index cb5a1404ff..8e7cf2e701 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -10,7 +10,7 @@ module ActionView # Full usage example: # # config/routes.rb: - # Basecamp::Application.routes.draw do |map| + # Basecamp::Application.routes.draw do # resources :posts # root :to => "posts#index" # end diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index f547f29087..b4d1ae74c7 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -325,7 +325,7 @@ We need to do this as Rails will deliver any static file in the +public+ directo Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. There are only comments in this file, so we need to add at the top the following: -Blog::Application.routes.draw do |map| +Blog::Application.routes.draw do root :to => "home#index" diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index b09bb470ee..63d22db485 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -287,7 +287,7 @@ You most probably have something like this in one of your applications: # config/routes.rb -Yourapp::Application.routes.draw do |map| +Yourapp::Application.routes.draw do root :to => "home#index" end -- cgit v1.2.3 From 3d7e4119539f373720bc9aa398ecbb6542c7dc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 12 Jul 2010 15:11:15 +0200 Subject: Routes should respect namespace even if action is a regexp and just controller is given to to. --- actionpack/lib/action_dispatch/routing/mapper.rb | 63 +++++++++++------------- actionpack/test/dispatch/routing_test.rb | 18 +++++-- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 430f6fc5a3..6a4b57e3f9 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -35,11 +35,12 @@ module ActionDispatch end class Mapping #:nodoc: - IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] + IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] def initialize(set, scope, args) @set, @scope = set, scope @path, @options = extract_path_and_options(args) + normalize_options! end def to_route @@ -57,14 +58,6 @@ module ActionDispatch path = args.first end - if @scope[:module] && options[:to] && !options[:to].is_a?(Proc) - if options[:to].to_s.include?("#") - options[:to] = "#{@scope[:module]}/#{options[:to]}" - elsif @scope[:controller].nil? - options[:to] = "#{@scope[:module]}##{options[:to]}" - end - end - if path.match(':controller') raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module] @@ -75,15 +68,19 @@ module ActionDispatch options.reverse_merge!(:controller => /.+?/) end - path = normalize_path(path) - path_without_format = path.sub(/\(\.:format\)$/, '') + [ normalize_path(path), options ] + end - if using_match_shorthand?(path_without_format, options) - options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1') - options[:as] ||= path_without_format[1..-1].gsub("/", "_") + def normalize_options! + path_without_format = @path.sub(/\(\.:format\)$/, '') + + if using_match_shorthand?(path_without_format, @options) + to_shorthand = @options[:to].blank? + @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1') + @options[:as] ||= path_without_format[1..-1].gsub("/", "_") end - [ path, options ] + @options.merge!(default_controller_and_action(to_shorthand)) end # match "account" => "account#index" @@ -122,44 +119,40 @@ module ActionDispatch def defaults @defaults ||= (@options[:defaults] || {}).tap do |defaults| - defaults.merge!(default_controller_and_action) defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults] @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) } end end - def default_controller_and_action + def default_controller_and_action(to_shorthand=nil) if to.respond_to?(:call) { } else - defaults = case to - when String + if to.is_a?(String) controller, action = to.split('#') - { :controller => controller, :action => action } - when Symbol - { :action => to.to_s } - else - {} + elsif to.is_a?(Symbol) + action = to.to_s end - defaults[:controller] ||= default_controller - defaults[:action] ||= default_action + controller ||= default_controller + action ||= default_action - defaults.delete(:controller) if defaults[:controller].blank? || defaults[:controller].is_a?(Regexp) - defaults.delete(:action) if defaults[:action].blank? || defaults[:action].is_a?(Regexp) + unless controller.is_a?(Regexp) || to_shorthand + controller = [@scope[:module], controller].compact.join("/").presence + end - defaults[:controller] = defaults[:controller].to_s if defaults.key?(:controller) - defaults[:action] = defaults[:action].to_s if defaults.key?(:action) + controller = controller.to_s unless controller.is_a?(Regexp) + action = action.to_s unless action.is_a?(Regexp) - if defaults[:controller].blank? && segment_keys.exclude?("controller") + if controller.blank? && segment_keys.exclude?("controller") raise ArgumentError, "missing :controller" end - if defaults[:action].blank? && segment_keys.exclude?("action") + if action.blank? && segment_keys.exclude?("action") raise ArgumentError, "missing :action" end - defaults + { :controller => controller, :action => action } end end @@ -207,6 +200,8 @@ module ActionDispatch def default_action if @options[:action] @options[:action] + elsif @scope[:action] + @scope[:action] end end end @@ -424,7 +419,7 @@ module ActionDispatch end def merge_controller_scope(parent, child) - @scope[:module] ? "#{@scope[:module]}/#{child}" : child + child end def merge_path_names_scope(parent, child) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 2a014bf976..4808663aa9 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -245,7 +245,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest namespace :account do match 'shorthand' - match 'description', :to => "description", :as => "description" + match 'description', :to => :description, :as => "description" + match ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback resource :subscription, :credit, :credit_card root :to => "account#index" @@ -1159,7 +1160,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - def test_convention_match_with_no_scope + def test_match_shorthand_with_no_scope with_test_routes do assert_equal '/account/overview', account_overview_path get '/account/overview' @@ -1167,7 +1168,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - def test_convention_match_inside_namespace + def test_match_shorthand_inside_namespace with_test_routes do assert_equal '/account/shorthand', account_shorthand_path get '/account/shorthand' @@ -1175,6 +1176,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_scoped_controller_with_namespace_and_action + with_test_routes do + assert_equal '/account/twitter/callback', account_callback_path("twitter") + get '/account/twitter/callback' + assert_equal 'account/callbacks#twitter', @response.body + + get '/account/whatever/callback' + assert_equal 'Not Found', @response.body + end + end + def test_convention_match_nested_and_with_leading_slash with_test_routes do assert_equal '/account/nested/overview', account_nested_overview_path -- cgit v1.2.3 From cf69a010790d22dc2e66f172437c71ccff2e6366 Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Mon, 12 Jul 2010 18:23:13 +0200 Subject: Getting started guide: typos and changelog --- railties/guides/source/getting_started.textile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 7a5266ce2c..3a4f16c3cd 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -1193,7 +1193,7 @@ The +destroy+ action will find the post we are looking at, locate the comment wi h4. Deleting Associated Objects -If you delete a post then it's associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows: +If you delete a post then its associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows: class Post < ActiveRecord::Base @@ -1485,6 +1485,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2 +* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com * May 16, 2010: Added a section on configuration gotchas to address common encoding problems that people might have by "Yehuda Katz":http://www.yehudakatz.com * April 30, 2010: Fixes, editing and updating of code samples by "Rohit Arondekar":http://rohitarondekar.com * April 25, 2010: Couple of more minor fixups "Mikel Lindsaar":credits.html#raasdnil -- cgit v1.2.3 From d030f830cfd903d43cd1436b0e97389728491f42 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 12 Jul 2010 14:42:57 -0300 Subject: Fixes a regression caused by having mapping options controller and action as blank MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_dispatch/routing/mapper.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6a4b57e3f9..526c97ff8e 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -152,7 +152,10 @@ module ActionDispatch raise ArgumentError, "missing :action" end - { :controller => controller, :action => action } + { :controller => controller, :action => action }.tap do |hash| + hash.delete(:controller) if hash[:controller].blank? + hash.delete(:action) if hash[:action].blank? + end end end -- cgit v1.2.3 From da84c9d0d243bb3b27748e250cb7ef71df70bb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 12 Jul 2010 20:18:04 +0200 Subject: Remove old rubygems require. --- .../connection_adapters/abstract/connection_specification.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 23c42d670b..8e74eff0ab 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -66,15 +66,9 @@ module ActiveRecord unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end begin - require 'rubygems' - gem "activerecord-#{spec[:adapter]}-adapter" require "active_record/connection_adapters/#{spec[:adapter]}_adapter" rescue LoadError - begin - require "active_record/connection_adapters/#{spec[:adapter]}_adapter" - rescue LoadError - raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})" - end + raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})" end adapter_method = "#{spec[:adapter]}_connection" -- cgit v1.2.3 From 6b29dc876fe185881d46731c3ae170478a3828fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 12 Jul 2010 20:18:54 +0200 Subject: Just add connection management middleware if running in a concurrent environment. --- activerecord/lib/active_record/railtie.rb | 13 ++++++++----- railties/test/application/initializers/frameworks_test.rb | 12 +++++++++++- railties/test/application/middleware_test.rb | 1 - 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 2808e199fe..eff51a7c87 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -16,11 +16,7 @@ module ActiveRecord config.generators.orm :active_record, :migration => true, :timestamps => true - config.app_middleware.insert_after "::ActionDispatch::Callbacks", - "ActiveRecord::QueryCache" - - config.app_middleware.insert_after "::ActionDispatch::Callbacks", - "ActiveRecord::ConnectionAdapters::ConnectionManagement" + config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::QueryCache" rake_tasks do load "active_record/railties/databases.rake" @@ -72,6 +68,13 @@ module ActiveRecord end end + initializer "active_record.add_concurrency_middleware" do |app| + if app.config.allow_concurrency + app.config.middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement" + end + end + config.after_initialize do ActiveSupport.on_load(:active_record) do instantiate_observers diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 4ff10091b1..d8e916f45f 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -98,7 +98,17 @@ module ApplicationTests require "#{app_path}/config/environment" - expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] + expects = [ActiveRecord::QueryCache, ActiveRecord::SessionStore] + middleware = Rails.application.config.middleware.map { |m| m.klass } + assert_equal expects, middleware & expects + end + + test "database middleware initializes when allow concurrency is true" do + add_to_config "config.threadsafe!" + + require "#{app_path}/config/environment" + + expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache] middleware = Rails.application.config.middleware.map { |m| m.klass } assert_equal expects, middleware & expects end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index e66e81ea2c..ed06b4c767 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -28,7 +28,6 @@ module ApplicationTests "ActionDispatch::RemoteIp", "Rack::Sendfile", "ActionDispatch::Callbacks", - "ActiveRecord::ConnectionAdapters::ConnectionManagement", "ActiveRecord::QueryCache", "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", -- cgit v1.2.3 From 8fb838ed1690fb38ea3dfced1827ef5e7683cc3b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 16:32:08 -0700 Subject: use Hash#each to avoid a second hash lookup --- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index b7f730d06a..7ccd50bd8c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -283,10 +283,8 @@ module ActiveRecord def select(sql, name = nil) #:nodoc: execute(sql, name).map do |row| record = {} - row.each_key do |key| - if key.is_a?(String) - record[key.sub(/^"?\w+"?\./, '')] = row[key] - end + row.each do |key, value| + record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String) end record end -- cgit v1.2.3 From cee2ff2768589cb81781fbbdafb9925ba203902f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 16:43:20 -0700 Subject: these strings do not need to be frozen --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 7ccd50bd8c..e812a0371b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -381,9 +381,9 @@ module ActiveRecord def default_primary_key_type if supports_autoincrement? - 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze + 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL' else - 'INTEGER PRIMARY KEY NOT NULL'.freeze + 'INTEGER PRIMARY KEY NOT NULL' end end -- cgit v1.2.3 From 7c4e0401d25e29b2960ae2adb31194d908cfd25b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 16:54:00 -0700 Subject: cleaning up PostgreSQLAdapter#select --- .../active_record/connection_adapters/postgresql_adapter.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2fe2ae7136..004c6620df 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -941,15 +941,9 @@ module ActiveRecord # conversions that are required to be performed here instead of in PostgreSQLColumn. def select(sql, name = nil) fields, rows = select_raw(sql, name) - result = [] - for row in rows - row_hash = {} - fields.each_with_index do |f, i| - row_hash[f] = row[i] - end - result << row_hash + rows.map do |row| + Hash[*fields.zip(row).flatten] end - result end def select_raw(sql, name = nil) -- cgit v1.2.3 From c9710a43ff6cdc536f08983b007768ae6a66dd10 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 17:00:29 -0700 Subject: refactor PostgreSQLAdapter#result_as_array to create fewer Range objects --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 004c6620df..9bf08a7671 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -431,15 +431,15 @@ module ActiveRecord def result_as_array(res) #:nodoc: # check if we have any binary column and if they need escaping unescape_col = [] - for j in 0...res.nfields do + res.nfields.times do |j| # unescape string passed BYTEA field (OID == 17) unescape_col << ( res.ftype(j)==17 ) end ary = [] - for i in 0...res.ntuples do + res.ntuples.times do |i| ary << [] - for j in 0...res.nfields do + res.nfields.times do |j| data = res.getvalue(i,j) data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String) ary[i] << data -- cgit v1.2.3 From 8521cdf03d2b9761f21b593e10a31ca96d05092c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 17:44:15 -0700 Subject: PostgreSQLAdapter#select_raw fields and results are empty even if ntuples is 0 --- .../connection_adapters/postgresql_adapter.rb | 46 ++++++++++------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 9bf08a7671..1429f44ff3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -949,34 +949,30 @@ module ActiveRecord def select_raw(sql, name = nil) res = execute(sql, name) results = result_as_array(res) - fields = [] - rows = [] - if res.ntuples > 0 - fields = res.fields - results.each do |row| - hashed_row = {} - row.each_index do |cell_index| - # If this is a money type column and there are any currency symbols, - # then strip them off. Indeed it would be prettier to do this in - # PostgreSQLColumn.string_to_decimal but would break form input - # fields that call value_before_type_cast. - if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID - # Because money output is formatted according to the locale, there are two - # cases to consider (note the decimal separators): - # (1) $12,345,678.12 - # (2) $12.345.678,12 - case column = row[cell_index] - when /^-?\D+[\d,]+\.\d{2}$/ # (1) - row[cell_index] = column.gsub(/[^-\d\.]/, '') - when /^-?\D+[\d\.]+,\d{2}$/ # (2) - row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.') - end + fields = res.fields + rows = results.map do |row| + hashed_row = {} + row.each_index do |cell_index| + # If this is a money type column and there are any currency symbols, + # then strip them off. Indeed it would be prettier to do this in + # PostgreSQLColumn.string_to_decimal but would break form input + # fields that call value_before_type_cast. + if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + case column = row[cell_index] + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + row[cell_index] = column.gsub(/[^-\d\.]/, '') + when /^-?\D+[\d\.]+,\d{2}$/ # (2) + row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.') end - - hashed_row[fields[cell_index]] = column end - rows << row + + hashed_row[fields[cell_index]] = column end + row end res.clear return fields, rows -- cgit v1.2.3 From 79e9f4a3183064aa5486cda0e7cbddfea6e08c33 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 17:49:12 -0700 Subject: PostgreSQLAdapter#select_raw use each_with_index to avoid multiple array lookups --- .../active_record/connection_adapters/postgresql_adapter.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 1429f44ff3..f4756ed18d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -952,7 +952,7 @@ module ActiveRecord fields = res.fields rows = results.map do |row| hashed_row = {} - row.each_index do |cell_index| + row.each_with_index do |cell, cell_index| # If this is a money type column and there are any currency symbols, # then strip them off. Indeed it would be prettier to do this in # PostgreSQLColumn.string_to_decimal but would break form input @@ -962,15 +962,15 @@ module ActiveRecord # cases to consider (note the decimal separators): # (1) $12,345,678.12 # (2) $12.345.678,12 - case column = row[cell_index] + case cell when /^-?\D+[\d,]+\.\d{2}$/ # (1) - row[cell_index] = column.gsub(/[^-\d\.]/, '') + row[cell_index] = cell.gsub(/[^-\d\.]/, '') when /^-?\D+[\d\.]+,\d{2}$/ # (2) - row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.') + row[cell_index] = cell.gsub(/[^-\d,]/, '').sub(/,/, '.') end end - hashed_row[fields[cell_index]] = column + hashed_row[fields[cell_index]] = cell end row end -- cgit v1.2.3 From 2c3aab32dd064c2812088c1226562ee522d55d48 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 17:52:53 -0700 Subject: PostgreSQLAdapter#select_raw removing dead code --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f4756ed18d..87ae23d104 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -951,7 +951,6 @@ module ActiveRecord results = result_as_array(res) fields = res.fields rows = results.map do |row| - hashed_row = {} row.each_with_index do |cell, cell_index| # If this is a money type column and there are any currency symbols, # then strip them off. Indeed it would be prettier to do this in @@ -969,8 +968,6 @@ module ActiveRecord row[cell_index] = cell.gsub(/[^-\d,]/, '').sub(/,/, '.') end end - - hashed_row[fields[cell_index]] = cell end row end -- cgit v1.2.3 From 2aed63eb526b5493df63151e2bf451d55114c49a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 12 Jul 2010 17:58:48 -0700 Subject: our method is modifying the original array, so refactor to use destructive methods --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 87ae23d104..dd623def2e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -950,7 +950,7 @@ module ActiveRecord res = execute(sql, name) results = result_as_array(res) fields = res.fields - rows = results.map do |row| + results.each do |row| row.each_with_index do |cell, cell_index| # If this is a money type column and there are any currency symbols, # then strip them off. Indeed it would be prettier to do this in @@ -963,16 +963,15 @@ module ActiveRecord # (2) $12.345.678,12 case cell when /^-?\D+[\d,]+\.\d{2}$/ # (1) - row[cell_index] = cell.gsub(/[^-\d\.]/, '') + cell.gsub!(/[^-\d\.]/, '') when /^-?\D+[\d\.]+,\d{2}$/ # (2) - row[cell_index] = cell.gsub(/[^-\d,]/, '').sub(/,/, '.') + cell.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end end end - row end res.clear - return fields, rows + return fields, results end # Returns the list of a table's column names, data types, and default values. -- cgit v1.2.3 From 1d45ea081493cd4eca95cd75cce7be7b8d9cb07c Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 9 Jul 2010 14:11:51 -0400 Subject: with this fix touch method - does not call validations - doest not call callbacks - updates updated_at/on along with attribute if attribute is provided - marks udpated_at/on and attribute as NOT changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#2520 state:resolved] Signed-off-by: José Valim --- activerecord/lib/active_record/persistence.rb | 1 + activerecord/lib/active_record/timestamp.rb | 16 +++++++--------- activerecord/test/cases/timestamp_test.rb | 10 ++++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 828a8b41b6..44264bec7f 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -111,6 +111,7 @@ module ActiveRecord if record_update_timestamps timestamp_attributes_for_update_in_model.each do |column| hash[column] = read_attribute(column) + @changed_attributes.delete(column.to_s) end end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 1075a60f07..5cddd07e82 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -21,24 +21,22 @@ module ActiveRecord end # Saves the record with the updated_at/on attributes set to the current time. - # If the save fails because of validation errors, an - # ActiveRecord::RecordInvalid exception is raised. If an attribute name is passed, - # that attribute is used for the touch instead of the updated_at/on attributes. + # Please note that no validation is performed and no callbacks are executed. + # If an attribute name is passed, that attribute is updated along with + # updated_at/on attributes. # # Examples: # - # product.touch # updates updated_at - # product.touch(:designed_at) # updates the designed_at attribute + # product.touch # updates updated_at/on + # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on def touch(attribute = nil) current_time = current_time_from_proper_timezone if attribute - write_attribute(attribute, current_time) + self.update_attribute(attribute, current_time) else - timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) } + timestamp_attributes_for_update_in_model.each { |column| self.update_attribute(column, current_time) } end - - save! end private diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 549c4af6b1..f765540808 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -25,16 +25,26 @@ class TimestampTest < ActiveRecord::TestCase end def test_touching_a_record_updates_its_timestamp + previous_salary = @developer.salary + @developer.salary = previous_salary + 10000 @developer.touch assert_not_equal @previously_updated_at, @developer.updated_at + assert_equal previous_salary + 10000, @developer.salary + assert @developer.salary_changed?, 'developer salary should have changed' + assert @developer.changed?, 'developer should be marked as changed' + @developer.reload + assert_equal previous_salary, @developer.salary end def test_touching_a_different_attribute previously_created_at = @developer.created_at @developer.touch(:created_at) + assert !@developer.created_at_changed? , 'created_at should not be changed' + assert !@developer.changed?, 'record should not be changed' assert_not_equal previously_created_at, @developer.created_at + assert_not_equal @previously_updated_at, @developer.updated_at end def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at -- cgit v1.2.3 From 34013115626b2632ffe8ec357e2c5f47cfa059b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 13 Jul 2010 08:05:09 +0200 Subject: Tidying up a bit, so update_attribute is not called twice on touch. --- activerecord/lib/active_record/persistence.rb | 18 ++++++++---------- activerecord/lib/active_record/timestamp.rb | 24 +++++++++--------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 44264bec7f..e53cc5ee8c 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -105,19 +105,17 @@ module ActiveRecord # Updates a single attribute and saves the record without going through the normal validation procedure # or callbacks. This is especially useful for boolean flags on existing records. def update_attribute(name, value) - send("#{name}=", value) - hash = { name => read_attribute(name) } - - if record_update_timestamps - timestamp_attributes_for_update_in_model.each do |column| - hash[column] = read_attribute(column) - @changed_attributes.delete(column.to_s) - end + changes = record_update_timestamps || {} + + if name + name = name.to_s + send("#{name}=", value) + changes[name] = read_attribute(name) end - @changed_attributes.delete(name.to_s) + @changed_attributes.except!(*changes.keys) primary_key = self.class.primary_key - self.class.update_all(hash, { primary_key => self[primary_key] }) == 1 + self.class.update_all(changes, { primary_key => self[primary_key] }) == 1 end # Updates all the attributes from the passed-in Hash and saves the record. diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 5cddd07e82..341cc87be5 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -30,16 +30,11 @@ module ActiveRecord # product.touch # updates updated_at/on # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on def touch(attribute = nil) - current_time = current_time_from_proper_timezone - - if attribute - self.update_attribute(attribute, current_time) - else - timestamp_attributes_for_update_in_model.each { |column| self.update_attribute(column, current_time) } - end + update_attribute(attribute, current_time_from_proper_timezone) end private + def create #:nodoc: if record_timestamps current_time = current_time_from_proper_timezone @@ -56,17 +51,16 @@ module ActiveRecord end def update(*args) #:nodoc: - record_update_timestamps + record_update_timestamps if !partial_updates? || changed? super end - def record_update_timestamps - if record_timestamps && (!partial_updates? || changed?) - current_time = current_time_from_proper_timezone - timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) } - true - else - false + def record_update_timestamps #:nodoc: + return unless record_timestamps + current_time = current_time_from_proper_timezone + timestamp_attributes_for_update_in_model.inject({}) do |hash, column| + hash[column.to_s] = write_attribute(column.to_s, current_time) + hash end end -- cgit v1.2.3 From 571cb1ddc6692ed96e04a14e670beb01ba8c93ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 8 Jul 2010 22:17:21 +0200 Subject: enable AS::JSON.encode to encode AR::Relation by providing `as_json` method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5073 state:resolved] Signed-off-by: José Valim --- activerecord/lib/active_record/relation.rb | 4 +++- activerecord/test/cases/json_serialization_test.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d9fc1b4940..7499100f55 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -10,7 +10,7 @@ module ActiveRecord include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches - delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a + delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a delegate :insert, :to => :arel attr_reader :table, :klass @@ -74,6 +74,8 @@ module ActiveRecord @records end + def as_json(options = nil) to_a end #:nodoc: + # Returns size of the records. def size loaded? ? @records.length : count diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index c275557da8..2bc746c0b8 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -201,4 +201,11 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase } assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name]) end + + def test_should_be_able_to_encode_relation + authors_relation = Author.where(:id => [@david.id, @mary.id]) + + json = ActiveSupport::JSON.encode authors_relation, :only => :name + assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json + end end -- cgit v1.2.3 From 44e7fba59e251c1eb12d8f793a643b5804cb3977 Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Fri, 9 Jul 2010 04:01:58 -0400 Subject: renaming test name to fix accidently override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5076 state:resolved] Signed-off-by: José Valim --- activerecord/test/cases/associations/has_many_associations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a52cedd8c2..f1440804d2 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -549,7 +549,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert the_client.new_record? end - def test_find_or_create + def test_find_or_create_updates_size number_of_clients = companies(:first_firm).clients.size the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client") assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size -- cgit v1.2.3 From b520d602ffb85f3816f4407ff9dd5c7721a2da7d Mon Sep 17 00:00:00 2001 From: "Will St. Clair + Neeraj Singh" Date: Sat, 10 Jul 2010 14:19:25 -0500 Subject: string IDs are now quoted correctly [#5064 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../associations/through_association_scope.rb | 2 +- .../associations/has_one_through_associations_test.rb | 12 +++++++++++- activerecord/test/fixtures/dashboards.yml | 3 +++ activerecord/test/fixtures/minivans.yml | 4 ++++ activerecord/test/fixtures/speedometers.yml | 4 ++++ activerecord/test/models/dashboard.rb | 3 +++ activerecord/test/models/minivan.rb | 6 ++++++ activerecord/test/models/speedometer.rb | 4 ++++ activerecord/test/schema/schema.rb | 17 +++++++++++++++++ 9 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 activerecord/test/fixtures/dashboards.yml create mode 100644 activerecord/test/fixtures/minivans.yml create mode 100644 activerecord/test/fixtures/speedometers.yml create mode 100644 activerecord/test/models/dashboard.rb create mode 100644 activerecord/test/models/minivan.rb create mode 100644 activerecord/test/models/speedometer.rb diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index 22e1033a9d..cabb33c4a8 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -35,7 +35,7 @@ module ActiveRecord @owner.class.base_class.name.to_s, reflection.klass.columns_hash["#{as}_type"]) } elsif reflection.macro == :belongs_to - { reflection.klass.primary_key => @owner[reflection.primary_key_name] } + { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) } else { reflection.primary_key_name => owner_quoted_id } end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 178c57435b..3fcd150422 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -6,9 +6,12 @@ require 'models/membership' require 'models/sponsor' require 'models/organization' require 'models/member_detail' +require 'models/minivan' +require 'models/dashboard' +require 'models/speedometer' class HasOneThroughAssociationsTest < ActiveRecord::TestCase - fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations + fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers def setup @member = members(:groucho) @@ -202,4 +205,11 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase Club.find(@club.id, :include => :sponsored_member).save! end end + + def test_value_is_properly_quoted + minivan = Minivan.find('m1') + assert_nothing_raised do + minivan.dashboard + end + end end diff --git a/activerecord/test/fixtures/dashboards.yml b/activerecord/test/fixtures/dashboards.yml new file mode 100644 index 0000000000..e75bf46e6c --- /dev/null +++ b/activerecord/test/fixtures/dashboards.yml @@ -0,0 +1,3 @@ +cool_first: + dashboard_id: d1 + name: my_dashboard \ No newline at end of file diff --git a/activerecord/test/fixtures/minivans.yml b/activerecord/test/fixtures/minivans.yml new file mode 100644 index 0000000000..e7a2ab77eb --- /dev/null +++ b/activerecord/test/fixtures/minivans.yml @@ -0,0 +1,4 @@ +cool_first: + minivan_id: m1 + name: my_minivan + speedometer_id: s1 diff --git a/activerecord/test/fixtures/speedometers.yml b/activerecord/test/fixtures/speedometers.yml new file mode 100644 index 0000000000..6a471aba0a --- /dev/null +++ b/activerecord/test/fixtures/speedometers.yml @@ -0,0 +1,4 @@ +cool_first: + speedometer_id: s1 + name: my_speedometer + dashboard_id: d1 \ No newline at end of file diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb new file mode 100644 index 0000000000..a8a25834b1 --- /dev/null +++ b/activerecord/test/models/dashboard.rb @@ -0,0 +1,3 @@ +class Dashboard < ActiveRecord::Base + set_primary_key :dashboard_id +end \ No newline at end of file diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb new file mode 100644 index 0000000000..c753319a20 --- /dev/null +++ b/activerecord/test/models/minivan.rb @@ -0,0 +1,6 @@ +class Minivan < ActiveRecord::Base + set_primary_key :minivan_id + + belongs_to :speedometer + has_one :dashboard, :through => :speedometer +end \ No newline at end of file diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb new file mode 100644 index 0000000000..94743eff8e --- /dev/null +++ b/activerecord/test/models/speedometer.rb @@ -0,0 +1,4 @@ +class Speedometer < ActiveRecord::Base + set_primary_key :speedometer_id + belongs_to :dashboard +end \ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index bea351b95a..641726b43f 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -164,6 +164,11 @@ ActiveRecord::Schema.define do t.string :address_country t.string :gps_location end + + create_table :dashboards, :force => true, :id => false do |t| + t.string :dashboard_id + t.string :name + end create_table :developers, :force => true do |t| t.string :name @@ -290,6 +295,12 @@ ActiveRecord::Schema.define do t.boolean :favourite t.integer :lock_version, :default => 0 end + + create_table :minivans, :force => true, :id => false do |t| + t.string :minivan_id + t.string :name + t.string :speedometer_id + end create_table :minimalistics, :force => true do |t| end @@ -452,6 +463,12 @@ ActiveRecord::Schema.define do t.string :name t.integer :ship_id end + + create_table :speedometers, :force => true, :id => false do |t| + t.string :speedometer_id + t.string :name + t.string :dashboard_id + end create_table :sponsors, :force => true do |t| t.integer :club_id -- cgit v1.2.3 From d2fae5f78efd7123b9ce633933f77cbe0e0711a9 Mon Sep 17 00:00:00 2001 From: Miles Egan Date: Sun, 11 Jul 2010 10:43:41 -0700 Subject: remove size attribute from tag generated by file_field helper [#5084 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_view/helpers/form_helper.rb | 2 +- actionpack/test/template/form_helper_test.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 6302491c2a..711c455ded 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -676,7 +676,7 @@ module ActionView # # => # def file_field(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options) + InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil})) end # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+) diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 4b9e41803f..9ddfa13c16 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -200,6 +200,11 @@ class FormHelperTest < ActionView::TestCase assert_equal object_name, "post[]" end + def test_file_field_has_no_size + expected = '' + assert_dom_equal expected, file_field("user", "avatar") + end + def test_hidden_field assert_dom_equal '', hidden_field("post", "title") -- cgit v1.2.3 From b75fca9e57423eec81384ff3e35f38fd4f7fe47f Mon Sep 17 00:00:00 2001 From: Vitalii Khustochka Date: Mon, 12 Jul 2010 14:17:48 +0300 Subject: Added reorder delegation for ActiveRecord::Base(to be able to overwrite the default_scope ordering in the named scope [#5093 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/base.rb | 2 +- activerecord/test/cases/relation_scoping_test.rb | 6 ++++++ activerecord/test/models/developer.rb | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c78060c956..400a0adbcf 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -398,7 +398,7 @@ module ActiveRecord #:nodoc: delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped + delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped # Executes a custom SQL query against your database and returns all the results. The results will diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 41dcdbcd37..a5a3b3ef38 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -364,6 +364,12 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_named_scope_reorders_default + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.reordered_by_name.find(:all).collect { |dev| dev.name } + assert_equal expected, received + end + def test_nested_exclusive_scope expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index de68fd7f24..c61c583c1d 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -88,6 +88,7 @@ class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope :order => 'salary DESC' scope :by_name, :order => 'name DESC' + scope :reordered_by_name, reorder('name DESC') def self.all_ordered_by_name with_scope(:find => { :order => 'name DESC' }) do -- cgit v1.2.3 From 7e075e62479a0eccad6eaf7a7c20f45347860c83 Mon Sep 17 00:00:00 2001 From: Benjamin Quorning Date: Fri, 9 Jul 2010 15:58:58 +0200 Subject: Fixed many references to the old config/environment.rb and Rails::Initializer --- actionmailer/lib/action_mailer/base.rb | 2 +- .../lib/action_controller/metal/mime_responds.rb | 2 +- .../middleware/session/cookie_store.rb | 2 +- .../lib/action_view/helpers/sanitize_helper.rb | 28 +++++++++++----------- .../connection_adapters/mysql_adapter.rb | 2 +- activerecord/lib/active_record/migration.rb | 2 +- activerecord/lib/active_record/observer.rb | 2 +- activerecord/lib/active_record/session_store.rb | 2 +- .../lib/active_support/values/time_zone.rb | 6 ++--- railties/lib/rails/generators/actions.rb | 4 ++-- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index ed4bea0c77..7f2ed5ba64 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -129,7 +129,7 @@ module ActionMailer #:nodoc: # # ActionMailer::Base.default_url_options[:host] = "example.com" # - # This can also be set as a configuration option in config/environment.rb: + # This can also be set as a configuration option in config/application.rb: # # config.action_mailer.default_url_options = { :host => "example.com" } # diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 4f384d1ec5..c12e5797ae 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -145,7 +145,7 @@ module ActionController #:nodoc: # and accept Rails' defaults, life will be much easier. # # If you need to use a MIME type which isn't supported by default, you can register your own handlers in - # environment.rb as follows. + # config/initializers/mime_types.rb as follows. # # Mime::Type.register "image/jpg", :jpg # diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index dce47c63bc..ca1494425f 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -35,7 +35,7 @@ module ActionDispatch # such as 'MD5', 'RIPEMD160', 'SHA256', etc. # # To generate a secret key for an existing application, run - # "rake secret" and set the key in config/environment.rb. + # "rake secret" and set the key in config/initializers/secret_token.rb. # # Note that changing digest or secret invalidates all existing sessions! class CookieStore < AbstractStore diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index b47818a22a..63f6154ec4 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -32,13 +32,13 @@ module ActionView # # Add table tags to the default allowed tags # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end # # Remove tags to the default allowed tags # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.after_initialize do # ActionView::Base.sanitized_allowed_tags.delete 'div' # end @@ -46,7 +46,7 @@ module ActionView # # Change allowed default attributes # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' # end # @@ -143,7 +143,7 @@ module ActionView # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with # any object that responds to +sanitize+. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.full_sanitizer = MySpecialSanitizer.new # end # @@ -154,7 +154,7 @@ module ActionView # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with # any object that responds to +sanitize+. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.link_sanitizer = MySpecialSanitizer.new # end # @@ -165,7 +165,7 @@ module ActionView # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+. # Replace with any object that responds to +sanitize+. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.white_list_sanitizer = MySpecialSanitizer.new # end # @@ -175,7 +175,7 @@ module ActionView # Adds valid HTML attributes that the +sanitize+ helper checks for URIs. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target' # end # @@ -185,7 +185,7 @@ module ActionView # Adds to the Set of 'bad' tags for the +sanitize+ helper. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_bad_tags = 'embed', 'object' # end # @@ -195,7 +195,7 @@ module ActionView # Adds to the Set of allowed tags for the +sanitize+ helper. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end # @@ -205,7 +205,7 @@ module ActionView # Adds to the Set of allowed HTML attributes for the +sanitize+ helper. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc' # end # @@ -215,7 +215,7 @@ module ActionView # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_css_properties = 'expression' # end # @@ -225,7 +225,7 @@ module ActionView # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_css_keywords = 'expression' # end # @@ -235,7 +235,7 @@ module ActionView # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_shorthand_css_properties = 'expression' # end # @@ -245,7 +245,7 @@ module ActionView # Adds to the Set of allowed protocols for the +sanitize+ helper. # - # Rails::Initializer.run do |config| + # class Application < Rails::Application # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed' # end # diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index aa3626a37e..b403443d8e 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -125,7 +125,7 @@ module ActiveRecord # By default, the MysqlAdapter will consider all columns of type tinyint(1) # as boolean. If you wish to disable this emulation (which was the default # behavior in versions 0.13.1 and earlier) you can add the following line - # to your environment.rb file: + # to your application.rb file: # # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false cattr_accessor :emulate_booleans diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4c5e1ae218..5e272f0ba4 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -284,7 +284,7 @@ module ActiveRecord # # config.active_record.timestamped_migrations = false # - # In environment.rb. + # In application.rb. # class Migration @@verbose = true diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index d2ed643f35..ce002f5e1a 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -68,7 +68,7 @@ module ActiveRecord # == Configuration # # In order to activate an observer, list it in the config.active_record.observers configuration setting in your - # config/environment.rb file. + # config/application.rb file. # # config.active_record.observers = :comment_observer, :signup_observer # diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index b88d550086..df2f429c5d 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -16,7 +16,7 @@ module ActiveRecord # ActionController::SessionOverflowError will be raised. # # You may configure the table name, primary key, and data column. - # For example, at the end of config/environment.rb: + # For example, at the end of config/application.rb: # # ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table' # ActiveRecord::SessionStore::Session.primary_key = 'session_id' diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 49dd8a1b99..abd585b64f 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -8,10 +8,10 @@ require 'active_support/core_ext/object/try' # * Lazily load TZInfo::Timezone instances only when they're needed. # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods. # -# If you set config.time_zone in the Rails Initializer, you can access this TimeZone object via Time.zone: +# If you set config.time_zone in the Rails Application, you can access this TimeZone object via Time.zone: # -# # environment.rb: -# Rails::Initializer.run do |config| +# # application.rb: +# class Application < Rails::Application # config.time_zone = "Eastern Time (US & Canada)" # end # diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index a27d38e23a..2280cc1507 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -40,7 +40,7 @@ module Rails end end - # Adds an entry into config/environment.rb for the supplied gem. If env + # Adds an entry into Gemfile for the supplied gem. If env # is specified, add the gem to the given environment. # # ==== Example @@ -100,7 +100,7 @@ module Rails end end - # Adds a line inside the Initializer block for config/environment.rb. + # Adds a line inside the Application class for config/application.rb. # # If options :env is specified, the line is appended to the corresponding # file in config/environments. -- cgit v1.2.3 From 2b22d1240be90a75c3f35b7a774d5efc9c4ac68f Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 13 Jul 2010 14:26:26 -0300 Subject: Makes CI builder install --pre version of Bundler, now that bundle install works again --- ci/ci_build.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 17f4ba5d51..0a39aa4f87 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -19,9 +19,9 @@ puts "[CruiseControl] Rails build" build_results = {} # Install required version of bundler. -#bundler_install_cmd = "gem install bundler --no-ri --no-rdoc" -#puts "Running command: #{bundler_install_cmd}" -#build_results[:install_bundler] = system bundler_install_cmd +bundler_install_cmd = "sudo gem install bundler --pre --no-ri --no-rdoc" +puts "Running command: #{bundler_install_cmd}" +build_results[:install_bundler] = system bundler_install_cmd cd root_dir do puts -- cgit v1.2.3 From bc35631dc7d67ec9c918ec1967c708a5388a76e4 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 13 Jul 2010 10:20:47 -0700 Subject: use constants instead of magic numbers. meow --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index dd623def2e..4baf99610e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -433,7 +433,7 @@ module ActiveRecord unescape_col = [] res.nfields.times do |j| # unescape string passed BYTEA field (OID == 17) - unescape_col << ( res.ftype(j)==17 ) + unescape_col << ( res.ftype(j) == BYTEA_COLUMN_TYPE_OID ) end ary = [] @@ -889,6 +889,8 @@ module ActiveRecord private # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: + # The internal PostgreSQL identifier of the BYTEA data type. + BYTEA_COLUMN_TYPE_OID = 17 #:nodoc: # Connects to a PostgreSQL server and sets up the adapter depending on the # connected server's characteristics. -- cgit v1.2.3 From 35e304193b8513e1da523a9c1c34bf323e52b9e6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 13 Jul 2010 10:57:15 -0700 Subject: only loop through all rows and columns once in the postgres adapter --- .../connection_adapters/postgresql_adapter.rb | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4baf99610e..d773be0ca6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -432,8 +432,7 @@ module ActiveRecord # check if we have any binary column and if they need escaping unescape_col = [] res.nfields.times do |j| - # unescape string passed BYTEA field (OID == 17) - unescape_col << ( res.ftype(j) == BYTEA_COLUMN_TYPE_OID ) + unescape_col << res.ftype(j) end ary = [] @@ -441,7 +440,28 @@ module ActiveRecord ary << [] res.nfields.times do |j| data = res.getvalue(i,j) - data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String) + case unescape_col[j] + + # unescape string passed BYTEA field (OID == 17) + when BYTEA_COLUMN_TYPE_OID + data = unescape_bytea(data) if String === data + + # If this is a money type column and there are any currency symbols, + # then strip them off. Indeed it would be prettier to do this in + # PostgreSQLColumn.string_to_decimal but would break form input + # fields that call value_before_type_cast. + when MONEY_COLUMN_TYPE_OID + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + case data + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + data.gsub!(/[^-\d\.]/, '') + when /^-?\D+[\d\.]+,\d{2}$/ # (2) + data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') + end + end ary[i] << data end end @@ -952,26 +972,6 @@ module ActiveRecord res = execute(sql, name) results = result_as_array(res) fields = res.fields - results.each do |row| - row.each_with_index do |cell, cell_index| - # If this is a money type column and there are any currency symbols, - # then strip them off. Indeed it would be prettier to do this in - # PostgreSQLColumn.string_to_decimal but would break form input - # fields that call value_before_type_cast. - if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID - # Because money output is formatted according to the locale, there are two - # cases to consider (note the decimal separators): - # (1) $12,345,678.12 - # (2) $12.345.678,12 - case cell - when /^-?\D+[\d,]+\.\d{2}$/ # (1) - cell.gsub!(/[^-\d\.]/, '') - when /^-?\D+[\d\.]+,\d{2}$/ # (2) - cell.gsub!(/[^-\d,]/, '').sub!(/,/, '.') - end - end - end - end res.clear return fields, results end -- cgit v1.2.3 From e35e6171bb27c26b13469ccf188c25bb324e38ab Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 13 Jul 2010 11:56:00 -0700 Subject: reducing range comparisons when converting types to sql --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d773be0ca6..e213eae026 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -848,11 +848,12 @@ module ActiveRecord # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) return super unless type.to_s == 'integer' + return 'integer' unless limit case limit - when 1..2; 'smallint' - when 3..4, nil; 'integer' - when 5..8; 'bigint' + when 1, 2; 'smallint' + when 3, 4; 'integer' + when 5..8; 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.") end end -- cgit v1.2.3 From f4fbc2c1f943ff11776b2c7c34df6bcbe655a4e5 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 13 Jul 2010 15:30:23 -0400 Subject: update_attributes and update_attributes! are now wrapped in a transaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#922 state:resovled] Signed-off-by: José Valim --- activerecord/lib/active_record/persistence.rb | 12 ++++++++---- activerecord/test/cases/transactions_test.rb | 23 ++++++++++++++++++++++- activerecord/test/models/author.rb | 2 ++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index e53cc5ee8c..3681a63e03 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -121,15 +121,19 @@ module ActiveRecord # Updates all the attributes from the passed-in Hash and saves the record. # If the object is invalid, the saving will fail and false will be returned. def update_attributes(attributes) - self.attributes = attributes - save + with_transaction_returning_status do + self.attributes = attributes + save + end end # Updates an object just like Base.update_attributes but calls save! instead # of save so an exception is raised if the record is invalid. def update_attributes!(attributes) - self.attributes = attributes - save! + with_transaction_returning_status do + self.attributes = attributes + save! + end end # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 958a4e4f94..9255190613 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -3,10 +3,12 @@ require 'models/topic' require 'models/reply' require 'models/developer' require 'models/book' +require 'models/author' +require 'models/post' class TransactionTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - fixtures :topics, :developers + fixtures :topics, :developers, :authors, :posts def setup @first, @second = Topic.find(1, 2).sort_by { |t| t.id } @@ -103,6 +105,25 @@ class TransactionTest < ActiveRecord::TestCase end end + def test_update_attributes_should_rollback_on_failure + author = Author.find(1) + posts_count = author.posts.size + assert posts_count > 0 + status = author.update_attributes(:name => nil, :post_ids => []) + assert !status + assert_equal posts_count, author.posts(true).size + end + + def test_update_attributes_should_rollback_on_failure! + author = Author.find(1) + posts_count = author.posts.size + assert posts_count > 0 + assert_raise(ActiveRecord::RecordInvalid) do + author.update_attributes!(:name => nil, :post_ids => []) + end + assert_equal posts_count, author.posts(true).size + end + def test_cancellation_from_before_destroy_rollbacks_in_destroy add_cancelling_before_destroy_with_db_side_effect_to_topic begin diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 655b45bf57..727978431c 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -108,6 +108,8 @@ class Author < ActiveRecord::Base %w(twitter github) end + validates_presence_of :name + private def log_before_adding(object) @post_log << "before_adding#{object.id || ''}" -- cgit v1.2.3 From edb5401039ee15c37b201244c5dbf660bed51fb4 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 1 Jul 2010 21:41:41 -0400 Subject: count method should not take options if it is operated on has_many association which has finder_sql or counter_sql MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#2395 state:resolved] Signed-off-by: José Valim --- .../associations/association_collection.rb | 7 ++++-- .../associations/has_many_associations_test.rb | 26 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 615b7d2719..1c3a6b56f3 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -183,10 +183,13 @@ module ActiveRecord # descendant's +construct_sql+ method will have set :counter_sql automatically. # Otherwise, construct options and pass them with scope to the target class's +count+. def count(column_name = nil, options = {}) - if @reflection.options[:counter_sql] + column_name, options = nil, column_name if column_name.is_a?(Hash) + + if @reflection.options[:counter_sql] && !options.blank? + raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed" + elsif @reflection.options[:counter_sql] @reflection.klass.count_by_sql(@counter_sql) else - column_name, options = nil, column_name if column_name.is_a?(Hash) if @reflection.options[:uniq] # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index f1440804d2..6218cdd344 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -11,6 +11,32 @@ require 'models/comment' require 'models/person' require 'models/reader' require 'models/tagging' +require 'models/invoice' +require 'models/line_item' + +class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase + class Invoice < ActiveRecord::Base + has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items" + end + def test_should_fail + assert_raise(ArgumentError) do + Invoice.create.custom_line_items.count(:conditions => {:amount => 0}) + end + end +end + +class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase + class Invoice < ActiveRecord::Base + has_many :custom_line_items, :class_name => 'LineItem', :counter_sql => "SELECT COUNT(*) line_items.* from line_items" + end + def test_should_fail + assert_raise(ArgumentError) do + Invoice.create.custom_line_items.count(:conditions => {:amount => 0}) + end + end +end + + class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, -- cgit v1.2.3 From 00f49c74e8354e393ed4ffd6bfa8beb657c8920d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 13 Jul 2010 23:35:58 +0200 Subject: AS guides: reword the docs of Enumerable#group_by --- railties/guides/source/active_support_core_extensions.textile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 58824d7aeb..097d51e007 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1840,9 +1840,7 @@ h3. Extensions to +Enumerable+ h4. +group_by+ -Ruby 1.8.7 and up define +group_by+, and Active Support does it for previous versions. - -This iterator takes a block and builds an ordered hash with its return values as keys. Each key is mapped to the array of elements for which the block returned that value: +Active Support redefines +group_by+ in Ruby 1.8.7 so that it returns an ordered hash as in 1.9: entries_by_surname_initial = address_book.group_by do |entry| @@ -1850,7 +1848,7 @@ entries_by_surname_initial = address_book.group_by do |entry| end -WARNING. Active Support redefines +group_by+ in Ruby 1.8.7 so that it still returns an ordered hash. +Distinct block return values are added to the hash as they come, so that's the resulting order. NOTE: Defined in +active_support/core_ext/enumerable.rb+. -- cgit v1.2.3 From 16bae77c568e3e2607ebcfb16a24b9cf6f53df8f Mon Sep 17 00:00:00 2001 From: wycats Date: Tue, 13 Jul 2010 16:13:37 -0700 Subject: Revert "Improve performance of MessageVerifier while keeping it constant time" This reverts commit 8b05c5207dd5757d55d0c384740db289e6bd5415. --- activesupport/lib/active_support/message_verifier.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 1031662293..6c46b68eaf 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -47,11 +47,11 @@ module ActiveSupport def secure_compare(a, b) return false unless a.bytesize == b.bytesize - l = a.unpack "C*" + l = a.unpack "C#{a.bytesize}" - res = true - b.each_byte { |byte| res = (byte == l.shift) && res } - res + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 end def generate_digest(data) -- cgit v1.2.3 From d10ecfefb8bd4461127f552f14970ad6b3df507f Mon Sep 17 00:00:00 2001 From: Jacob Lewallen Date: Wed, 7 Jul 2010 10:55:40 +0900 Subject: Set destroyed=true in opt locking's destroy [#5058 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/locking/optimistic.rb | 1 + activerecord/test/cases/locking_test.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index ceb0902fde..b6f87a57b8 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -124,6 +124,7 @@ module ActiveRecord end end + @destroyed = true freeze end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 66874cdad1..e7126964cd 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -53,7 +53,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } assert p1.destroy - assert_equal true, p1.frozen? + assert p1.frozen? + assert p1.destroyed? assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } end -- cgit v1.2.3 From 981258cf5e3c4ae8e3e2fa93327e2ac325e9b4b3 Mon Sep 17 00:00:00 2001 From: Michael Hutchinson Date: Wed, 14 Jul 2010 01:39:00 -0700 Subject: Active Record Query Interface Guide: Fixed minor typos. --- railties/guides/source/active_record_querying.textile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index f5e70aef41..a8d86659d1 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -337,7 +337,7 @@ Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash C h4. Hash Conditions -Active Record also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: +Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: NOTE: Only equality, range and subset checking are possible with Hash conditions. @@ -473,7 +473,7 @@ SELECT * FROM clients LIMIT 5, 5 h4. Group -To apply +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find. +To apply a +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find. For example, if you want to find a collection of the dates orders were created on: @@ -527,7 +527,9 @@ client.save h4. Locking Records for Update -Locking is helpful for preventing the race conditions when updating records in the database and ensuring atomic updated. Active Record provides two locking mechanism: +Locking is helpful for preventing race conditions when updating records in the database and ensuring atomic updates. + +Active Record provides two locking mechanisms: * Optimistic Locking * Pessimistic Locking @@ -569,7 +571,7 @@ end h5. Pessimistic Locking -Pessimistic locking uses locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions. +Pessimistic locking uses a locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions. For example: @@ -601,7 +603,7 @@ end h3. Joining Tables -Model.find provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There multiple different ways to specify the +:joins+ option: +Model.find provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to specify the +:joins+ option: h4. Using a String SQL Fragment @@ -782,7 +784,7 @@ You can specify an exclamation point (!) on the end of the dynamic find If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_first_name_and_locked("Ryan", true)+. -There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will firstly perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+: +There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will first perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+: SELECT * FROM clients WHERE (clients.first_name = 'Ryan') LIMIT 1 @@ -792,7 +794,7 @@ INSERT INTO clients (first_name, updated_at, created_at, orders_count, locked) COMMIT -+find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the arguments you passed in. For example: ++find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similarly to calling +new+ with the arguments you passed in. For example: client = Client.find_or_initialize_by_first_name('Ryan') -- cgit v1.2.3 From 6de6fa80106bd1a5b1b2369891536ec36e94037a Mon Sep 17 00:00:00 2001 From: Michael Hutchinson Date: Wed, 14 Jul 2010 02:30:12 -0700 Subject: Active Record Query Interface Guide: Fixed a few typos and made minor changes to improve readability. --- .../guides/source/active_record_querying.textile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index a8d86659d1..8c43a02c96 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -347,7 +347,7 @@ h5. Equality Conditions Client.where({ :locked => true }) -The field name does not have to be a symbol it can also be a string: +The field name can also be a string: Client.where({ 'locked' => true }) @@ -447,7 +447,7 @@ h4. Limit and Offset To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +limit+ and +offset+ methods on the relation. -If you want to limit the amount of records to a certain subset of all the records retrieved you usually use +limit+ for this, sometimes coupled with +offset+. Limit is the maximum number of records that will be retrieved from a query, and offset is the number of records it will start reading from from the first record of the set. For example: +You can use +limit+ to specify the number of records to be retrieved, and use +offset+ to specify the number of records to skip before starting to return the records. For example: Client.limit(5) @@ -491,7 +491,7 @@ SELECT * FROM orders GROUP BY date(created_at) h4. Having -SQL uses +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can specify the +HAVING+ clause to the SQL fired by the +Model.find+ using +:having+ option on the find. +SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can add the +HAVING+ clause to the SQL fired by the +Model.find+ by adding the +:having+ option to the find. For example: @@ -517,7 +517,7 @@ Any attempt to alter or destroy the readonly records will not succeed, raising a Client.first.readonly(true) -If you assign this record to a variable client, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception: +For example, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception: client = Client.first.readonly(true) @@ -540,7 +540,7 @@ Optimistic locking allows multiple users to access the same record for edits, an Optimistic locking column -In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice will let the last one saved raise an +ActiveRecord::StaleObjectError+ exception if the first was also updated. Example: +In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example: c1 = Client.find(1) @@ -700,7 +700,7 @@ time_range = (Time.now.midnight - 1.day)..Time.now.midnight Client.joins(:orders).where('orders.created_at' => time_range) -An alternative and cleaner syntax to this is to nest the hash conditions: +An alternative and cleaner syntax is to nest the hash conditions: time_range = (Time.now.midnight - 1.day)..Time.now.midnight @@ -729,7 +729,7 @@ This code looks fine at the first sight. But the problem lies within the total n Solution to N 1 queries problem -Active Record lets you specify all the associations in advanced that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all the specified associations are loaded using minimum possible number of queries. +Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries. Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses: @@ -751,7 +751,7 @@ SELECT addresses.* FROM addresses h4. Eager Loading Multiple Associations -Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method. +Active Record lets you eager load any number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method. h5. Array of Multiple Associations @@ -767,7 +767,7 @@ h5. Nested Associations Hash Category.find(1).includes(:posts => [{:comments => :guest}, :tags]) -The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well. +This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association. h4. Specifying Conditions on Eager Loaded Associations @@ -838,7 +838,7 @@ Client.exists?(1,2,3) Client.exists?([1,2,3]) -Further more, +exists+ takes a +conditions+ option much like find: +The +exists+ method may also take a +conditions+ option much like find: Client.exists?(:conditions => "first_name = 'Ryan'") -- cgit v1.2.3 From 438bff6ccd0ba495c684a0377fd94cf302e340c5 Mon Sep 17 00:00:00 2001 From: Michael Hutchinson Date: Wed, 14 Jul 2010 03:36:55 -0700 Subject: Active Record Query Interface Guide: Corrected the explanation for the OFFSET example. --- railties/guides/source/active_record_querying.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 8c43a02c96..2e23604838 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -465,7 +465,7 @@ Or chaining both +limit+ and +offset+: Client.limit(5).offset(5) -This code will return a maximum of 5 clients and because it specifies an offset this time, it will return these records starting from the 5th client in the clients table. The SQL looks like: +This code will return a maximum of 5 clients beginning with the 6th client in the clients table, skipping the first five clients as specified by the offset. The SQL looks like: SELECT * FROM clients LIMIT 5, 5 -- cgit v1.2.3 From 684fb5e7d50801edcf20eceaa465259602871e9c Mon Sep 17 00:00:00 2001 From: Andrew Kaspick Date: Tue, 13 Jul 2010 21:59:28 -0500 Subject: upgrade prototype to be compatible with rails.js [#5109 state:committed] Signed-off-by: Xavier Noria --- .../app/templates/public/javascripts/prototype.js | 2927 ++++++++++++++------ 1 file changed, 2027 insertions(+), 900 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js index 9fe6e1243b..06249a6ae3 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js @@ -1,5 +1,5 @@ -/* Prototype JavaScript framework, version 1.6.1 - * (c) 2005-2009 Sam Stephenson +/* Prototype JavaScript framework, version 1.7_rc2 + * (c) 2005-2010 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ @@ -7,7 +7,8 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.1', + + Version: '1.7_rc2', Browser: (function(){ var ua = navigator.userAgent; @@ -17,13 +18,15 @@ var Prototype = { Opera: isOpera, WebKit: ua.indexOf('AppleWebKit/') > -1, Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, - MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + MobileSafari: /Apple.*Mobile/.test(ua) } })(), BrowserFeatures: { XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: (function() { var constructor = window.Element || window.HTMLElement; return !!(constructor && constructor.prototype); @@ -32,9 +35,9 @@ var Prototype = { if (typeof window.HTMLDivElement !== 'undefined') return true; - var div = document.createElement('div'); - var form = document.createElement('form'); - var isSupported = false; + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { isSupported = true; @@ -50,6 +53,7 @@ var Prototype = { JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, + K: function(x) { return x } }; @@ -79,6 +83,14 @@ var Try = { /* Based on Alex Arnell's inheritance implementation. */ var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + function subclass() {}; function create() { var parent = null, properties = $A(arguments); @@ -99,7 +111,7 @@ var Class = (function() { parent.subclasses.push(klass); } - for (var i = 0; i < properties.length; i++) + for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) @@ -110,10 +122,10 @@ var Class = (function() { } function addMethods(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); - if (!Object.keys({ toString: true }).length) { + if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) @@ -123,7 +135,7 @@ var Class = (function() { for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { + value.argumentNames()[0] == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; @@ -147,7 +159,35 @@ var Class = (function() { })(); (function() { - var _toString = Object.prototype.toString; + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } function extend(destination, source) { for (var property in source) @@ -166,27 +206,70 @@ var Class = (function() { } } - function toJSON(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); } - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (isElement(object)) return; + var _class = _toString.call(value); - var results = []; - for (var property in object) { - var value = toJSON(object[property]); - if (!isUndefined(value)) - results.push(property.toJSON() + ': ' + value); + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; } + } - return '{' + results.join(', ') + '}'; + function stringify(object) { + return JSON.stringify(object); } function toQueryString(object) { @@ -198,9 +281,13 @@ var Class = (function() { } function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } var results = []; - for (var property in object) - results.push(property); + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } return results; } @@ -220,9 +307,15 @@ var Class = (function() { } function isArray(object) { - return _toString.call(object) == "[object Array]"; + return _toString.call(object) === ARRAY_CLASS; } + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } function isHash(object) { return object instanceof Hash; @@ -233,11 +326,11 @@ var Class = (function() { } function isString(object) { - return _toString.call(object) == "[object String]"; + return _toString.call(object) === STRING_CLASS; } function isNumber(object) { - return _toString.call(object) == "[object Number]"; + return _toString.call(object) === NUMBER_CLASS; } function isUndefined(object) { @@ -247,10 +340,10 @@ var Class = (function() { extend(Object, { extend: extend, inspect: inspect, - toJSON: toJSON, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, toQueryString: toQueryString, toHTML: toHTML, - keys: keys, + keys: Object.keys || keys, values: values, clone: clone, isElement: isElement, @@ -311,7 +404,7 @@ Object.extend(Function.prototype, (function() { function delay(timeout) { var __method = this, args = slice.call(arguments, 1); - timeout = timeout * 1000 + timeout = timeout * 1000; return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); @@ -352,14 +445,28 @@ Object.extend(Function.prototype, (function() { })()); -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; + +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); RegExp.prototype.match = RegExp.prototype.test; @@ -418,6 +525,9 @@ Object.extend(String, { }); Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; function prepareReplacement(replacement) { if (Object.isFunction(replacement)) return replacement; @@ -484,8 +594,8 @@ Object.extend(String.prototype, (function() { } function extractScripts() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); @@ -510,8 +620,9 @@ Object.extend(String.prototype, (function() { return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); if (key in hash) { @@ -538,17 +649,9 @@ Object.extend(String.prototype, (function() { } function camelize() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - - return camelized; + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); } function capitalize() { @@ -578,10 +681,6 @@ Object.extend(String.prototype, (function() { return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } - function toJSON() { - return this.inspect(true); - } - function unfilterJSON(filter) { return this.replace(filter || Prototype.JSONFilter, '$1'); } @@ -589,29 +688,42 @@ Object.extend(String.prototype, (function() { function isJSON() { var str = this; if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); } function evalJSON(sanitize) { - var json = this.unfilterJSON(); + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); } + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } + function include(pattern) { return this.indexOf(pattern) > -1; } function startsWith(pattern) { - return this.indexOf(pattern) === 0; + return this.lastIndexOf(pattern, 0) === 0; } function endsWith(pattern) { var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; + return d >= 0 && this.indexOf(pattern, d) === d; } function empty() { @@ -631,7 +743,7 @@ Object.extend(String.prototype, (function() { sub: sub, scan: scan, truncate: truncate, - strip: String.prototype.trim ? String.prototype.trim : strip, + strip: String.prototype.trim || strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, @@ -648,10 +760,9 @@ Object.extend(String.prototype, (function() { underscore: underscore, dasherize: dasherize, inspect: inspect, - toJSON: toJSON, unfilterJSON: unfilterJSON, isJSON: isJSON, - evalJSON: evalJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, include: include, startsWith: startsWith, endsWith: endsWith, @@ -677,8 +788,9 @@ var Template = Class.create({ var before = match[1] || ''; if (before == '\\') return match[2]; - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); if (match == null) return before; @@ -943,6 +1055,7 @@ var Enumerable = (function() { find: detect }; })(); + function $A(iterable) { if (!iterable) return []; if ('toArray' in Object(iterable)) return iterable.toArray(); @@ -951,6 +1064,7 @@ function $A(iterable) { return results; } + function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); @@ -1007,7 +1121,7 @@ Array.from = $A; } function reverse(inline) { - return (inline !== false ? this : this.toArray())._reverse(); + return (inline === false ? this.toArray() : this)._reverse(); } function uniq(sorted) { @@ -1037,15 +1151,6 @@ Array.from = $A; return '[' + this.map(Object.inspect).join(', ') + ']'; } - function toJSON() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; - } - function indexOf(item, i) { i || (i = 0); var length = this.length; @@ -1094,8 +1199,7 @@ Array.from = $A; clone: clone, toArray: clone, size: size, - inspect: inspect, - toJSON: toJSON + inspect: inspect }); var CONCAT_ARGUMENTS_BUGGY = (function() { @@ -1116,6 +1220,7 @@ var Hash = Class.create(Enumerable, (function() { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); } + function _each(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; @@ -1144,6 +1249,8 @@ var Hash = Class.create(Enumerable, (function() { return Object.clone(this._object); } + + function keys() { return this.pluck('key'); } @@ -1193,10 +1300,6 @@ var Hash = Class.create(Enumerable, (function() { }).join(', ') + '}>'; } - function toJSON() { - return Object.toJSON(this.toObject()); - } - function clone() { return new Hash(this); } @@ -1216,7 +1319,7 @@ var Hash = Class.create(Enumerable, (function() { update: update, toQueryString: toQueryString, inspect: inspect, - toJSON: toJSON, + toJSON: toObject, clone: clone }; })()); @@ -1241,10 +1344,6 @@ Object.extend(Number.prototype, (function() { return '0'.times(length - string.length) + string; } - function toJSON() { - return isFinite(this) ? this.toString() : 'null'; - } - function abs() { return Math.abs(this); } @@ -1266,7 +1365,6 @@ Object.extend(Number.prototype, (function() { succ: succ, times: times, toPaddedString: toPaddedString, - toJSON: toJSON, abs: abs, round: round, ceil: ceil, @@ -1558,14 +1656,14 @@ Ajax.Response = Class.create({ var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } - if(readyState == 4) { + if (readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); @@ -1705,7 +1803,6 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { }); - function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) @@ -1730,7 +1827,7 @@ if (Prototype.BrowserFeatures.XPath) { /*--------------------------------------------------------------------------*/ -if (!window.Node) var Node = { }; +if (!Node) var Node = { }; if (!Node.ELEMENT_NODE) { Object.extend(Node, { @@ -1750,29 +1847,26 @@ if (!Node.ELEMENT_NODE) { } + (function(global) { - var SETATTRIBUTE_IGNORES_NAME = (function(){ - var elForm = document.createElement("form"); - var elInput = document.createElement("input"); - var root = document.documentElement; - elInput.setAttribute("name", "test"); - elForm.appendChild(elInput); - root.appendChild(elForm); - var isBuggy = elForm.elements - ? (typeof elForm.elements.test == "undefined") - : null; - root.removeChild(elForm); - elForm = elInput = null; - return isBuggy; + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } })(); var element = global.Element; + global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; - if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); @@ -1780,12 +1874,23 @@ if (!Node.ELEMENT_NODE) { if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; + Object.extend(global.Element, element || { }); if (element) global.Element.prototype = element.prototype; + })(this); -Element.cache = { }; Element.idCounter = 1; +Element.cache = { }; + +function purgeElement(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} Element.Methods = { visible: function(element) { @@ -1798,7 +1903,6 @@ Element.Methods = { return element; }, - hide: function(element) { element = $(element); element.style.display = 'none'; @@ -1861,6 +1965,10 @@ Element.Methods = { function update(element, content) { element = $(element); + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + if (content && content.toElement) content = content.toElement(); @@ -1967,19 +2075,26 @@ Element.Methods = { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, - recursivelyCollect: function(element, property) { + recursivelyCollect: function(element, property, maximumLength) { element = $(element); + maximumLength = maximumLength || -1; var elements = []; - while (element = element[property]) + + while (element = element[property]) { if (element.nodeType == 1) elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + return elements; }, @@ -1998,13 +2113,17 @@ Element.Methods = { }, immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; }, - previousSiblings: function(element) { + previousSiblings: function(element, maximumLength) { return Element.recursivelyCollect(element, 'previousSibling'); }, @@ -2019,9 +2138,10 @@ Element.Methods = { }, match: function(element, selector) { + element = $(element); if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); + return Prototype.Selector.match(element, selector); + return selector.match(element); }, up: function(element, expression, index) { @@ -2029,7 +2149,7 @@ Element.Methods = { if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); + Prototype.Selector.find(ancestors, expression, index); }, down: function(element, expression, index) { @@ -2041,29 +2161,40 @@ Element.Methods = { previous: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = Element.previousSiblings(element); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } }, next: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = Element.nextSiblings(element); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } }, select: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element, args); + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); }, adjacent: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element.parentNode, args).without(element); + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); }, identify: function(element) { @@ -2227,28 +2358,6 @@ Element.Methods = { return element; }, - getDimensions: function(element) { - element = $(element); - var display = Element.getStyle(element, 'display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); @@ -2295,11 +2404,13 @@ Element.Methods = { cumulativeOffset: function(element) { var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } return Element._returnOffset(valueL, valueT); }, @@ -2322,11 +2433,11 @@ Element.Methods = { element = $(element); if (Element.getStyle(element, 'position') == 'absolute') return element; - var offsets = Element.positionedOffset(element); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); @@ -2346,8 +2457,8 @@ Element.Methods = { if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; @@ -2378,9 +2489,10 @@ Element.Methods = { }, viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; + var valueT = 0, + valueL = 0, + element = forElement; - var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; @@ -2412,11 +2524,10 @@ Element.Methods = { }, arguments[2] || { }); source = $(source); - var p = Element.viewportOffset(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; element = $(element); - var delta = [0, 0]; - var parent = null; + if (Element.getStyle(element, 'position') == 'absolute') { parent = Element.getOffsetParent(element); delta = Element.viewportOffset(parent); @@ -2495,8 +2606,7 @@ else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); - try { element.offsetParent } - catch(e) { return $(document.body) } + if (!element.parentNode) return $(document.body); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); @@ -2510,8 +2620,7 @@ else if (Prototype.Browser.IE) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } + if (!element.parentNode) return Element._returnOffset(0, 0); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); var offsetParent = element.getOffsetParent(); @@ -2525,14 +2634,6 @@ else if (Prototype.Browser.IE) { ); }); - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -2576,10 +2677,9 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations = (function(){ - var classProp = 'className'; - var forProp = 'for'; - - var el = document.createElement('div'); + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); el.setAttribute(classProp, 'x'); @@ -2622,10 +2722,9 @@ else if (Prototype.Browser.IE) { }, _getEv: (function(){ - var el = document.createElement('div'); + var el = document.createElement('div'), f; el.onclick = Prototype.emptyFunction; var value = el.getAttribute('onclick'); - var f; if (String(value).indexOf('{') > -1) { f = function(element, attribute) { @@ -2753,7 +2852,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { + if (element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2793,8 +2892,8 @@ if ('outerHTML' in document.documentElement) { var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); @@ -2816,11 +2915,17 @@ Element._returnOffset = function(l, t) { }; Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } return $A(div.childNodes); }; @@ -2877,7 +2982,7 @@ Object.extend(Element, Element.Methods); div = null; -})(document.createElement('div')) +})(document.createElement('div')); Element.extend = (function() { @@ -2885,8 +2990,8 @@ Element.extend = (function() { if (typeof window.Element != 'undefined') { var proto = window.Element.prototype; if (proto) { - var id = '_' + (Math.random()+'').slice(2); - var el = document.createElement(tagName); + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); proto[id] = 'x'; var isBuggy = (el[id] !== 'x'); delete proto[id]; @@ -2953,10 +3058,14 @@ Element.extend = (function() { return extend; })(); -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; @@ -3020,8 +3129,9 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - var element = document.createElement(tagName); - var proto = element['__proto__'] || element.constructor.prototype; + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + element = null; return proto; } @@ -3104,8 +3214,8 @@ Element.addMethods({ uid = 0; } else { if (typeof element._prototypeUID === "undefined") - element._prototypeUID = [Element.Storage.UID++]; - uid = element._prototypeUID[0]; + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; } if (!Element.Storage[uid]) @@ -3150,770 +3260,1698 @@ Element.addMethods({ } } return Element.extend(clone); - } -}); -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ - -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - }, - shouldUseXPath: (function() { - - var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ - var isBuggy = false; - if (document.evaluate && window.XPathResult) { - var el = document.createElement('div'); - el.innerHTML = '
'; - - var xpath = ".//*[local-name()='ul' or local-name()='UL']" + - "//*[local-name()='li' or local-name()='LI']"; - - var result = document.evaluate(xpath, el, null, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - - isBuggy = (result.snapshotLength !== 2); - el = null; - } - return isBuggy; - })(); + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); - return function() { - if (!Prototype.BrowserFeatures.XPath) return false; + var descendants = element.getElementsByTagName('*'), + i = descendants.length; - var e = this.expression; + while (i--) purgeElement(descendants[i]); - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; + return null; + } +}); - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; +(function() { - if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } - return true; + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); } - - })(), - - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - - if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; - - if (!Selector._div) Selector._div = new Element('div'); - - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; + if (value === null) { + return null; } - return true; - }, + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m, len = ps.length, name; + if (/\d/.test(value) && element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; + return value; } - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i = 0; i"; - } -}); + var position = element.getStyle('position'), + width = element.getStyle('width'); -if (Prototype.BrowserFeatures.SelectorsAPI && - document.compatMode === 'BackCompat') { - Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ - var div = document.createElement('div'), - span = document.createElement('span'); - - div.id = "prototype_test_id"; - span.className = 'Test'; - div.appendChild(span); - var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); - div = span = null; - return isIgnored; - })(); -} + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); - }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); - }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" - }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v, len = p.length, name; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i = 0; i= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(width); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = getPixelValue(width); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); } - } - }, - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: [ - { name: 'laterSibling', re: /^\s*~\s*/ }, - { name: 'child', re: /^\s*>\s*/ }, - { name: 'adjacent', re: /^\s*\+\s*/ }, - { name: 'descendant', re: /^\s/ }, - - { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, - { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, - { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, - { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, - { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, - { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } - ], - - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; }, - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, - id: function(element, matches) { - return element.id === matches[1]; + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; }, - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); }, - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); - } - }, + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; - handlers: { - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; }, - unmark: (function(){ + inspect: function() { + return "#"; + } + }); - var PROPERTIES_ATTRIBUTES_MAP = (function(){ - var el = document.createElement('div'), - isBuggy = false, - propName = '_countedByPrototype', - value = 'x' - el[propName] = value; - isBuggy = (el.getAttribute(propName) === value); - el = null; - return isBuggy; - })(); - - return PROPERTIES_ATTRIBUTES_MAP ? - function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } : - function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = void 0; - return nodes; - } - })(), + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, + if (!this._preComputing) this._end(); - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, + return bHeight - bTop - bBottom - pTop - pBottom; + }, - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, + 'width': function(element) { + if (!this._preComputing) this._begin(); - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); - if (root == document) { - if (!targetNode) return []; - if (!nodes) return [targetNode]; - } else { - if (!root.sourceIndex || root.sourceIndex < 1) { - var nodes = root.getElementsByTagName('*'); - for (var j = 0, node; node = nodes[j]; j++) { - if (node.id === id) return [node]; - } - } + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + return element.offsetHeight; + }, + + 'border-box-width': function(element) { + return element.offsetWidth; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return Object.isNumber(element.clientTop) ? element.clientTop : + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return Object.isNumber(element.clientBottom) ? element.clientBottom : + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return Object.isNumber(element.clientLeft) ? element.clientLeft : + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return Object.isNumber(element.clientRight) ? element.clientRight : + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); } + } + }); - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, + }); + } - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; }, - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); - } - return results; + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); }, - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; + inspect: function() { + return "#".interpolate(this); }, - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; + toString: function() { + return "[#{left}, #{top}]".interpolate(this); }, - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); + toArray: function() { + return [this.left, this.top]; } - }, + }); - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, + function measure(element, property) { + return $(element).getLayout().get(property); + } - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.get('width'), + height: layout.get('height') + }; + } + + function getOffsetParent(element) { + if (isDetached(element)) return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } + } + + return $(document.body); + } + + + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); + function viewportOffset(forElement) { + var valueT = 0, valueL = 0, docBody = document.body; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; } - return results; - }, + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, + function absolutize(element) { + element = $(element); - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; } - }, - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + if (isDetached(element)) return new Element.Offset(0, 0); + + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); + } + + var eOffset = element.viewportOffset(), + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); + } }); - return expressions; - }, + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; - matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; - }, +Prototype.Selector = (function() { - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, + } - findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); } - return (l > 1) ? h.unique(results) : results; + return elements; } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; }); -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - } - }); +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
"; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } } -function $$() { - return Selector.findChildElements(document, $A(arguments)); +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } } +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + + +window.Sizzle = Sizzle; + +})(); + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + var Form = { reset: function(form) { form = $(form); @@ -4334,8 +5372,12 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; + } } function pointer(event) { @@ -4504,12 +5546,12 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { window.addEventListener('unload', Prototype.emptyFunction, false); - var _getDOMEventName = Prototype.K; + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { - var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; - return eventName in translations ? translations[eventName] : eventName; + return (translations[eventName] || eventName); }; } @@ -4543,38 +5585,29 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { element = $(element); var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; - if (Object.isUndefined(registry)) return element; - - if (eventName && !handler) { - var responders = registry.get(eventName); - - if (Object.isUndefined(responders)) return element; - - responders.each( function(r) { - Element.stopObserving(element, eventName, r.handler); - }); - return element; - } else if (!eventName) { + if (!eventName) { registry.each( function(pair) { - var eventName = pair.key, responders = pair.value; - - responders.each( function(r) { - Element.stopObserving(element, eventName, r.handler); - }); + var eventName = pair.key; + stopObserving(element, eventName); }); return element; } var responders = registry.get(eventName); + if (!responders) return element; - if (!responders) return; + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } var responder = responders.find( function(r) { return r.handler === handler; }); if (!responder) return element; - var actualEventName = _getDOMEventName(eventName); - if (eventName.include(':')) { if (element.removeEventListener) element.removeEventListener("dataavailable", responder, false); @@ -4583,6 +5616,7 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { element.detachEvent("onfilterchange", responder); } } else { + var actualEventName = _getDOMEventName(eventName); if (element.removeEventListener) element.removeEventListener(actualEventName, responder, false); else @@ -4623,13 +5657,47 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { return Event.extend(event); } + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = event.findElement(this.selector); + if (element) this.callback.call(this.element, event, element); + } + }); + + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } Object.extend(Event, Event.Methods); Object.extend(Event, { fire: fire, observe: observe, - stopObserving: stopObserving + stopObserving: stopObserving, + on: on }); Element.addMethods({ @@ -4637,7 +5705,9 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { observe: observe, - stopObserving: stopObserving + stopObserving: stopObserving, + + on: on }); Object.extend(document, { @@ -4647,6 +5717,8 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { stopObserving: stopObserving.methodize(), + on: on.methodize(), + loaded: false }); @@ -4872,3 +5944,58 @@ Element.ClassNames.prototype = { Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ + +(function() { + window.Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, + + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); -- cgit v1.2.3 From 59945678393b591e263cdee90a8e278a723f93df Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Wed, 14 Jul 2010 13:17:01 +0200 Subject: AR queying guide: let limit and offset be different numbers to help making clear what is what in the explanation, rewords also a bit --- railties/guides/source/active_record_querying.textile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 2e23604838..5c4ed3a803 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -447,28 +447,28 @@ h4. Limit and Offset To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +limit+ and +offset+ methods on the relation. -You can use +limit+ to specify the number of records to be retrieved, and use +offset+ to specify the number of records to skip before starting to return the records. For example: +You can use +limit+ to specify the number of records to be retrieved, and use +offset+ to specify the number of records to skip before starting to return the records. For example Client.limit(5) -This code will return a maximum of 5 clients and because it specifies no offset it will return the first 5 clients in the table. The SQL it executes will look like this: +will return a maximum of 5 clients and because it specifies no offset it will return the first 5 in the table. The SQL it executes looks like this: SELECT * FROM clients LIMIT 5 -Or chaining both +limit+ and +offset+: +Adding +offset+ to that -Client.limit(5).offset(5) +Client.limit(5).offset(30) -This code will return a maximum of 5 clients beginning with the 6th client in the clients table, skipping the first five clients as specified by the offset. The SQL looks like: +will return instead a maximum of 5 clients beginning with the 31st. The SQL looks like: -SELECT * FROM clients LIMIT 5, 5 +SELECT * FROM clients LIMIT 5, 30 h4. Group -- cgit v1.2.3 From b7944e1b211c37fd9db8d72bef9f12c42b68a0a4 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Wed, 14 Jul 2010 17:42:48 +0200 Subject: revises the rdoc of update_attributes and update_attributes! to document they are wrapped in a transaction, and adds code comments explaining why --- activerecord/lib/active_record/persistence.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 3681a63e03..7ec443ccc7 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -118,18 +118,23 @@ module ActiveRecord self.class.update_all(changes, { primary_key => self[primary_key] }) == 1 end - # Updates all the attributes from the passed-in Hash and saves the record. - # If the object is invalid, the saving will fail and false will be returned. + # Updates the attributes of the model from the passed-in hash and saves the + # record, all wrapped in a transaction. If the object is invalid, the saving + # will fail and false will be returned. def update_attributes(attributes) + # The following transaction covers any possible database side-effects of the + # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do self.attributes = attributes save end end - # Updates an object just like Base.update_attributes but calls save! instead - # of save so an exception is raised if the record is invalid. + # Updates its receiver just like +update_attributes+ but calls save! instead + # of +save+, so an exception is raised if the record is invalid. def update_attributes!(attributes) + # The following transaction covers any possible database side-effects of the + # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do self.attributes = attributes save! -- cgit v1.2.3 From 4a06489525809755bcb8fd4c05394dbfb900f237 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 14 Jul 2010 16:50:18 +0100 Subject: Style fixes --- activerecord/lib/active_record/base.rb | 2 +- .../lib/active_record/relation/query_methods.rb | 40 +++++++++++----------- activerecord/test/cases/base_test.rb | 4 +-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 400a0adbcf..19ccf75b00 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1471,7 +1471,7 @@ MSG # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) - return unless new_attributes.is_a? Hash + return unless new_attributes.is_a?(Hash) attributes = new_attributes.stringify_keys multi_parameter_attributes = [] diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4692271266..0593897fa5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -11,84 +11,84 @@ module ActiveRecord def includes(*args) args.reject! { |a| a.blank? } - clone.tap { |r| r.includes_values += args if args.present? } + clone.tap {|r| r.includes_values += args if args.present? } end def eager_load(*args) - clone.tap { |r| r.eager_load_values += args if args.present? } + clone.tap {|r| r.eager_load_values += args if args.present? } end def preload(*args) - clone.tap { |r| r.preload_values += args if args.present? } + clone.tap {|r| r.preload_values += args if args.present? } end def select(*args) if block_given? - to_a.select { |*block_args| yield(*block_args) } + to_a.select {|*block_args| yield(*block_args) } else - clone.tap { |r| r.select_values += args if args.present? } + clone.tap {|r| r.select_values += args if args.present? } end end def group(*args) - clone.tap { |r| r.group_values += args if args.present? } + clone.tap {|r| r.group_values += args if args.present? } end def order(*args) - clone.tap { |r| r.order_values += args if args.present? } + clone.tap {|r| r.order_values += args if args.present? } end def reorder(*args) - clone.tap { |r| r.order_values = args if args.present? } + clone.tap {|r| r.order_values = args if args.present? } end def joins(*args) args.flatten! - clone.tap { |r| r.joins_values += args if args.present? } + clone.tap {|r| r.joins_values += args if args.present? } end def where(*args) value = build_where(*args) - clone.tap { |r| r.where_values += Array.wrap(value) if value.present? } + clone.tap {|r| r.where_values += Array.wrap(value) if value.present? } end def having(*args) value = build_where(*args) - clone.tap { |r| r.having_values += Array.wrap(value) if value.present? } + clone.tap {|r| r.having_values += Array.wrap(value) if value.present? } end def limit(value = true) - clone.tap { |r| r.limit_value = value } + clone.tap {|r| r.limit_value = value } end def offset(value = true) - clone.tap { |r| r.offset_value = value } + clone.tap {|r| r.offset_value = value } end def lock(locks = true) case locks when String, TrueClass, NilClass - clone.tap { |r| r.lock_value = locks || true } + clone.tap {|r| r.lock_value = locks || true } else - clone.tap { |r| r.lock_value = false } + clone.tap {|r| r.lock_value = false } end end def readonly(value = true) - clone.tap { |r| r.readonly_value = value } + clone.tap {|r| r.readonly_value = value } end def create_with(value = true) - clone.tap { |r| r.create_with_value = value } + clone.tap {|r| r.create_with_value = value } end def from(value = true) - clone.tap { |r| r.from_value = value } + clone.tap {|r| r.from_value = value } end def extending(*modules, &block) modules << Module.new(&block) if block_given? - clone.tap { |r| r.send(:apply_modules, *modules) } + clone.tap {|r| r.send(:apply_modules, *modules) } end def reverse_order @@ -230,7 +230,7 @@ module ActiveRecord @implicit_readonly = false # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array. # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array - if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) + if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) arel.project(*selects) else arel.project(selects.last) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index a4cf5120e1..87f46d2992 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -63,9 +63,7 @@ class BasicsTest < ActiveRecord::TestCase def test_set_attributes_without_hash topic = Topic.new - assert_nothing_raised do - topic.attributes = '' - end + assert_nothing_raised { topic.attributes = '' } end def test_integers_as_nil -- cgit v1.2.3 From d5921cdb7a00b393d0ba5f2f795da60a33b11cf1 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 14 Jul 2010 18:32:24 +0100 Subject: Remove unintentional API changes. [#1108] --- .../associations/association_collection.rb | 13 ++---- .../lib/active_record/dynamic_finder_match.rb | 4 ++ .../associations/has_many_associations_test.rb | 50 ++++------------------ 3 files changed, 17 insertions(+), 50 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1c3a6b56f3..f4e34657a5 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -418,15 +418,10 @@ module ActiveRecord end def method_missing(method, *args) - case method.to_s - when 'find_or_create' - return find(:first, :conditions => args.first) || create(args.first) - when /^find_or_create_by_(.*)$/ - rest = $1 - return send("find_by_#{rest}", *args) || - method_missing("create_by_#{rest}", *args) - when /^create_by_(.*)$/ - return create Hash[$1.split('_and_').zip(args)] + match = DynamicFinderMatch.match(method) + if match && match.creator? + attributes = match.attribute_names + return send(:"find_by_#{attributes.join('and')}", *args) || create(Hash[attributes.zip(args)]) end if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index b39b291352..dfb8a3ba60 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -42,6 +42,10 @@ module ActiveRecord @finder == :first && !@instantiator.nil? end + def creator? + @finder == :first && @instantiator == :create + end + def bang? @bang end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6218cdd344..6fe737a817 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -47,14 +47,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.destroyed_client_ids.clear end - def test_create_by - person = Person.create! :first_name => 'tenderlove' - post = Post.find :first + def test_create_resets_cached_counters + person = Person.create!(:first_name => 'tenderlove') + post = Post.first assert_equal [], person.readers - assert_nil person.readers.find_by_post_id post.id + assert_nil person.readers.find_by_post_id(post.id) - reader = person.readers.create_by_post_id post.id + reader = person.readers.create(:post_id => post.id) assert_equal 1, person.readers.count assert_equal 1, person.readers.length @@ -62,13 +62,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal person, person.readers.first.person end - def test_create_by_multi + def test_find_or_create_by_resets_cached_counters person = Person.create! :first_name => 'tenderlove' - post = Post.find :first + post = Post.first assert_equal [], person.readers + assert_nil person.readers.find_by_post_id(post.id) - reader = person.readers.create_by_post_id_and_skimmer post.id, false + reader = person.readers.find_or_create_by_post_id(post.id) assert_equal 1, person.readers.count assert_equal 1, person.readers.length @@ -76,39 +77,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal person, person.readers.first.person end - def test_find_or_create_by - person = Person.create! :first_name => 'tenderlove' - post = Post.find :first - - assert_equal [], person.readers - assert_nil person.readers.find_by_post_id post.id - - reader = person.readers.find_or_create_by_post_id post.id - - assert_equal 1, person.readers.count - assert_equal 1, person.readers.length - assert_equal post, person.readers.first.post - assert_equal person, person.readers.first.person - end - - def test_find_or_create - person = Person.create! :first_name => 'tenderlove' - post = Post.find :first - - assert_equal [], person.readers - assert_nil person.readers.find(:first, :conditions => { - :post_id => post.id - }) - - reader = person.readers.find_or_create :post_id => post.id - - assert_equal 1, person.readers.count - assert_equal 1, person.readers.length - assert_equal post, person.readers.first.post - assert_equal person, person.readers.first.person - end - - def force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.each {|f| } end -- cgit v1.2.3 From 36150c902b3253101aaa2e14ed31d8d8b9a2e0d2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 15:04:14 -0700 Subject: Let's initialize instance variables in the postgres adapter. --- .../connection_adapters/postgresql_adapter.rb | 3 +++ .../adapters/postgresql/postgresql_adapter_test.rb | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e213eae026..e9443fc237 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -218,6 +218,9 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil + @table_alias_length = nil + @postgresql_version = nil + connect @local_tz = execute('SHOW TIME ZONE').first["TimeZone"] end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb new file mode 100644 index 0000000000..7b72151b57 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -0,0 +1,17 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapterTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def test_table_alias_length + assert_nothing_raised do + @connection.table_alias_length + end + end + end + end +end -- cgit v1.2.3 From 51c9b666e634e2d9551410cbc393bfd83bba7856 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 15:10:45 -0700 Subject: PostgreSQLAdapter#query bail early if there is no column and dry up our conditionals --- .../active_record/connection_adapters/postgresql_adapter.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e9443fc237..cf0dc64d2b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -311,14 +311,16 @@ module ActiveRecord # Quotes PostgreSQL-specific data types for SQL input. def quote(value, column = nil) #:nodoc: - if value.kind_of?(String) && column && column.type == :binary + return super unless column + + if value.kind_of?(String) && column.type == :binary "'#{escape_bytea(value)}'" - elsif value.kind_of?(String) && column && column.sql_type == 'xml' + elsif value.kind_of?(String) && column.sql_type == 'xml' "xml '#{quote_string(value)}'" - elsif value.kind_of?(Numeric) && column && column.sql_type == 'money' + elsif value.kind_of?(Numeric) && column.sql_type == 'money' # Not truly string input, so doesn't require (or allow) escape string syntax. "'#{value.to_s}'" - elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/ + elsif value.kind_of?(String) && column.sql_type =~ /^bit/ case value when /^[01]*$/ "B'#{value}'" # Bit-string notation -- cgit v1.2.3 From 8ba14c238e62e5f34846b4f82c5b787e33161511 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 15:18:48 -0700 Subject: PostgreSQLAdapter#query string interpolation calls to_s for us --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index cf0dc64d2b..121785bc15 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -319,7 +319,7 @@ module ActiveRecord "xml '#{quote_string(value)}'" elsif value.kind_of?(Numeric) && column.sql_type == 'money' # Not truly string input, so doesn't require (or allow) escape string syntax. - "'#{value.to_s}'" + "'#{value}'" elsif value.kind_of?(String) && column.sql_type =~ /^bit/ case value when /^[01]*$/ -- cgit v1.2.3 From 6a534455f66a24e807d75dccc847981fdf43fa18 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 15:44:15 -0700 Subject: remove useless ternary in PostgreSQL adapter --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 121785bc15..37e94502a4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -375,7 +375,7 @@ module ActiveRecord def supports_disable_referential_integrity?() #:nodoc: version = query("SHOW server_version")[0][0].split('.') - (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false + version[0].to_i >= 8 && version[1].to_i >= 1 rescue return false end -- cgit v1.2.3 From 1db8ed0f4b134dfe6d1f3f3c440a536b2f7d2824 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 16:35:52 -0700 Subject: activerecord tests should inherit from ActiveRecord::TestCase --- activerecord/test/cases/adapters/postgresql/active_schema_test.rb | 2 +- activerecord/test/cases/i18n_test.rb | 2 +- activerecord/test/cases/invalid_date_test.rb | 2 +- activerecord/test/cases/log_subscriber_test.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index f106e14319..e4746d4aa3 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -1,6 +1,6 @@ require 'cases/helper' -class PostgresqlActiveSchemaTest < Test::Unit::TestCase +class PostgresqlActiveSchemaTest < ActiveRecord::TestCase def setup ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do alias_method :real_execute, :execute diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index ae4dcfb81e..3287626378 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'models/topic' require 'models/reply' -class ActiveRecordI18nTests < Test::Unit::TestCase +class ActiveRecordI18nTests < ActiveRecord::TestCase def setup I18n.backend = I18n::Backend::Simple.new diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index 99af7d2986..2de50b224c 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -1,7 +1,7 @@ require 'cases/helper' require 'models/topic' -class InvalidDateTest < Test::Unit::TestCase +class InvalidDateTest < ActiveRecord::TestCase def test_assign_valid_dates valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]] diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 4aeae1fe45..cde383783b 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require "models/developer" require "active_support/log_subscriber/test_helper" -class LogSubscriberTest < ActiveSupport::TestCase +class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper def setup -- cgit v1.2.3 From d41a28bcca521222594a70d237935a4415a85230 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 17:09:14 -0700 Subject: use array intersection for dry / faster code --- activerecord/lib/active_record/fixtures.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 82270c56b3..a2328663fd 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -669,9 +669,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) end def timestamp_column_names - @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name| - column_names.include?(name) - end + @timestamp_column_names ||= + %w(created_at created_on updated_at updated_on) & column_names end def inheritance_column_name -- cgit v1.2.3 From 955b26ac6e729d7197f9c5134ad7d438c08293c0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 17:11:52 -0700 Subject: primary_key_name already checks for the model_class --- activerecord/lib/active_record/fixtures.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index a2328663fd..5bf43b3a72 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -664,8 +664,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) end def has_primary_key_column? - @has_primary_key_column ||= model_class && primary_key_name && - model_class.columns.find { |c| c.name == primary_key_name } + @has_primary_key_column ||= primary_key_name && + model_class.columns.any? { |c| c.name == primary_key_name } end def timestamp_column_names -- cgit v1.2.3 From 3c300b31219f2af3ecd46ef22b04e5c5548db899 Mon Sep 17 00:00:00 2001 From: wycats Date: Wed, 14 Jul 2010 17:33:07 -0700 Subject: Style fixes --- activerecord/lib/active_record/base.rb | 2 +- .../lib/active_record/relation/query_methods.rb | 40 +++++++++++----------- activerecord/test/cases/base_test.rb | 4 ++- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 19ccf75b00..400a0adbcf 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1471,7 +1471,7 @@ MSG # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) - return unless new_attributes.is_a?(Hash) + return unless new_attributes.is_a? Hash attributes = new_attributes.stringify_keys multi_parameter_attributes = [] diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0593897fa5..4692271266 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -11,84 +11,84 @@ module ActiveRecord def includes(*args) args.reject! { |a| a.blank? } - clone.tap {|r| r.includes_values += args if args.present? } + clone.tap { |r| r.includes_values += args if args.present? } end def eager_load(*args) - clone.tap {|r| r.eager_load_values += args if args.present? } + clone.tap { |r| r.eager_load_values += args if args.present? } end def preload(*args) - clone.tap {|r| r.preload_values += args if args.present? } + clone.tap { |r| r.preload_values += args if args.present? } end def select(*args) if block_given? - to_a.select {|*block_args| yield(*block_args) } + to_a.select { |*block_args| yield(*block_args) } else - clone.tap {|r| r.select_values += args if args.present? } + clone.tap { |r| r.select_values += args if args.present? } end end def group(*args) - clone.tap {|r| r.group_values += args if args.present? } + clone.tap { |r| r.group_values += args if args.present? } end def order(*args) - clone.tap {|r| r.order_values += args if args.present? } + clone.tap { |r| r.order_values += args if args.present? } end def reorder(*args) - clone.tap {|r| r.order_values = args if args.present? } + clone.tap { |r| r.order_values = args if args.present? } end def joins(*args) args.flatten! - clone.tap {|r| r.joins_values += args if args.present? } + clone.tap { |r| r.joins_values += args if args.present? } end def where(*args) value = build_where(*args) - clone.tap {|r| r.where_values += Array.wrap(value) if value.present? } + clone.tap { |r| r.where_values += Array.wrap(value) if value.present? } end def having(*args) value = build_where(*args) - clone.tap {|r| r.having_values += Array.wrap(value) if value.present? } + clone.tap { |r| r.having_values += Array.wrap(value) if value.present? } end def limit(value = true) - clone.tap {|r| r.limit_value = value } + clone.tap { |r| r.limit_value = value } end def offset(value = true) - clone.tap {|r| r.offset_value = value } + clone.tap { |r| r.offset_value = value } end def lock(locks = true) case locks when String, TrueClass, NilClass - clone.tap {|r| r.lock_value = locks || true } + clone.tap { |r| r.lock_value = locks || true } else - clone.tap {|r| r.lock_value = false } + clone.tap { |r| r.lock_value = false } end end def readonly(value = true) - clone.tap {|r| r.readonly_value = value } + clone.tap { |r| r.readonly_value = value } end def create_with(value = true) - clone.tap {|r| r.create_with_value = value } + clone.tap { |r| r.create_with_value = value } end def from(value = true) - clone.tap {|r| r.from_value = value } + clone.tap { |r| r.from_value = value } end def extending(*modules, &block) modules << Module.new(&block) if block_given? - clone.tap {|r| r.send(:apply_modules, *modules) } + clone.tap { |r| r.send(:apply_modules, *modules) } end def reverse_order @@ -230,7 +230,7 @@ module ActiveRecord @implicit_readonly = false # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array. # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array - if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) + if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) arel.project(*selects) else arel.project(selects.last) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 87f46d2992..a4cf5120e1 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -63,7 +63,9 @@ class BasicsTest < ActiveRecord::TestCase def test_set_attributes_without_hash topic = Topic.new - assert_nothing_raised { topic.attributes = '' } + assert_nothing_raised do + topic.attributes = '' + end end def test_integers_as_nil -- cgit v1.2.3 From 62dd1d8d23c843fa4839e47b9e67d84c15a61f35 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Jul 2010 17:53:03 -0700 Subject: clean up ||= with an initialized variable and attr_reader --- activerecord/lib/active_record/associations.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 65daa8ffbe..9fe8d54f02 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1965,7 +1965,7 @@ module ActiveRecord end class JoinAssociation < JoinBase # :nodoc: - attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name + attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection def initialize(reflection, join_dependency, parent = nil) @@ -1982,6 +1982,7 @@ module ActiveRecord @parent_table_name = parent.active_record.table_name @aliased_table_name = aliased_table_name_for(table_name) @join = nil + @join_class = Arel::InnerJoin if reflection.macro == :has_and_belongs_to_many @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join") @@ -2004,10 +2005,6 @@ module ActiveRecord end end - def join_class - @join_class ||= Arel::InnerJoin - end - def with_join_class(join_class) @join_class = join_class self -- cgit v1.2.3 From 897f86a974636581b8409fd87b288581e2533c28 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 15 Jul 2010 09:47:00 +0200 Subject: layouts and rendering guide: use content_for? instead of the yield or yield idiom --- railties/guides/source/layouts_and_rendering.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index f4ba6dd53b..b9a201e5f0 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -1168,7 +1168,7 @@ Suppose you have the following +ApplicationController+ layout:
Top menu items here
-
<%= yield(:content) or yield %>
+
<%= content_for?(:content) ? yield(:content) : yield %>
-- cgit v1.2.3 From 0c2c8934345ec8a3f2eb7896e252496167fdf652 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 15 Jul 2010 13:17:35 +0100 Subject: Revert "Style fixes" This reverts commit 3c300b31219f2af3ecd46ef22b04e5c5548db899. --- activerecord/lib/active_record/base.rb | 2 +- .../lib/active_record/relation/query_methods.rb | 40 +++++++++++----------- activerecord/test/cases/base_test.rb | 4 +-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 400a0adbcf..19ccf75b00 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1471,7 +1471,7 @@ MSG # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) - return unless new_attributes.is_a? Hash + return unless new_attributes.is_a?(Hash) attributes = new_attributes.stringify_keys multi_parameter_attributes = [] diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4692271266..0593897fa5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -11,84 +11,84 @@ module ActiveRecord def includes(*args) args.reject! { |a| a.blank? } - clone.tap { |r| r.includes_values += args if args.present? } + clone.tap {|r| r.includes_values += args if args.present? } end def eager_load(*args) - clone.tap { |r| r.eager_load_values += args if args.present? } + clone.tap {|r| r.eager_load_values += args if args.present? } end def preload(*args) - clone.tap { |r| r.preload_values += args if args.present? } + clone.tap {|r| r.preload_values += args if args.present? } end def select(*args) if block_given? - to_a.select { |*block_args| yield(*block_args) } + to_a.select {|*block_args| yield(*block_args) } else - clone.tap { |r| r.select_values += args if args.present? } + clone.tap {|r| r.select_values += args if args.present? } end end def group(*args) - clone.tap { |r| r.group_values += args if args.present? } + clone.tap {|r| r.group_values += args if args.present? } end def order(*args) - clone.tap { |r| r.order_values += args if args.present? } + clone.tap {|r| r.order_values += args if args.present? } end def reorder(*args) - clone.tap { |r| r.order_values = args if args.present? } + clone.tap {|r| r.order_values = args if args.present? } end def joins(*args) args.flatten! - clone.tap { |r| r.joins_values += args if args.present? } + clone.tap {|r| r.joins_values += args if args.present? } end def where(*args) value = build_where(*args) - clone.tap { |r| r.where_values += Array.wrap(value) if value.present? } + clone.tap {|r| r.where_values += Array.wrap(value) if value.present? } end def having(*args) value = build_where(*args) - clone.tap { |r| r.having_values += Array.wrap(value) if value.present? } + clone.tap {|r| r.having_values += Array.wrap(value) if value.present? } end def limit(value = true) - clone.tap { |r| r.limit_value = value } + clone.tap {|r| r.limit_value = value } end def offset(value = true) - clone.tap { |r| r.offset_value = value } + clone.tap {|r| r.offset_value = value } end def lock(locks = true) case locks when String, TrueClass, NilClass - clone.tap { |r| r.lock_value = locks || true } + clone.tap {|r| r.lock_value = locks || true } else - clone.tap { |r| r.lock_value = false } + clone.tap {|r| r.lock_value = false } end end def readonly(value = true) - clone.tap { |r| r.readonly_value = value } + clone.tap {|r| r.readonly_value = value } end def create_with(value = true) - clone.tap { |r| r.create_with_value = value } + clone.tap {|r| r.create_with_value = value } end def from(value = true) - clone.tap { |r| r.from_value = value } + clone.tap {|r| r.from_value = value } end def extending(*modules, &block) modules << Module.new(&block) if block_given? - clone.tap { |r| r.send(:apply_modules, *modules) } + clone.tap {|r| r.send(:apply_modules, *modules) } end def reverse_order @@ -230,7 +230,7 @@ module ActiveRecord @implicit_readonly = false # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array. # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array - if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) + if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) arel.project(*selects) else arel.project(selects.last) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index a4cf5120e1..87f46d2992 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -63,9 +63,7 @@ class BasicsTest < ActiveRecord::TestCase def test_set_attributes_without_hash topic = Topic.new - assert_nothing_raised do - topic.attributes = '' - end + assert_nothing_raised { topic.attributes = '' } end def test_integers_as_nil -- cgit v1.2.3 From 5543e8bd6bdc29cfd2e809bdf267b720310af5ec Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Thu, 15 Jul 2010 17:42:41 +0200 Subject: Migrations guide: minor typos corrected --- railties/guides/source/migrations.textile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index f05c0589b5..16f616a5bc 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -1,6 +1,6 @@ h2. Migrations -Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run it. You'd also have to keep track of which changes need to be run against the production machines next time you deploy. +Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them. You'd also have to keep track of which changes need to be run against the production machines next time you deploy. Active Record tracks which migrations have already been run so all you have to do is update your source and run +rake db:migrate+. Active Record will work out which migrations should be run. It will also update your +db/schema.rb+ file to match the structure of your database. @@ -234,7 +234,7 @@ end which creates a +products+ table with a column called +name+ (and as discussed below, an implicit +id+ column). -The object yielded to the block allows you create columns on the table. There are two ways of doing this: The first (traditional) form looks like +The object yielded to the block allows you to create columns on the table. There are two ways of doing this: The first (traditional) form looks like create_table :products do |t| @@ -250,7 +250,7 @@ create_table :products do |t| end -By default +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass +:id => false+. If you need to pass database specific options you can place an SQL fragment in the +:options+ option. For example +By default +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass +:id => false+. If you need to pass database specific options you can place a SQL fragment in the +:options+ option. For example create_table :products, :options => "ENGINE=BLACKHOLE" do |t| @@ -592,4 +592,5 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/6 +* July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com * September 14, 2008: initial version by "Frederick Cheung":credits.html#fcheung -- cgit v1.2.3 From 3952268929d72b691c1db925c5e9bf7dff8962ce Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 15 Jul 2010 16:18:25 -0300 Subject: Bump up to rack-mount 0.6.9 and rack-mount-0.6.6.pre removed from action_dispatch vendor --- Gemfile | 1 - actionpack/actionpack.gemspec | 2 +- .../lib/action_dispatch/routing/route_set.rb | 4 +- .../vendor/rack-mount-0.6.6.pre/rack/mount.rb | 32 -- .../rack/mount/analysis/frequency.rb | 60 --- .../rack/mount/analysis/histogram.rb | 74 --- .../rack/mount/analysis/splitting.rb | 159 ------ .../rack/mount/code_generation.rb | 113 ---- .../rack/mount/generatable_regexp.rb | 210 -------- .../rack-mount-0.6.6.pre/rack/mount/multimap.rb | 53 -- .../rack-mount-0.6.6.pre/rack/mount/prefix.rb | 36 -- .../rack/mount/regexp_with_named_groups.rb | 69 --- .../rack-mount-0.6.6.pre/rack/mount/route.rb | 130 ----- .../rack-mount-0.6.6.pre/rack/mount/route_set.rb | 409 --------------- .../rack-mount-0.6.6.pre/rack/mount/strexp.rb | 68 --- .../rack/mount/strexp/parser.rb | 160 ------ .../rack/mount/strexp/parser.y | 34 -- .../rack/mount/strexp/tokenizer.rb | 83 --- .../rack/mount/strexp/tokenizer.rex | 12 - .../rack-mount-0.6.6.pre/rack/mount/utils.rb | 148 ------ .../rack/mount/vendor/multimap/multimap.rb | 569 --------------------- .../rack/mount/vendor/multimap/multiset.rb | 185 ------- .../rack/mount/vendor/multimap/nested_multimap.rb | 158 ------ .../rack/mount/vendor/regin/regin.rb | 45 -- .../rack/mount/vendor/regin/regin/alternation.rb | 40 -- .../rack/mount/vendor/regin/regin/anchor.rb | 4 - .../rack/mount/vendor/regin/regin/atom.rb | 59 --- .../rack/mount/vendor/regin/regin/character.rb | 56 -- .../mount/vendor/regin/regin/character_class.rb | 55 -- .../rack/mount/vendor/regin/regin/collection.rb | 83 --- .../rack/mount/vendor/regin/regin/expression.rb | 126 ----- .../rack/mount/vendor/regin/regin/group.rb | 90 ---- .../rack/mount/vendor/regin/regin/options.rb | 55 -- .../rack/mount/vendor/regin/regin/parser.rb | 415 --------------- .../rack/mount/vendor/regin/regin/tokenizer.rb | 213 -------- .../rack/mount/vendor/regin/regin/version.rb | 3 - .../rack-mount-0.6.6.pre/rack/mount/version.rb | 5 - railties/guides/source/initialization.textile | 2 +- 38 files changed, 3 insertions(+), 4017 deletions(-) delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb delete mode 100644 actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb diff --git a/Gemfile b/Gemfile index 827c67e522..fe6ef9aa8a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,6 @@ source 'http://rubygems.org' gem "arel", :git => "git://github.com/rails/arel.git" -#gem "rack-mount", :git => "git://github.com/rails/rack-mount.git" gem "rails", :path => File.dirname(__FILE__) gem "rake", ">= 0.8.7" diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index df0b5ac9a2..3531906341 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.4.1') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.4') - #s.add_dependency('rack-mount', '~> 0.6.6') + s.add_dependency('rack-mount', '~> 0.6.9') s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('erubis', '~> 2.6.6') end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 1b1a221c60..36c52eb65a 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,10 +1,8 @@ +require 'rack/mount' require 'forwardable' require 'active_support/core_ext/object/to_query' require 'action_dispatch/routing/deprecated_mapper' -$: << File.expand_path('../../vendor/rack-mount-0.6.6.pre', __FILE__) -require 'rack/mount' - module ActionDispatch module Routing class RouteSet #:nodoc: diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb deleted file mode 100644 index 9fbf707724..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'rack' - -module Rack #:nodoc: - # A stackable dynamic tree based Rack router. - # - # Rack::Mount supports Rack's Cascade style of trying several routes until - # it finds one that is not a 404. This allows multiple routes to be nested - # or stacked on top of each other. Since the application endpoint can - # trigger the router to continue matching, middleware can be used to add - # arbitrary conditions to any route. This allows you to route based on - # other request attributes, session information, or even data dynamically - # pulled from a database. - module Mount - autoload :CodeGeneration, 'rack/mount/code_generation' - autoload :GeneratableRegexp, 'rack/mount/generatable_regexp' - autoload :Multimap, 'rack/mount/multimap' - autoload :Prefix, 'rack/mount/prefix' - autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups' - autoload :Route, 'rack/mount/route' - autoload :RouteSet, 'rack/mount/route_set' - autoload :RoutingError, 'rack/mount/route_set' - autoload :Strexp, 'rack/mount/strexp' - autoload :Utils, 'rack/mount/utils' - autoload :Version, 'rack/mount/version' - - module Analysis #:nodoc: - autoload :Frequency, 'rack/mount/analysis/frequency' - autoload :Histogram, 'rack/mount/analysis/histogram' - autoload :Splitting, 'rack/mount/analysis/splitting' - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb deleted file mode 100644 index 671258f807..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb +++ /dev/null @@ -1,60 +0,0 @@ -require 'rack/mount/utils' - -module Rack::Mount - module Analysis - class Frequency #:nodoc: - def initialize(*keys) - clear - keys.each { |key| self << key } - end - - def clear - @raw_keys = [] - @key_frequency = Analysis::Histogram.new - self - end - - def <<(key) - raise ArgumentError unless key.is_a?(Hash) - @raw_keys << key - nil - end - - def possible_keys - @possible_keys ||= begin - @raw_keys.map do |key| - key.inject({}) { |requirements, (method, requirement)| - process_key(requirements, method, requirement) - requirements - } - end - end - end - - def process_key(requirements, method, requirement) - if requirement.is_a?(Regexp) - expression = Utils.parse_regexp(requirement) - - if expression.is_a?(Regin::Expression) && expression.anchored_to_line? - expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) }) - return requirements[method] = expression.to_s if expression.literal? - end - end - - requirements[method] = requirement - end - - def report - @report ||= begin - possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } } - return [] if @key_frequency.count <= 1 - @key_frequency.keys_in_upper_quartile - end - end - - def expire! - @possible_keys = @report = nil - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb deleted file mode 100644 index 20aaa132f9..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Rack::Mount - module Analysis - class Histogram < Hash #:nodoc: - attr_reader :count - - def initialize - @count = 0 - super(0) - expire_caches! - end - - def <<(value) - @count += 1 - self[value] += 1 if value - expire_caches! - self - end - - def sorted_by_frequency - sort_by { |_, value| value }.reverse! - end - - def max - @max ||= values.max || 0 - end - - def min - @min ||= values.min || 0 - end - - def mean - @mean ||= calculate_mean - end - - def standard_deviation - @standard_deviation ||= calculate_standard_deviation - end - - def upper_quartile_limit - @upper_quartile_limit ||= calculate_upper_quartile_limit - end - - def keys_in_upper_quartile - @keys_in_upper_quartile ||= compute_keys_in_upper_quartile - end - - private - def calculate_mean - count / size - end - - def calculate_variance - values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / count.to_f - end - - def calculate_standard_deviation - Math.sqrt(calculate_variance) - end - - def calculate_upper_quartile_limit - mean + standard_deviation - end - - def compute_keys_in_upper_quartile - sorted_by_frequency.select { |_, value| value >= upper_quartile_limit }.map! { |key, _| key } - end - - def expire_caches! - @max = @min = @mean = @standard_deviation = nil - @keys_in_upper_quartile = nil - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb deleted file mode 100644 index 8a8c551302..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb +++ /dev/null @@ -1,159 +0,0 @@ -require 'rack/mount/utils' - -module Rack::Mount - module Analysis - class Splitting < Frequency - NULL = "\0".freeze - - class Key < Struct.new(:method, :index, :separators) - def self.split(value, separator_pattern) - keys = value.split(separator_pattern) - keys.shift if keys[0] == '' - keys << NULL - keys - end - - def call(cache, obj) - (cache[method] ||= self.class.split(obj.send(method), separators))[index] - end - - def call_source(cache, obj) - "(#{cache}[:#{method}] ||= Analysis::Splitting::Key.split(#{obj}.#{method}, #{separators.inspect}))[#{index}]" - end - - def inspect - "#{method}[#{index}]" - end - end - - def clear - @boundaries = {} - super - end - - def <<(key) - super - key.each_pair do |k, v| - analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new) - end - end - - def separators(key) - (@boundaries[key].keys_in_upper_quartile + ['/']).uniq - end - - def process_key(requirements, method, requirement) - separators = separators(method) - if requirement.is_a?(Regexp) && separators.any? - generate_split_keys(requirement, separators).each_with_index do |value, index| - requirements[Key.new(method, index, Regexp.union(*separators))] = value - end - else - super - end - end - - private - def analyze_capture_boundaries(regexp, boundaries) #:nodoc: - return boundaries unless regexp.is_a?(Regexp) - - parts = Utils.parse_regexp(regexp) - parts.each_with_index do |part, index| - if part.is_a?(Regin::Group) - if index > 0 - previous = parts[index-1] - if previous.is_a?(Regin::Character) && previous.literal? - boundaries << previous.to_s - end - end - - if inside = part.expression[0] - if inside.is_a?(Regin::Character) && inside.literal? - boundaries << inside.to_s - end - end - - if index < parts.length - following = parts[index+1] - if following.is_a?(Regin::Character) && following.literal? - boundaries << following.to_s - end - end - end - end - - boundaries - end - - def generate_split_keys(regexp, separators) #:nodoc: - segments = [] - buf = nil - parts = Utils.parse_regexp(regexp) - parts.each_with_index do |part, index| - case part - when Regin::Anchor - if part.value == '$' || part.value == '\Z' - segments << join_buffer(buf, regexp) if buf - segments << NULL - buf = nil - break - end - when Regin::CharacterClass - break if separators.any? { |s| part.include?(s) } - buf = nil - segments << part.to_regexp(true) - when Regin::Character - if separators.any? { |s| part.include?(s) } - segments << join_buffer(buf, regexp) if buf - peek = parts[index+1] - if peek.is_a?(Regin::Character) && separators.include?(peek.value) - segments << '' - end - buf = nil - else - buf ||= Regin::Expression.new([]) - buf += [part] - end - when Regin::Group - if part.quantifier == '?' - value = part.expression.first - if separators.any? { |s| value.include?(s) } - segments << join_buffer(buf, regexp) if buf - buf = nil - end - break - elsif part.quantifier == nil - break if separators.any? { |s| part.include?(s) } - buf = nil - segments << part.to_regexp(true) - else - break - end - else - break - end - - if index + 1 == parts.size - segments << join_buffer(buf, regexp) if buf - buf = nil - break - end - end - - while segments.length > 0 && (segments.last.nil? || segments.last == '') - segments.pop - end - - segments - end - - def join_buffer(parts, regexp) - if parts.literal? - parts.to_s - else - parts.to_regexp(true) - end - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb deleted file mode 100644 index 903c79fdc6..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb +++ /dev/null @@ -1,113 +0,0 @@ -module Rack::Mount - module CodeGeneration #:nodoc: - def _expired_recognize(env) #:nodoc: - raise 'route set not finalized' - end - - def rehash - super - optimize_recognize! - end - - private - def expire! - if @optimized_recognize_defined - remove_metaclass_method :recognize - - class << self - alias_method :recognize, :_expired_recognize - end - - @optimized_recognize_defined = false - end - - super - end - - def optimize_container_iterator(container) - body = [] - - container.each_with_index { |route, i| - body << "route = self[#{i}]" - body << 'matches = {}' - body << 'params = route.defaults.dup' - - conditions = [] - route.conditions.each do |method, condition| - b = [] - if condition.is_a?(Regexp) - b << "if m = obj.#{method}.match(#{condition.inspect})" - b << "matches[:#{method}] = m" - if (named_captures = route.named_captures[method]) && named_captures.any? - b << 'captures = m.captures' - b << 'p = nil' - b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ') - end - else - b << "if m = obj.#{method} == route.conditions[:#{method}]" - end - b << 'true' - b << 'end' - conditions << "(#{b.join('; ')})" - end - - body << <<-RUBY - if #{conditions.join(' && ')} - yield route, matches, params - end - RUBY - } - - container.instance_eval(<<-RUBY, __FILE__, __LINE__) - def optimized_each(obj) - #{body.join("\n")} - nil - end - RUBY - end - - def optimize_recognize! - keys = @recognition_keys.map { |key| - if key.respond_to?(:call_source) - key.call_source(:cache, :obj) - else - "obj.#{key}" - end - }.join(', ') - - @optimized_recognize_defined = true - - remove_metaclass_method :recognize - - instance_eval(<<-RUBY, __FILE__, __LINE__) - def recognize(obj) - cache = {} - container = @recognition_graph[#{keys}] - optimize_container_iterator(container) unless container.respond_to?(:optimized_each) - - if block_given? - container.optimized_each(obj) do |route, matches, params| - yield route, matches, params - end - else - container.optimized_each(obj) do |route, matches, params| - return route, matches, params - end - end - - nil - end - RUBY - end - - # method_defined? can't distinguish between instance - # and meta methods. So we have to rescue if the method - # has not been defined in the metaclass yet. - def remove_metaclass_method(symbol) - metaclass = class << self; self; end - metaclass.send(:remove_method, symbol) - rescue NameError => e - nil - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb deleted file mode 100644 index 47bbab3784..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb +++ /dev/null @@ -1,210 +0,0 @@ -require 'rack/mount/utils' - -module Rack::Mount - class GeneratableRegexp < Regexp #:nodoc: - class DynamicSegment #:nodoc: - attr_reader :name, :requirement - - def initialize(name, requirement) - @name, @requirement = name.to_sym, requirement - freeze - end - - def ==(obj) - @name == obj.name && @requirement == obj.requirement - end - - def =~(str) - @requirement =~ str - end - - def to_hash - { @name => @requirement } - end - - def inspect - "/(?<#{@name}>#{@requirement.source})/" - end - end - - module InstanceMethods - def self.extended(obj) - obj.segments - end - - def defaults=(defaults) - @required_captures = nil - @required_params = nil - @required_defaults = nil - @defaults = defaults - end - - def defaults - @defaults ||= {} - end - - def generatable? - segments.any? - end - - def generate(params = {}, recall = {}, options = {}) - return nil unless generatable? - - merged = recall.merge(params) - return nil unless required_params.all? { |p| merged.include?(p) } - return nil unless required_defaults.all? { |k, v| merged[k] == v } - - generate_from_segments(segments, params, merged, options) - end - - def segments - @segments ||= begin - defaults - segments = [] - catch(:halt) do - expression = Utils.parse_regexp(self) - segments = parse_segments(expression) - end - segments - end - end - - def captures - segments.flatten.find_all { |s| s.is_a?(DynamicSegment) } - end - - def required_captures - @required_captures ||= segments.find_all { |s| - s.is_a?(DynamicSegment) && !@defaults.include?(s.name) - }.freeze - end - - def required_params - @required_params ||= required_captures.map { |s| s.name }.freeze - end - - def required_defaults - @required_defaults ||= begin - required_defaults = @defaults.dup - captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name| - required_defaults.delete(name) - } - required_defaults - end - end - - def freeze - segments - captures - required_captures - required_params - required_defaults - super - end - - private - def parse_segments(segments) - s = [] - segments.each_with_index do |part, index| - case part - when Regin::Anchor - # ignore - when Regin::Character - throw :halt unless part.literal? - - if s.last.is_a?(String) - s.last << part.value.dup - else - s << part.value.dup - end - when Regin::Group - if part.name - s << DynamicSegment.new(part.name, part.expression.to_regexp(true)) - else - s << parse_segments(part.expression) - end - when Regin::Expression - return parse_segments(part) - else - throw :halt - end - end - - s - end - - EMPTY_STRING = ''.freeze - - def generate_from_segments(segments, params, merged, options, optional = false) - if optional - return EMPTY_STRING if segments.all? { |s| s.is_a?(String) } - return EMPTY_STRING unless segments.flatten.any? { |s| - params.has_key?(s.name) if s.is_a?(DynamicSegment) - } - return EMPTY_STRING if segments.any? { |segment| - if segment.is_a?(DynamicSegment) - value = merged[segment.name] || @defaults[segment.name] - value = parameterize(segment.name, value, options) - - merged_value = parameterize(segment.name, merged[segment.name], options) - default_value = parameterize(segment.name, @defaults[segment.name], options) - - if value.nil? || segment !~ value - true - elsif merged_value == default_value - # Nasty control flow - return :clear_remaining_segments - else - false - end - end - } - end - - generated = segments.map do |segment| - case segment - when String - segment - when DynamicSegment - value = params[segment.name] || merged[segment.name] || @defaults[segment.name] - value = parameterize(segment.name, value, options) - if value && segment =~ value.to_s - value - else - return - end - when Array - value = generate_from_segments(segment, params, merged, options, true) - if value == :clear_remaining_segments - segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) } - EMPTY_STRING - elsif value.nil? - EMPTY_STRING - else - value - end - end - end - - # Delete any used items from the params - segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) } - - generated.join - end - - def parameterize(name, value, options) - if block = options[:parameterize] - block.call(name, value) - else - value - end - end - end - include InstanceMethods - - def initialize(regexp) - super - segments - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb deleted file mode 100644 index 0f8eaaec67..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb +++ /dev/null @@ -1,53 +0,0 @@ -begin - require 'nested_multimap' -rescue LoadError - $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap')) - require 'nested_multimap' -end - -module Rack::Mount - class Multimap < NestedMultimap #:nodoc: - def store(*args) - keys = args.dup - value = keys.pop - key = keys.shift - - raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value - - unless key.respond_to?(:=~) - raise ArgumentError, "unsupported key: #{args.first.inspect}" - end - - if key.is_a?(Regexp) - if keys.empty? - @hash.each_pair { |k, l| l << value if k =~ key } - self.default << value - else - @hash.each_pair { |k, _| - if k =~ key - args[0] = k - super(*args) - end - } - - self.default = self.class.new(default) unless default.is_a?(self.class) - default[*keys.dup] = value - end - else - super(*args) - end - end - alias_method :[]=, :store - - undef :index, :invert - - def height - containers_with_default.max { |a, b| a.length <=> b.length }.length - end - - def average_height - lengths = containers_with_default.map { |e| e.length } - lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb deleted file mode 100644 index 58892e4c4f..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'rack/mount/utils' - -module Rack::Mount - class Prefix #:nodoc: - EMPTY_STRING = ''.freeze - PATH_INFO = 'PATH_INFO'.freeze - SCRIPT_NAME = 'SCRIPT_NAME'.freeze - SLASH = '/'.freeze - - KEY = 'rack.mount.prefix'.freeze - - def initialize(app, prefix = nil) - @app, @prefix = app, prefix.freeze - freeze - end - - def call(env) - if prefix = env[KEY] || @prefix - old_path_info = env[PATH_INFO].dup - old_script_name = env[SCRIPT_NAME].dup - - begin - env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(prefix, EMPTY_STRING)) - env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH - env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix) - @app.call(env) - ensure - env[PATH_INFO] = old_path_info - env[SCRIPT_NAME] = old_script_name - end - else - @app.call(env) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb deleted file mode 100644 index c11292b2a2..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb +++ /dev/null @@ -1,69 +0,0 @@ -module Rack::Mount - if Regin.regexp_supports_named_captures? - RegexpWithNamedGroups = Regexp - else - require 'strscan' - - # A wrapper that adds shim named capture support to older - # versions of Ruby. - # - # Because the named capture syntax causes a parse error, an - # alternate syntax is used to indicate named captures. - # - # Ruby 1.9+ named capture syntax: - # - # /(?[a-z]+)/ - # - # Ruby 1.8 shim syntax: - # - # /(?:[a-z]+)/ - class RegexpWithNamedGroups < Regexp - def self.new(regexp) #:nodoc: - if regexp.is_a?(RegexpWithNamedGroups) - regexp - else - super - end - end - - # Wraps Regexp with named capture support. - def initialize(regexp) - regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp) - source, options = regexp.source, regexp.options - @names, scanner = [], StringScanner.new(source) - - while scanner.skip_until(/\(/) - if scanner.scan(/\?:<([^>]+)>/) - @names << scanner[1] - elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/) - # ignore noncapture - else - @names << nil - end - end - source.gsub!(/\?:<([^>]+)>/, '') - - @names = [] unless @names.any? - @names.freeze - - super(source, options) - end - - def names - @names.dup - end - - def named_captures - named_captures = {} - names.each_with_index { |n, i| - named_captures[n] = [i+1] if n - } - named_captures - end - - def eql?(other) - super && @names.eql?(other.names) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb deleted file mode 100644 index 680c40f147..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb +++ /dev/null @@ -1,130 +0,0 @@ -require 'rack/mount/generatable_regexp' -require 'rack/mount/regexp_with_named_groups' -require 'rack/mount/utils' - -module Rack::Mount - # Route is an internal class used to wrap a single route attributes. - # - # Plugins should not depend on any method on this class or instantiate - # new Route objects. Instead use the factory method, RouteSet#add_route - # to create new routes and add them to the set. - class Route - # Valid rack application to call if conditions are met - attr_reader :app - - # A hash of conditions to match against. Conditions may be expressed - # as strings or regexps to match against. - attr_reader :conditions - - # A hash of values that always gets merged into the parameters hash - attr_reader :defaults - - # Symbol identifier for the route used with named route generations - attr_reader :name - - attr_reader :named_captures - - def initialize(app, conditions, defaults, name) - unless app.respond_to?(:call) - raise ArgumentError, 'app must be a valid rack application' \ - ' and respond to call' - end - @app = app - - @name = name ? name.to_sym : nil - @defaults = (defaults || {}).freeze - - @conditions = {} - - conditions.each do |method, pattern| - next unless method && pattern - - pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String) - - if pattern.is_a?(Regexp) - pattern = Utils.normalize_extended_expression(pattern) - pattern = RegexpWithNamedGroups.new(pattern) - pattern.extend(GeneratableRegexp::InstanceMethods) - pattern.defaults = @defaults - end - - @conditions[method] = pattern.freeze - end - - @named_captures = {} - @conditions.map { |method, condition| - next unless condition.respond_to?(:named_captures) - @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)| - named_captures[k.to_sym] = v.last - 1 - named_captures - }.freeze - } - @named_captures.freeze - - @has_significant_params = @conditions.any? { |method, condition| - (condition.respond_to?(:required_params) && condition.required_params.any?) || - (condition.respond_to?(:required_defaults) && condition.required_defaults.any?) - } - - if @conditions.has_key?(:path_info) && - !Utils.regexp_anchored?(@conditions[:path_info]) - @prefix = true - @app = Prefix.new(@app) - else - @prefix = false - end - - @conditions.freeze - end - - def prefix? - @prefix - end - - - def generation_keys - @conditions.inject({}) { |keys, (method, condition)| - if condition.respond_to?(:required_defaults) - keys.merge!(condition.required_defaults) - else - keys - end - } - end - - def significant_params? - @has_significant_params - end - - def generate(method, params = {}, recall = {}, options = {}) - if method.nil? - result = @conditions.inject({}) { |h, (m, condition)| - if condition.respond_to?(:generate) - h[m] = condition.generate(params, recall, options) - end - h - } - return nil if result.values.compact.empty? - else - if condition = @conditions[method] - if condition.respond_to?(:generate) - result = condition.generate(params, recall, options) - end - end - end - - if result - @defaults.each do |key, value| - params.delete(key) if params[key] == value - end - end - - result - end - - - def inspect #:nodoc: - "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>" - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb deleted file mode 100644 index 0e5a65a640..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb +++ /dev/null @@ -1,409 +0,0 @@ -require 'rack/mount/multimap' -require 'rack/mount/route' -require 'rack/mount/utils' - -module Rack::Mount - class RoutingError < StandardError; end - - class RouteSet - # Initialize a new RouteSet without optimizations - def self.new_without_optimizations(options = {}, &block) - new(options.merge(:_optimize => false), &block) - end - - # Basic RouteSet initializer. - # - # If a block is given, the set is yielded and finalized. - # - # See other aspects for other valid options: - # - Generation::RouteSet.new - # - Recognition::RouteSet.new - def initialize(options = {}, &block) - @parameters_key = options.delete(:parameters_key) || 'rack.routing_args' - @parameters_key.freeze - - @named_routes = {} - - @recognition_key_analyzer = Analysis::Splitting.new - @generation_key_analyzer = Analysis::Frequency.new - - @request_class = options.delete(:request_class) || Rack::Request - @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym } - - extend CodeGeneration unless options[:_optimize] == false - @optimized_recognize_defined = false - - @routes = [] - expire! - - if block_given? - yield self - rehash - end - end - - # Builder method to add a route to the set - # - # app:: A valid Rack app to call if the conditions are met. - # conditions:: A hash of conditions to match against. - # Conditions may be expressed as strings or - # regexps to match against. - # defaults:: A hash of values that always gets merged in - # name:: Symbol identifier for the route used with named - # route generations - def add_route(app, conditions = {}, defaults = {}, name = nil) - unless conditions.is_a?(Hash) - raise ArgumentError, 'conditions must be a Hash' - end - - unless conditions.all? { |method, pattern| - @valid_conditions.include?(method) - } - raise ArgumentError, 'conditions may only include ' + - @valid_conditions.inspect - end - - route = Route.new(app, conditions, defaults, name) - @routes << route - - @recognition_key_analyzer << route.conditions - - @named_routes[route.name] = route if route.name - @generation_key_analyzer << route.generation_keys - - expire! - route - end - - def recognize(obj) - raise 'route set not finalized' unless @recognition_graph - - cache = {} - keys = @recognition_keys.map { |key| - if key.respond_to?(:call) - key.call(cache, obj) - else - obj.send(key) - end - } - - @recognition_graph[*keys].each do |route| - matches = {} - params = route.defaults.dup - - if route.conditions.all? { |method, condition| - value = obj.send(method) - if condition.is_a?(Regexp) && (m = value.match(condition)) - matches[method] = m - captures = m.captures - route.named_captures[method].each do |k, i| - if v = captures[i] - params[k] = v - end - end - true - elsif value == condition - true - else - false - end - } - if block_given? - yield route, matches, params - else - return route, matches, params - end - end - end - - nil - end - - X_CASCADE = 'X-Cascade'.freeze - PASS = 'pass'.freeze - PATH_INFO = 'PATH_INFO'.freeze - - # Rack compatible recognition and dispatching method. Routes are - # tried until one returns a non-catch status code. If no routes - # match, the catch status code is returned. - # - # This method can only be invoked after the RouteSet has been - # finalized. - def call(env) - raise 'route set not finalized' unless @recognition_graph - - env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO]) - - request = nil - req = @request_class.new(env) - recognize(req) do |route, matches, params| - # TODO: We only want to unescape params from uri related methods - params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) } - - if route.prefix? - env[Prefix::KEY] = matches[:path_info].to_s - end - - env[@parameters_key] = params - result = route.app.call(env) - return result unless result[1][X_CASCADE] == PASS - end - - request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']] - end - - # Generates a url from Rack env and identifiers or significant keys. - # - # To generate a url by named route, pass the name in as a +Symbol+. - # url(env, :dashboard) # => "/dashboard" - # - # Additional parameters can be passed in as a hash - # url(env, :people, :id => "1") # => "/people/1" - # - # If no name route is given, it will fall back to a slower - # generation search. - # url(env, :controller => "people", :action => "show", :id => "1") - # # => "/people/1" - def url(env, *args) - named_route, params = nil, {} - - case args.length - when 2 - named_route, params = args[0], args[1].dup - when 1 - if args[0].is_a?(Hash) - params = args[0].dup - else - named_route = args[0] - end - else - raise ArgumentError - end - - only_path = params.delete(:only_path) - recall = env[@parameters_key] || {} - - unless result = generate(:all, named_route, params, recall, - :parameterize => lambda { |name, param| Utils.escape_uri(param) }) - return - end - - parts, params = result - return unless parts - - params.each do |k, v| - if v - params[k] = v - else - params.delete(k) - end - end - - req = stubbed_request_class.new(env) - req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params)) - only_path ? req.fullpath : req.url - end - - def generate(method, *args) #:nodoc: - raise 'route set not finalized' unless @generation_graph - - method = nil if method == :all - named_route, params, recall, options = extract_params!(*args) - merged = recall.merge(params) - route = nil - - if named_route - if route = @named_routes[named_route.to_sym] - recall = route.defaults.merge(recall) - url = route.generate(method, params, recall, options) - [url, params] - else - raise RoutingError, "#{named_route} failed to generate from #{params.inspect}" - end - else - keys = @generation_keys.map { |key| - if k = merged[key] - k.to_s - else - nil - end - } - @generation_graph[*keys].each do |r| - next unless r.significant_params? - if url = r.generate(method, params, recall, options) - return [url, params] - end - end - - raise RoutingError, "No route matches #{params.inspect}" - end - end - - # Number of routes in the set - def length - @routes.length - end - - def rehash #:nodoc: - @recognition_keys = build_recognition_keys - @recognition_graph = build_recognition_graph - @generation_keys = build_generation_keys - @generation_graph = build_generation_graph - end - - # Finalizes the set and builds optimized data structures. You *must* - # freeze the set before you can use call and url. - # So remember to call freeze after you are done adding routes. - def freeze - unless frozen? - rehash - - @recognition_key_analyzer = nil - @generation_key_analyzer = nil - @valid_conditions = nil - - @routes.each { |route| route.freeze } - @routes.freeze - end - - super - end - - def marshal_dump #:nodoc: - hash = {} - - instance_variables_to_serialize.each do |ivar| - hash[ivar] = instance_variable_get(ivar) - end - - if graph = hash[:@recognition_graph] - hash[:@recognition_graph] = graph.dup - end - - hash - end - - def marshal_load(hash) #:nodoc: - hash.each do |ivar, value| - instance_variable_set(ivar, value) - end - end - - protected - def recognition_stats - { :keys => @recognition_keys, - :keys_size => @recognition_keys.size, - :graph_size => @recognition_graph.size, - :graph_height => @recognition_graph.height, - :graph_average_height => @recognition_graph.average_height } - end - - private - def expire! #:nodoc: - @recognition_keys = @recognition_graph = nil - @recognition_key_analyzer.expire! - - @generation_keys = @generation_graph = nil - @generation_key_analyzer.expire! - end - - def instance_variables_to_serialize - instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined] - end - - # An internal helper method for constructing a nested set from - # the linear route set. - # - # build_nested_route_set([:request_method, :path_info]) { |route, method| - # route.send(method) - # } - def build_nested_route_set(keys, &block) - graph = Multimap.new - @routes.each_with_index do |route, index| - catch(:skip) do - k = keys.map { |key| block.call(key, index) } - Utils.pop_trailing_nils!(k) - k.map! { |key| key || /.+/ } - graph[*k] = route - end - end - graph - end - - def build_recognition_graph - build_nested_route_set(@recognition_keys) { |k, i| - @recognition_key_analyzer.possible_keys[i][k] - } - end - - def build_recognition_keys - @recognition_key_analyzer.report - end - - def build_generation_graph - build_nested_route_set(@generation_keys) { |k, i| - throw :skip unless @routes[i].significant_params? - - if k = @generation_key_analyzer.possible_keys[i][k] - k.to_s - else - nil - end - } - end - - def build_generation_keys - @generation_key_analyzer.report - end - - def extract_params!(*args) - case args.length - when 4 - named_route, params, recall, options = args - when 3 - if args[0].is_a?(Hash) - params, recall, options = args - else - named_route, params, recall = args - end - when 2 - if args[0].is_a?(Hash) - params, recall = args - else - named_route, params = args - end - when 1 - if args[0].is_a?(Hash) - params = args[0] - else - named_route = args[0] - end - else - raise ArgumentError - end - - named_route ||= nil - params ||= {} - recall ||= {} - options ||= {} - - [named_route, params.dup, recall.dup, options.dup] - end - - def stubbed_request_class - @stubbed_request_class ||= begin - klass = Class.new(@request_class) - klass.public_instance_methods.each do |method| - next if method =~ /^__|object_id/ - klass.class_eval <<-RUBY - def #{method}(*args, &block) - @_stubbed_values[:#{method}] || super - end - RUBY - end - klass.class_eval { attr_accessor :_stubbed_values } - klass - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb deleted file mode 100644 index d0d8797008..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'rack/mount/strexp/parser' - -module Rack::Mount - class Strexp - class << self - # Parses segmented string expression and converts it into a Regexp - # - # Strexp.compile('foo') - # # => %r{\Afoo\Z} - # - # Strexp.compile('foo/:bar', {}, ['/']) - # # => %r{\Afoo/(?[^/]+)\Z} - # - # Strexp.compile(':foo.example.com') - # # => %r{\A(?.+)\.example\.com\Z} - # - # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/']) - # # => %r{\Afoo/(?[a-z]+)\Z} - # - # Strexp.compile('foo(.:extension)') - # # => %r{\Afoo(\.(?.+))?\Z} - # - # Strexp.compile('src/*files') - # # => %r{\Asrc/(?.+)\Z} - def compile(str, requirements = {}, separators = [], anchor = true) - return Regexp.compile(str) if str.is_a?(Regexp) - - requirements = requirements ? requirements.dup : {} - normalize_requirements!(requirements, separators) - - parser = StrexpParser.new - parser.anchor = anchor - parser.requirements = requirements - - begin - re = parser.scan_str(str) - rescue Racc::ParseError => e - raise RegexpError, e.message - end - - Regexp.compile(re) - end - alias_method :new, :compile - - private - def normalize_requirements!(requirements, separators) - requirements.each do |key, value| - if value.is_a?(Regexp) - if regexp_has_modifiers?(value) - requirements[key] = value - else - requirements[key] = value.source - end - else - requirements[key] = Regexp.escape(value) - end - end - requirements.default ||= separators.any? ? - "[^#{separators.join}]+" : '.+' - requirements - end - - def regexp_has_modifiers?(regexp) - regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb deleted file mode 100644 index cfe05afc61..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb +++ /dev/null @@ -1,160 +0,0 @@ -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.6 -# from Racc grammer file "". -# - -require 'racc/parser.rb' - -require 'rack/mount/utils' -require 'rack/mount/strexp/tokenizer' - -module Rack - module Mount - class StrexpParser < Racc::Parser - - -if Regin.regexp_supports_named_captures? - REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze -else - REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze -end - -attr_accessor :anchor, :requirements -##### State transition tables begin ### - -racc_action_table = [ - 1, 2, 3, 9, 4, 1, 2, 3, 12, 4, - 1, 2, 3, 11, 4, 1, 2, 3, nil, 4 ] - -racc_action_check = [ - 0, 0, 0, 5, 0, 3, 3, 3, 9, 3, - 8, 8, 8, 8, 8, 6, 6, 6, nil, 6 ] - -racc_action_pointer = [ - -2, nil, nil, 3, nil, 3, 13, nil, 8, 8, - nil, nil, nil ] - -racc_action_default = [ - -8, -4, -5, -8, -7, -8, -1, -3, -8, -8, - -2, -6, 13 ] - -racc_goto_table = [ - 6, 5, 10, 8, 10 ] - -racc_goto_check = [ - 2, 1, 3, 2, 3 ] - -racc_goto_pointer = [ - nil, 1, 0, -4 ] - -racc_goto_default = [ - nil, nil, nil, 7 ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 1, 8, :_reduce_1, - 2, 9, :_reduce_2, - 1, 9, :_reduce_none, - 1, 10, :_reduce_4, - 1, 10, :_reduce_5, - 3, 10, :_reduce_6, - 1, 10, :_reduce_7 ] - -racc_reduce_n = 8 - -racc_shift_n = 13 - -racc_token_table = { - false => 0, - :error => 1, - :PARAM => 2, - :GLOB => 3, - :LPAREN => 4, - :RPAREN => 5, - :CHAR => 6 } - -racc_nt_base = 7 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ - "$end", - "error", - "PARAM", - "GLOB", - "LPAREN", - "RPAREN", - "CHAR", - "$start", - "target", - "expr", - "token" ] - -Racc_debug_parser = false - -##### State transition tables end ##### - -# reduce 0 omitted - -def _reduce_1(val, _values, result) - result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" - result -end - -def _reduce_2(val, _values, result) - result = val.join - result -end - -# reduce 3 omitted - -def _reduce_4(val, _values, result) - name = val[0].to_sym - requirement = requirements[name] - result = REGEXP_NAMED_CAPTURE % [name, requirement] - - result -end - -def _reduce_5(val, _values, result) - name = val[0].to_sym - requirement = requirements[name] - result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement] - - result -end - -def _reduce_6(val, _values, result) - result = "(?:#{val[1]})?" - result -end - -def _reduce_7(val, _values, result) - result = Regexp.escape(val[0]) - result -end - -def _reduce_none(val, _values, result) - val[0] -end - - end # class StrexpParser - end # module Mount -end # module Rack diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y deleted file mode 100644 index ffbd9fae11..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y +++ /dev/null @@ -1,34 +0,0 @@ -class Rack::Mount::StrexpParser -rule - target: expr { result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" } - - expr: expr token { result = val.join } - | token - - token: PARAM { - name = val[0].to_sym - requirement = requirements[name] - result = REGEXP_NAMED_CAPTURE % [name, requirement] - } - | GLOB { - name = val[0].to_sym - requirement = requirements[name] - result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement] - } - | LPAREN expr RPAREN { result = "(?:#{val[1]})?" } - | CHAR { result = Regexp.escape(val[0]) } -end - ----- header ---- -require 'rack/mount/utils' -require 'rack/mount/strexp/tokenizer' - ----- inner - -if Regin.regexp_supports_named_captures? - REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze -else - REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze -end - -attr_accessor :anchor, :requirements diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb deleted file mode 100644 index 0ff7f67661..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb +++ /dev/null @@ -1,83 +0,0 @@ -#-- -# DO NOT MODIFY!!!! -# This file is automatically generated by rex 1.0.5.beta1 -# from lexical definition file "lib/rack/mount/strexp/tokenizer.rex". -#++ - -require 'racc/parser' -class Rack::Mount::StrexpParser < Racc::Parser - require 'strscan' - - class ScanError < StandardError ; end - - attr_reader :lineno - attr_reader :filename - attr_accessor :state - - def scan_setup(str) - @ss = StringScanner.new(str) - @lineno = 1 - @state = nil - end - - def action - yield - end - - def scan_str(str) - scan_setup(str) - do_parse - end - alias :scan :scan_str - - def load_file( filename ) - @filename = filename - open(filename, "r") do |f| - scan_setup(f.read) - end - end - - def scan_file( filename ) - load_file(filename) - do_parse - end - - - def next_token - return if @ss.eos? - - text = @ss.peek(1) - @lineno += 1 if text == "\n" - token = case @state - when nil - case - when (text = @ss.scan(/\\(\(|\)|:|\*)/)) - action { [:CHAR, @ss[1]] } - - when (text = @ss.scan(/\:([a-zA-Z_]\w*)/)) - action { [:PARAM, @ss[1]] } - - when (text = @ss.scan(/\*([a-zA-Z_]\w*)/)) - action { [:GLOB, @ss[1]] } - - when (text = @ss.scan(/\(/)) - action { [:LPAREN, text] } - - when (text = @ss.scan(/\)/)) - action { [:RPAREN, text] } - - when (text = @ss.scan(/./)) - action { [:CHAR, text] } - - else - text = @ss.string[@ss.pos .. -1] - raise ScanError, "can not match: '" + text + "'" - end # if - - else - raise ScanError, "undefined state: '" + state.to_s + "'" - end # case state - token - end # def next_token - -end # class diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex deleted file mode 100644 index 473bd096e1..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex +++ /dev/null @@ -1,12 +0,0 @@ -class Rack::Mount::StrexpParser -macro - RESERVED \(|\)|:|\* - ALPHA_U [a-zA-Z_] -rule - \\({RESERVED}) { [:CHAR, @ss[1]] } - \:({ALPHA_U}\w*) { [:PARAM, @ss[1]] } - \*({ALPHA_U}\w*) { [:GLOB, @ss[1]] } - \( { [:LPAREN, text] } - \) { [:RPAREN, text] } - . { [:CHAR, text] } -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb deleted file mode 100644 index aa23b1162f..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb +++ /dev/null @@ -1,148 +0,0 @@ -begin - require 'regin' -rescue LoadError - $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/regin')) - require 'regin' -end - -require 'uri' - -module Rack::Mount - # Private utility methods used throughout Rack::Mount. - #-- - # This module is a trash can. Try to move these functions into - # more appropriate contexts. - #++ - module Utils - # Normalizes URI path. - # - # Strips off trailing slash and ensures there is a leading slash. - # - # normalize_path("/foo") # => "/foo" - # normalize_path("/foo/") # => "/foo" - # normalize_path("foo") # => "/foo" - # normalize_path("") # => "/" - def normalize_path(path) - path = "/#{path}" - path.squeeze!('/') - path.sub!(%r{/+\Z}, '') - path = '/' if path == '' - path - end - module_function :normalize_path - - # Removes trailing nils from array. - # - # pop_trailing_nils!([1, 2, 3]) # => [1, 2, 3] - # pop_trailing_nils!([1, 2, 3, nil, nil]) # => [1, 2, 3] - # pop_trailing_nils!([nil]) # => [] - def pop_trailing_nils!(ary) - while ary.length > 0 && ary.last.nil? - ary.pop - end - ary - end - module_function :pop_trailing_nils! - - RESERVED_PCHAR = ':@&=+$,;%' - SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" - if RUBY_VERSION >= '1.9' - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze - else - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze - end - - Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI - - def escape_uri(uri) - Parser.escape(uri.to_s, UNSAFE_PCHAR) - end - module_function :escape_uri - - if ''.respond_to?(:force_encoding) - def unescape_uri(uri) - Parser.unescape(uri).force_encoding('utf-8') - end - else - def unescape_uri(uri) - URI.unescape(uri) - end - end - module_function :unescape_uri - - # Taken from Rack 1.1.x to build nested query strings - def build_nested_query(value, prefix = nil) #:nodoc: - case value - when Array - value.map { |v| - build_nested_query(v, "#{prefix}[]") - }.join("&") - when Hash - value.map { |k, v| - build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k)) - }.join("&") - when String - raise ArgumentError, "value must be a Hash" if prefix.nil? - "#{prefix}=#{Rack::Utils.escape(value)}" - else - prefix - end - end - module_function :build_nested_query - - # Determines whether the regexp must match the entire string. - # - # regexp_anchored?(/^foo$/) # => true - # regexp_anchored?(/foo/) # => false - # regexp_anchored?(/^foo/) # => false - # regexp_anchored?(/foo$/) # => false - def regexp_anchored?(regexp) - regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/m ? true : false - end - module_function :regexp_anchored? - - def normalize_extended_expression(regexp) - return regexp unless regexp.options & Regexp::EXTENDED != 0 - source = regexp.source - source.gsub!(/#.+$/, '') - source.gsub!(/\s+/, '') - source.gsub!(/\\\//, '/') - Regexp.compile(source) - end - module_function :normalize_extended_expression - - def parse_regexp(regexp) - cache = @@_parse_regexp_cache ||= {} - - if expression = cache[regexp] - return expression - end - - unless regexp.is_a?(RegexpWithNamedGroups) - regexp = RegexpWithNamedGroups.new(regexp) - end - - expression = Regin.parse(regexp) - - unless Regin.regexp_supports_named_captures? - tag_captures = Proc.new do |group| - case group - when Regin::Group - # TODO: dup instead of mutating - group.instance_variable_set('@name', regexp.names[group.index]) if group.index - tag_captures.call(group.expression) - when Regin::Expression - group.each { |child| tag_captures.call(child) } - end - end - tag_captures.call(expression) - end - - cache[regexp] = expression.freeze - expression - rescue Racc::ParseError, Regin::Parser::ScanError - [] - end - module_function :parse_regexp - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb deleted file mode 100644 index 0b49b49280..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb +++ /dev/null @@ -1,569 +0,0 @@ -require 'forwardable' -require 'multiset' - -# Multimap is a generalization of a map or associative array -# abstract data type in which more than one value may be associated -# with and returned for a given key. -# -# == Example -# -# require 'multimap' -# map = Multimap.new -# map["a"] = 100 -# map["b"] = 200 -# map["a"] = 300 -# map["a"] # -> [100, 300] -# map["b"] # -> [200] -# map.keys # -> # -class Multimap - extend Forwardable - - include Enumerable - - # call-seq: - # Multimap[ [key =>|, value]* ] => multimap - # - # Creates a new multimap populated with the given objects. - # - # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]} - # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]} - def self.[](*args) - default = [] - - if args.size == 2 && args.last.is_a?(Hash) - default = args.shift - elsif !args.first.is_a?(Hash) && args.size % 2 == 1 - default = args.shift - end - - if args.size == 1 && args.first.is_a?(Hash) - args[0] = args.first.inject({}) { |hash, (key, value)| - unless value.is_a?(default.class) - value = (default.dup << value) - end - hash[key] = value - hash - } - else - index = 0 - args.map! { |value| - unless index % 2 == 0 || value.is_a?(default.class) - value = (default.dup << value) - end - index += 1 - value - } - end - - map = new - map.instance_variable_set(:@hash, Hash[*args]) - map.default = default - map - end - - # call-seq: - # Multimap.new => multimap - # Multimap.new(default) => multimap - # - # Returns a new, empty multimap. - # - # map = Multimap.new(Set.new) - # h["a"] = 100 - # h["b"] = 200 - # h["a"] #=> [100].to_set - # h["c"] #=> [].to_set - def initialize(default = []) - @hash = Hash.new(default) - end - - def initialize_copy(original) #:nodoc: - @hash = Hash.new(original.default.dup) - original._internal_hash.each_pair do |key, container| - @hash[key] = container.dup - end - end - - def_delegators :@hash, :clear, :default, :default=, :empty?, - :fetch, :has_key?, :key? - - # Retrieves the value object corresponding to the - # *keys object. - def [](key) - @hash[key] - end - - # call-seq: - # map[key] = value => value - # map.store(key, value) => value - # - # Associates the value given by value with the key - # given by key. Unlike a regular hash, multiple can be - # assoicated with the same value. - # - # map = Multimap["a" => 100, "b" => 200] - # map["a"] = 9 - # map["c"] = 4 - # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]} - def store(key, value) - update_container(key) do |container| - container << value - container - end - end - alias_method :[]=, :store - - # call-seq: - # map.delete(key, value) => value - # map.delete(key) => value - # - # Deletes and returns a key-value pair from map. If only - # key is given, all the values matching that key will be - # deleted. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.delete("b", 300) #=> 300 - # map.delete("a") #=> [100] - def delete(key, value = nil) - if value - @hash[key].delete(value) - else - @hash.delete(key) - end - end - - # call-seq: - # map.each { |key, value| block } => map - # - # Calls block for each key/value pair in map, passing - # the key and value to the block as a two-element array. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.each { |key, value| puts "#{key} is #{value}" } - # - # produces: - # - # a is 100 - # b is 200 - # b is 300 - def each - each_pair do |key, value| - yield [key, value] - end - end - - # call-seq: - # map.each_association { |key, container| block } => map - # - # Calls block once for each key/container in map, passing - # the key and container to the block as parameters. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.each_association { |key, container| puts "#{key} is #{container}" } - # - # produces: - # - # a is [100] - # b is [200, 300] - def each_association(&block) - @hash.each_pair(&block) - end - - # call-seq: - # map.each_container { |container| block } => map - # - # Calls block for each container in map, passing the - # container as a parameter. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.each_container { |container| puts container } - # - # produces: - # - # [100] - # [200, 300] - def each_container - each_association do |_, container| - yield container - end - end - - # call-seq: - # map.each_key { |key| block } => map - # - # Calls block for each key in hsh, passing the key - # as a parameter. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.each_key { |key| puts key } - # - # produces: - # - # a - # b - # b - def each_key - each_pair do |key, _| - yield key - end - end - - # call-seq: - # map.each_pair { |key_value_array| block } => map - # - # Calls block for each key/value pair in map, - # passing the key and value as parameters. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.each_pair { |key, value| puts "#{key} is #{value}" } - # - # produces: - # - # a is 100 - # b is 200 - # b is 300 - def each_pair - each_association do |key, values| - values.each do |value| - yield key, value - end - end - end - - # call-seq: - # map.each_value { |value| block } => map - # - # Calls block for each key in map, passing the - # value as a parameter. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.each_value { |value| puts value } - # - # produces: - # - # 100 - # 200 - # 300 - def each_value - each_pair do |_, value| - yield value - end - end - - def ==(other) #:nodoc: - case other - when Multimap - @hash == other._internal_hash - else - @hash == other - end - end - - def eql?(other) #:nodoc: - case other - when Multimap - @hash.eql?(other._internal_hash) - else - @hash.eql?(other) - end - end - - def freeze #:nodoc: - each_container { |container| container.freeze } - default.freeze - super - end - - # call-seq: - # map.has_value?(value) => true or false - # map.value?(value) => true or false - # - # Returns true if the given value is present for any key - # in map. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.has_value?(300) #=> true - # map.has_value?(999) #=> false - def has_value?(value) - values.include?(value) - end - alias_method :value?, :has_value? - - # call-seq: - # map.index(value) => key - # - # Returns the key for a given value. If not found, returns - # nil. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.index(100) #=> "a" - # map.index(200) #=> "b" - # map.index(999) #=> nil - def index(value) - invert[value] - end - - # call-seq: - # map.delete_if {| key, value | block } -> map - # - # Deletes every key-value pair from map for which block - # evaluates to true. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.delete_if {|key, value| value >= 300 } - # #=> Multimap["a" => 100, "b" => 200] - # - def delete_if - each_association do |key, container| - container.delete_if do |value| - yield [key, value] - end - end - self - end - - # call-seq: - # map.reject {| key, value | block } -> map - # - # Same as Multimap#delete_if, but works on (and returns) a - # copy of the map. Equivalent to - # map.dup.delete_if. - # - def reject(&block) - dup.delete_if(&block) - end - - # call-seq: - # map.reject! {| key, value | block } -> map or nil - # - # Equivalent to Multimap#delete_if, but returns - # nil if no changes were made. - # - def reject!(&block) - old_size = size - delete_if(&block) - old_size == size ? nil : self - end - - # call-seq: - # map.replace(other_map) => map - # - # Replaces the contents of map with the contents of - # other_map. - # - # map = Multimap["a" => 100, "b" => 200] - # map.replace({ "c" => 300, "d" => 400 }) - # #=> Multimap["c" => 300, "d" => 400] - def replace(other) - case other - when Array - @hash.replace(self.class[self.default, *other]) - when Hash - @hash.replace(self.class[self.default, other]) - when self.class - @hash.replace(other) - else - raise ArgumentError - end - end - - # call-seq: - # map.invert => multimap - # - # Returns a new multimap created by using map's values as keys, - # and the keys as values. - # - # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]] - # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"] - def invert - h = self.class.new(default.dup) - each_pair { |key, value| h[value] = key } - h - end - - # call-seq: - # map.keys => multiset - # - # Returns a new +Multiset+ populated with the keys from this hash. See also - # Multimap#values and Multimap#containers. - # - # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] - # map.keys #=> Multiset.new(["a", "b", "b", "c"]) - def keys - keys = Multiset.new - each_key { |key| keys << key } - keys - end - - # Returns true if the given key is present in Multimap. - def include?(key) - keys.include?(key) - end - alias_method :member?, :include? - - # call-seq: - # map.length => fixnum - # map.size => fixnum - # - # Returns the number of key-value pairs in the map. - # - # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] - # map.length #=> 4 - # map.delete("a") #=> 100 - # map.length #=> 3 - def size - values.size - end - alias_method :length, :size - - # call-seq: - # map.merge(other_map) => multimap - # - # Returns a new multimap containing the contents of other_map and - # the contents of map. - # - # map1 = Multimap["a" => 100, "b" => 200] - # map2 = Multimap["a" => 254, "c" => 300] - # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] - # map1 #=> Multimap["a" => 100, "b" => 200] - def merge(other) - dup.update(other) - end - - # call-seq: - # map.merge!(other_map) => multimap - # map.update(other_map) => multimap - # - # Adds each pair from other_map to map. - # - # map1 = Multimap["a" => 100, "b" => 200] - # map2 = Multimap["b" => 254, "c" => 300] - # - # map1.merge!(map2) - # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] - def update(other) - case other - when self.class - other.each_pair { |key, value| store(key, value) } - when Hash - update(self.class[self.default, other]) - else - raise ArgumentError - end - self - end - alias_method :merge!, :update - - # call-seq: - # map.select { |key, value| block } => multimap - # - # Returns a new Multimap consisting of the pairs for which the - # block returns true. - # - # map = Multimap["a" => 100, "b" => 200, "c" => 300] - # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300] - # map.select { |k,v| v < 200 } #=> Multimap["a" => 100] - def select - inject(self.class.new) { |map, (key, value)| - map[key] = value if yield([key, value]) - map - } - end - - # call-seq: - # map.to_a => array - # - # Converts map to a nested array of [key, - # value] arrays. - # - # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] - # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]] - def to_a - ary = [] - each_pair do |key, value| - ary << [key, value] - end - ary - end - - # call-seq: - # map.to_hash => hash - # - # Converts map to a basic hash. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.to_hash #=> { "a" => [100], "b" => [200, 300] } - def to_hash - @hash.dup - end - - # call-seq: - # map.containers => array - # - # Returns a new array populated with the containers from map. See - # also Multimap#keys and Multimap#values. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.containers #=> [[100], [200, 300]] - def containers - containers = [] - each_container { |container| containers << container } - containers - end - - # call-seq: - # map.values => array - # - # Returns a new array populated with the values from map. See - # also Multimap#keys and Multimap#containers. - # - # map = Multimap["a" => 100, "b" => [200, 300]] - # map.values #=> [100, 200, 300] - def values - values = [] - each_value { |value| values << value } - values - end - - # Return an array containing the values associated with the given keys. - def values_at(*keys) - @hash.values_at(*keys) - end - - def marshal_dump #:nodoc: - @hash - end - - def marshal_load(hash) #:nodoc: - @hash = hash - end - - def to_yaml(opts = {}) #:nodoc: - YAML::quick_emit(self, opts) do |out| - out.map(taguri, to_yaml_style) do |map| - @hash.each do |k, v| - map.add(k, v) - end - map.add('__default__', @hash.default) - end - end - end - - def yaml_initialize(tag, val) #:nodoc: - default = val.delete('__default__') - @hash = val - @hash.default = default - self - end - - protected - def _internal_hash #:nodoc: - @hash - end - - def update_container(key) #:nodoc: - container = @hash[key] - container = container.dup if container.equal?(default) - container = yield(container) - @hash[key] = container - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb deleted file mode 100644 index 119bf12646..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb +++ /dev/null @@ -1,185 +0,0 @@ -require 'set' - -# Multiset implements a collection of unordered values and -# allows duplicates. -# -# == Example -# -# require 'multiset' -# s1 = Multiset.new [1, 2] # -> # -# s1.add(2) # -> # -# s1.merge([2, 6]) # -> # -# s1.multiplicity(2) # -> 3 -# s1.multiplicity(3) # -> 1 -class Multiset < Set - def initialize(*args, &block) #:nodoc: - @hash = Hash.new(0) - super - end - - # Returns the number of times an element belongs to the multiset. - def multiplicity(e) - @hash[e] - end - - # Returns the total number of elements in a multiset, including - # repeated memberships - def cardinality - @hash.inject(0) { |s, (e, m)| s += m } - end - alias_method :size, :cardinality - alias_method :length, :cardinality - - # Converts the set to an array. The order of elements is uncertain. - def to_a - inject([]) { |ary, (key, _)| ary << key } - end - - # Returns true if the set is a superset of the given set. - def superset?(set) - set.is_a?(self.class) or raise ArgumentError, "value must be a set" - return false if cardinality < set.cardinality - set.all? { |o| set.multiplicity(o) <= multiplicity(o) } - end - - # Returns true if the set is a proper superset of the given set. - def proper_superset?(set) - set.is_a?(self.class) or raise ArgumentError, "value must be a set" - return false if cardinality <= set.cardinality - set.all? { |o| set.multiplicity(o) <= multiplicity(o) } - end - - # Returns true if the set is a subset of the given set. - def subset?(set) - set.is_a?(self.class) or raise ArgumentError, "value must be a set" - return false if set.cardinality < cardinality - all? { |o| multiplicity(o) <= set.multiplicity(o) } - end - - # Returns true if the set is a proper subset of the given set. - def proper_subset?(set) - set.is_a?(self.class) or raise ArgumentError, "value must be a set" - return false if set.cardinality <= cardinality - all? { |o| multiplicity(o) <= set.multiplicity(o) } - end - - # Calls the given block once for each element in the set, passing - # the element as parameter. Returns an enumerator if no block is - # given. - def each - @hash.each_pair do |key, multiplicity| - multiplicity.times do - yield(key) - end - end - self - end - - # Adds the given object to the set and returns self. Use +merge+ to - # add many elements at once. - def add(o) - @hash[o] ||= 0 - @hash[o] += 1 - self - end - alias << add - - undef :add? - - # Deletes all the identical object from the set and returns self. - # If +n+ is given, it will remove that amount of identical objects - # from the set. Use +subtract+ to delete many different items at - # once. - def delete(o, n = nil) - if n - @hash[o] ||= 0 - @hash[o] -= n if @hash[o] > 0 - @hash.delete(o) if @hash[o] == 0 - else - @hash.delete(o) - end - self - end - - undef :delete? - - # Deletes every element of the set for which block evaluates to - # true, and returns self. - def delete_if - each { |o| delete(o) if yield(o) } - self - end - - # Merges the elements of the given enumerable object to the set and - # returns self. - def merge(enum) - enum.each { |o| add(o) } - self - end - - # Deletes every element that appears in the given enumerable object - # and returns self. - def subtract(enum) - enum.each { |o| delete(o, 1) } - self - end - - # Returns a new set containing elements common to the set and the - # given enumerable object. - def &(enum) - s = dup - n = self.class.new - enum.each { |o| - if s.include?(o) - s.delete(o, 1) - n.add(o) - end - } - n - end - alias intersection & - - # Returns a new set containing elements exclusive between the set - # and the given enumerable object. (set ^ enum) is equivalent to - # ((set | enum) - (set & enum)). - def ^(enum) - n = self.class.new(enum) - each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) } - n - end - - # Returns true if two sets are equal. Two multisets are equal if - # they have the same cardinalities and each element has the same - # multiplicity in both sets. The equality of each element inside - # the multiset is defined according to Object#eql?. - def eql?(set) - return true if equal?(set) - set = self.class.new(set) unless set.is_a?(self.class) - return false unless cardinality == set.cardinality - superset?(set) && subset?(set) - end - alias_method :==, :eql? - - def marshal_dump #:nodoc: - @hash - end - - def marshal_load(hash) #:nodoc: - @hash = hash - end - - def to_yaml(opts = {}) #:nodoc: - YAML::quick_emit(self, opts) do |out| - out.map(taguri, to_yaml_style) do |map| - @hash.each do |k, v| - map.add(k, v) - end - end - end - end - - def yaml_initialize(tag, val) #:nodoc: - @hash = val - self - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb deleted file mode 100644 index 4eb088b91a..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb +++ /dev/null @@ -1,158 +0,0 @@ -require 'multimap' - -# NestedMultimap allows values to be assoicated with a nested -# set of keys. -class NestedMultimap < Multimap - # call-seq: - # multimap[*keys] = value => value - # multimap.store(*keys, value) => value - # - # Associates the value given by value with multiple key - # given by keys. - # - # map = NestedMultimap.new - # map["a"] = 100 - # map["a", "b"] = 101 - # map["a"] = 102 - # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}} - def store(*args) - keys = args - value = args.pop - - raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value - - if keys.length > 1 - update_container(keys.shift) do |container| - container = self.class.new(container) unless container.is_a?(self.class) - container[*keys] = value - container - end - elsif keys.length == 1 - super(keys.first, value) - else - self << value - end - end - alias_method :[]=, :store - - # call-seq: - # multimap << obj => multimap - # - # Pushes the given object on to the end of all the containers. - # - # map = NestedMultimap["a" => [100], "b" => [200, 300]] - # map << 300 - # map["a"] #=> [100, 300] - # map["c"] #=> [300] - def <<(value) - @hash.each_value { |container| container << value } - self.default << value - self - end - - # call-seq: - # multimap[*keys] => value - # multimap[key1, key2, key3] => value - # - # Retrieves the value object corresponding to the - # *keys object. - def [](*keys) - i, l, r, k = 0, keys.length, self, self.class - while r.is_a?(k) - r = i < l ? r._internal_hash[keys[i]] : r.default - i += 1 - end - r - end - - # call-seq: - # multimap.each_association { |key, container| block } => multimap - # - # Calls block once for each key/container in map, passing - # the key and container to the block as parameters. - # - # map = NestedMultimap.new - # map["a"] = 100 - # map["a", "b"] = 101 - # map["a"] = 102 - # map["c"] = 200 - # map.each_association { |key, container| puts "#{key} is #{container}" } - # - # produces: - # - # ["a", "b"] is [100, 101, 102] - # "c" is [200] - def each_association - super() do |key, container| - if container.respond_to?(:each_association) - container.each_association do |nested_key, value| - yield [key, nested_key].flatten, value - end - else - yield key, container - end - end - end - - # call-seq: - # multimap.each_container_with_default { |container| block } => map - # - # Calls block for every container in map including - # the default, passing the container as a parameter. - # - # map = NestedMultimap.new - # map["a"] = 100 - # map["a", "b"] = 101 - # map["a"] = 102 - # map.each_container_with_default { |container| puts container } - # - # produces: - # - # [100, 101, 102] - # [100, 102] - # [] - def each_container_with_default(&block) - @hash.each_value do |container| - iterate_over_container(container, &block) - end - iterate_over_container(default, &block) - self - end - - # call-seq: - # multimap.containers_with_default => array - # - # Returns a new array populated with all the containers from - # map including the default. - # - # map = NestedMultimap.new - # map["a"] = 100 - # map["a", "b"] = 101 - # map["a"] = 102 - # map.containers_with_default #=> [[100, 101, 102], [100, 102], []] - def containers_with_default - containers = [] - each_container_with_default { |container| containers << container } - containers - end - - def inspect #:nodoc: - super.gsub(/\}$/, ", default => #{default.inspect}}") - end - - private - def iterate_over_container(container) - if container.respond_to?(:each_container_with_default) - container.each_container_with_default do |value| - yield value - end - else - yield container - end - end -end - -begin - require 'nested_multimap_ext' -rescue LoadError -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb deleted file mode 100644 index d38922bcc6..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Regin - autoload :Alternation, 'regin/alternation' - autoload :Anchor, 'regin/anchor' - autoload :Atom, 'regin/atom' - autoload :Character, 'regin/character' - autoload :CharacterClass, 'regin/character_class' - autoload :Collection, 'regin/collection' - autoload :Expression, 'regin/expression' - autoload :Group, 'regin/group' - autoload :Options, 'regin/options' - autoload :Parser, 'regin/parser' - - class << self - begin - eval('foo = /(?.*)/').named_captures - - # Returns true if the interpreter is using the Oniguruma Regexp lib - # and supports named captures. - # - # /(?bar)/ - def regexp_supports_named_captures? - true - end - rescue SyntaxError, NoMethodError - def regexp_supports_named_captures? #:nodoc: - false - end - end - - # Parses Regexp and returns a Expression data structure. - def parse(regexp) - Parser.parse_regexp(regexp) - end - - # Recompiles Regexp by parsing it and turning it back into a Regexp. - # - # (In the future Regin will perform some Regexp optimizations - # such as removing unnecessary captures and options) - def compile(source) - regexp = Regexp.compile(source) - expression = parse(regexp) - Regexp.compile(expression.to_s(true), expression.flags) - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb deleted file mode 100644 index ce4f52bfdb..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb +++ /dev/null @@ -1,40 +0,0 @@ -module Regin - class Alternation < Collection - def initialize(*args) - args, options = extract_options(args) - - if args.length == 1 && args.first.instance_of?(Array) - super(args.first) - else - super(args) - end - - if options.key?(:ignorecase) - @array.map! { |e| e.dup(:ignorecase => options[:ignorecase]) } - end - end - - # Returns true if expression could be treated as a literal string. - # - # Alternation groups are never literal. - def literal? - false - end - - def flags - 0 - end - - def dup(options = {}) - self.class.new(to_a, options) - end - - def to_s(parent = false) - map { |e| e.to_s(parent) }.join('|') - end - - def inspect #:nodoc: - to_s.inspect - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb deleted file mode 100644 index 05520dd5e0..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb +++ /dev/null @@ -1,4 +0,0 @@ -module Regin - class Anchor < Atom - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb deleted file mode 100644 index eb1923a5a1..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Regin - class Atom - attr_reader :value, :ignorecase - - def initialize(value, options = {}) - @value = value - @ignorecase = options[:ignorecase] - end - - def option_names - %w( ignorecase ) - end - - # Returns true if expression could be treated as a literal string. - def literal? - false - end - - def casefold? - ignorecase ? true : false - end - - def dup(options = {}) - original_options = option_names.inject({}) do |h, m| - h[m.to_sym] = send(m) - h - end - self.class.new(value, original_options.merge(options)) - end - - def to_s(parent = false) - "#{value}" - end - - def inspect #:nodoc: - "#<#{self.class.to_s.sub('Regin::', '')} #{to_s.inspect}>" - end - - def ==(other) #:nodoc: - case other - when String - other == to_s - else - eql?(other) - end - end - - def eql?(other) #:nodoc: - other.instance_of?(self.class) && - self.value.eql?(other.value) && - (!!self.ignorecase).eql?(!!other.ignorecase) - end - - def freeze #:nodoc: - value.freeze - super - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb deleted file mode 100644 index 12a9199d2a..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb +++ /dev/null @@ -1,56 +0,0 @@ -module Regin - class Character < Atom - attr_reader :quantifier - - def initialize(value, options = {}) - @quantifier = options[:quantifier] - super - end - - def option_names - %w( quantifier ) + super - end - - # Returns true if expression could be treated as a literal string. - # - # A Character is literal is there is no quantifier attached to it. - def literal? - quantifier.nil? && !ignorecase - end - - def to_s(parent = false) - if !parent && ignorecase - "(?i-mx:#{value})#{quantifier}" - else - "#{value}#{quantifier}" - end - end - - def to_regexp(anchored = false) - re = to_s(true) - re = "\\A#{re}\\Z" if anchored - Regexp.compile(re, ignorecase) - end - - def match(char) - to_regexp(true).match(char) - end - - def include?(char) - if ignorecase - value.downcase == char.downcase - else - value == char - end - end - - def eql?(other) #:nodoc: - super && quantifier.eql?(other.quantifier) - end - - def freeze #:nodoc: - quantifier.freeze if quantifier - super - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb deleted file mode 100644 index caed5ef9d0..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Regin - class CharacterClass < Character - def initialize(value, options = {}) - @negate = options[:negate] - super - end - - def option_names - %w( negate ) + super - end - - attr_reader :negate - - def negated? - negate ? true : false - end - - # Returns true if expression could be treated as a literal string. - # - # A CharacterClass is never literal. - def literal? - false - end - - def bracketed? - value != '.' && value !~ /^\\[dDsSwW]$/ - end - - def to_s(parent = false) - if bracketed? - if !parent && ignorecase - "(?i-mx:[#{negate && '^'}#{value}])#{quantifier}" - else - "[#{negate && '^'}#{value}]#{quantifier}" - end - else - super - end - end - - def include?(char) - re = quantifier ? to_s.sub(/#{Regexp.escape(quantifier)}$/, '') : to_s - Regexp.compile("\\A#{re}\\Z").match(char) - end - - def eql?(other) #:nodoc: - super && negate == other.negate - end - - def freeze #:nodoc: - negate.freeze if negate - super - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb deleted file mode 100644 index b60353268a..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Regin - class Collection - include Enumerable - - def initialize(*args) - @array = Array.new(*args) - end - - def each - @array.each{ |item| yield item } - end - - def [](i) - @array[i] - end - - def length - @array.length - end - alias_method :size, :length - - def first - @array.first - end - - def last - @array.last - end - - def +(other) - ary = other.is_a?(self.class) ? other.internal_array : other - self.class.new(@array + ary) - end - - def to_regexp(anchored = false) - re = to_s(true) - re = "\\A#{re}\\Z" if anchored - Regexp.compile(re, flags) - end - - def match(char) - to_regexp.match(char) - end - - def include?(char) - any? { |e| e.include?(char) } - end - - def ==(other) #:nodoc: - case other - when String - other == to_s - when Array - other == @array - else - eql?(other) - end - end - - def eql?(other) #:nodoc: - other.instance_of?(self.class) && @array.eql?(other.internal_array) - end - - def freeze #:nodoc: - each { |e| e.freeze } - @array.freeze - super - end - - protected - def internal_array #:nodoc: - @array - end - - def extract_options(args) - if args.last.is_a?(Hash) - return args[0..-2], args.last - else - return args, {} - end - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb deleted file mode 100644 index 18e4965097..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb +++ /dev/null @@ -1,126 +0,0 @@ -module Regin - class Expression < Collection - attr_reader :ignorecase, :multiline, :extended - - def initialize(*args) - args, options = extract_options(args) - - @multiline = @ignorecase = @extended = nil - - if args.length == 1 && args.first.instance_of?(Array) - super(args.first) - else - args = args.map { |e| e.instance_of?(String) ? Character.new(e) : e } - super(args) - end - - self.multiline = options[:multiline] if options.key?(:multiline) - self.ignorecase = options[:ignorecase] if options.key?(:ignorecase) - self.extended = options[:extended] if options.key?(:extended) - end - - # Returns true if expression could be treated as a literal string. - # - # A Expression is literal if all its elements are literal. - def literal? - !ignorecase && all? { |e| e.literal? } - end - - def anchored? - anchored_to_start? && anchored_to_end? - end - - def anchored_to_start? - first.is_a?(Anchor) && first == '\A' - end - - def anchored_to_end? - last.is_a?(Anchor) && last == '\Z' - end - - def anchored_to_line? - anchored_to_start_of_line? && anchored_to_end_of_line? - end - - def anchored_to_start_of_line? - anchored_to_start? || (first.is_a?(Anchor) && first == '^') - end - - def anchored_to_end_of_line? - anchored_to_end? || (last.is_a?(Anchor) && last == '$') - end - - def options? - options.any?(true) - end - - def flags - options.to_i - end - - def +(other) - ary = other.is_a?(self.class) ? other.internal_array : other - ary = @array + ary + [options.to_h(true)] - self.class.new(*ary) - end - - def dup(options = {}) - expression = super() - expression.multiline = options[:multiline] if options.key?(:multiline) - expression.ignorecase = options[:ignorecase] if options.key?(:ignorecase) - expression.extended = options[:extended] if options.key?(:extended) - expression - end - - def to_s(parent = false) - if parent || !options? - map { |e| e.to_s(parent) }.join - else - with, without = [], [] - multiline ? (with << 'm') : (without << 'm') - ignorecase ? (with << 'i') : (without << 'i') - extended ? (with << 'x') : (without << 'x') - - with = with.join - without = without.any? ? "-#{without.join}" : '' - - "(?#{with}#{without}:#{map { |e| e.to_s(true) }.join})" - end - end - - def inspect #:nodoc: - "#" - end - - def casefold? - ignorecase - end - - def eql?(other) #:nodoc: - super && - !!self.multiline == !!other.multiline && - !!self.ignorecase == !!other.ignorecase && - !!self.extended == !!other.extended - end - - protected - def options - Options.new(multiline, ignorecase, extended) - end - - def multiline=(multiline) - @multiline = multiline - end - - def ignorecase=(ignorecase) - if @ignorecase.nil? - @array.map! { |e| e.dup(:ignorecase => ignorecase) } - @ignorecase = ignorecase - end - end - - def extended=(extended) - @extended = extended - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb deleted file mode 100644 index d682148bd9..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb +++ /dev/null @@ -1,90 +0,0 @@ -module Regin - class Group - attr_reader :expression, :quantifier, :capture, :index, :name - - def initialize(expression, options = {}) - @quantifier = @index = @name = nil - @capture = true - @expression = expression.dup(options) - - @quantifier = options[:quantifier] if options.key?(:quantifier) - @capture = options[:capture] if options.key?(:capture) - @index = options[:index] if options.key?(:index) - @name = options[:name] if options.key?(:name) - end - - def option_names - %w( quantifier capture index name ) - end - - # Returns true if expression could be treated as a literal string. - # - # A Group is literal if its expression is literal and it has no quantifier. - def literal? - quantifier.nil? && expression.literal? - end - - def to_s(parent = false) - if !expression.options? - "(#{capture ? '' : '?:'}#{expression.to_s(parent)})#{quantifier}" - elsif capture == false - "#{expression.to_s}#{quantifier}" - else - "(#{expression.to_s})#{quantifier}" - end - end - - def to_regexp(anchored = false) - re = to_s - re = "\\A#{re}\\Z" if anchored - Regexp.compile(re) - end - - def dup(options = {}) - original_options = option_names.inject({}) do |h, m| - h[m.to_sym] = send(m) - h - end - self.class.new(expression, original_options.merge(options)) - end - - def inspect #:nodoc: - to_s.inspect - end - - def match(char) - to_regexp.match(char) - end - - def include?(char) - expression.include?(char) - end - - def capture? - capture - end - - def ==(other) #:nodoc: - case other - when String - other == to_s - else - eql?(other) - end - end - - def eql?(other) #:nodoc: - other.is_a?(self.class) && - self.expression == other.expression && - self.quantifier == other.quantifier && - self.capture == other.capture && - self.index == other.index && - self.name == other.name - end - - def freeze #:nodoc: - expression.freeze if expression - super - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb deleted file mode 100644 index 03ba29d9a5..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Regin - class Options - def self.from_int(flags) - multiline = flags & Regexp::MULTILINE != 0 - ignorecase = flags & Regexp::IGNORECASE != 0 - extended = flags & Regexp::EXTENDED != 0 - - new(multiline, ignorecase, extended) - end - - attr_reader :multiline, :ignorecase, :extended - - def initialize(*args) - if args.first.is_a?(Hash) - @multiline = args[0][:multiline] - @ignorecase = args[0][:ignorecase] - @extended = args[0][:extended] - else - @multiline = args[0] - @ignorecase = args[1] - @extended = args[2] - end - end - - def any?(explicit = false) - if explicit - !multiline.nil? || !ignorecase.nil? || !extended.nil? - else - multiline || ignorecase || extended - end - end - - def to_h(explicit = false) - if explicit - options = {} - options[:multiline] = multiline unless multiline.nil? - options[:ignorecase] = ignorecase unless ignorecase.nil? - options[:extended] = extended unless extended.nil? - options - else - { :multiline => multiline, - :ignorecase => ignorecase, - :extended => extended } - end - end - - def to_i - flag = 0 - flag |= Regexp::MULTILINE if multiline - flag |= Regexp::IGNORECASE if ignorecase - flag |= Regexp::EXTENDED if extended - flag - end - end -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb deleted file mode 100644 index 0bb9b87e9c..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb +++ /dev/null @@ -1,415 +0,0 @@ -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.6 -# from Racc grammer file "". -# - -require 'racc/parser.rb' -module Regin - class Parser < Racc::Parser #:nodoc: all - -def self.parse_regexp(regexp) - options = Options.from_int(regexp.options) - - parser = new - parser.options_stack << options.to_h - - expression = parser.scan_str(regexp.source) - expression = expression.dup(options.to_h) if options.any? - expression -end - -attr_accessor :options_stack - -def initialize - @capture_index = 0 - @capture_index_stack = [] - @options_stack = [] -end - -##### State transition tables begin ### - -racc_action_table = [ - 2, 18, 19, 19, 8, 10, 11, 13, 48, 19, - 2, 45, 3, 5, 8, 10, 11, 13, 64, 47, - 2, 55, 3, 5, 8, 10, 11, 13, 29, 19, - 2, 16, 3, 5, 8, 10, 11, 13, 61, 19, - 2, 63, 3, 5, 8, 10, 11, 13, 60, 36, - 2, 34, 3, 5, 8, 10, 11, 13, 28, 49, - 2, nil, 3, 5, 8, 10, 11, 13, nil, nil, - 2, nil, 3, 5, 8, 10, 11, 13, nil, 26, - 42, 43, 3, 5, 37, 38, 40, 21, 44, 37, - 38, 40, 22, 23, 24, 14, nil, 15, 31, 32, - 16, nil, 33, 46, 32, nil, nil, 33, 51, 37, - 38, 40, 58, 37, 38, 40, 37, 38, 40, 37, - 38, 40, 37, 38, 40, 37, 38, 40, 37, 38, - 40 ] - -racc_action_check = [ - 0, 4, 27, 4, 0, 0, 0, 0, 36, 56, - 49, 27, 0, 0, 49, 49, 49, 49, 56, 36, - 43, 48, 49, 49, 43, 43, 43, 43, 15, 53, - 6, 15, 43, 43, 6, 6, 6, 6, 53, 52, - 42, 55, 6, 6, 42, 42, 42, 42, 52, 24, - 35, 18, 42, 42, 35, 35, 35, 35, 14, 39, - 19, nil, 35, 35, 19, 19, 19, 19, nil, nil, - 13, nil, 19, 19, 13, 13, 13, 13, nil, 13, - 26, 26, 13, 13, 54, 54, 54, 9, 26, 26, - 26, 26, 9, 9, 9, 2, nil, 2, 17, 17, - 2, nil, 17, 30, 30, nil, nil, 30, 41, 41, - 41, 41, 50, 50, 50, 50, 44, 44, 44, 59, - 59, 59, 58, 58, 58, 51, 51, 51, 62, 62, - 62 ] - -racc_action_pointer = [ - -3, nil, 91, nil, 1, nil, 27, nil, nil, 75, - nil, nil, nil, 67, 53, 22, nil, 93, 51, 57, - nil, nil, nil, nil, 40, nil, 67, 0, nil, nil, - 98, nil, nil, nil, nil, 47, -1, nil, nil, 46, - nil, 87, 37, 17, 94, nil, nil, nil, 12, 7, - 91, 103, 37, 27, 62, 21, 7, nil, 100, 97, - nil, nil, 106, nil, nil, nil, nil, nil ] - -racc_action_default = [ - -37, -13, -37, -19, -37, -20, -2, -4, -11, -6, - -12, -14, -7, -37, -37, -29, -28, -37, -37, -37, - -3, -23, -21, -22, -37, -5, -37, -37, -8, -29, - -37, -9, -27, -26, 68, -1, -37, -34, -35, -37, - -36, -37, -37, -37, -37, -15, -10, -25, -37, -37, - -37, -37, -37, -37, -37, -37, -37, -33, -37, -37, - -17, -18, -37, -24, -16, -32, -31, -30 ] - -racc_goto_table = [ - 4, 41, 20, 35, 17, 39, 25, nil, nil, nil, - nil, nil, nil, 27, nil, nil, 50, 30, nil, 54, - nil, nil, nil, nil, nil, 57, 59, nil, nil, 62, - nil, 20, nil, 65, 66, nil, nil, 67, nil, nil, - nil, nil, 52, 53, nil, nil, nil, nil, nil, 56 ] - -racc_goto_check = [ - 1, 10, 3, 2, 7, 9, 5, nil, nil, nil, - nil, nil, nil, 1, nil, nil, 10, 7, nil, 10, - nil, nil, nil, nil, nil, 10, 10, nil, nil, 10, - nil, 3, nil, 10, 10, nil, nil, 10, nil, nil, - nil, nil, 1, 1, nil, nil, nil, nil, nil, 1 ] - -racc_goto_pointer = [ - nil, 0, -16, -4, nil, -3, nil, 2, nil, -21, - -25 ] - -racc_goto_default = [ - nil, nil, 6, 7, 9, nil, 12, nil, 1, nil, - nil ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 3, 26, :_reduce_1, - 1, 26, :_reduce_2, - 2, 27, :_reduce_3, - 1, 27, :_reduce_4, - 2, 28, :_reduce_5, - 1, 28, :_reduce_none, - 1, 29, :_reduce_none, - 3, 29, :_reduce_8, - 3, 29, :_reduce_9, - 4, 29, :_reduce_10, - 1, 29, :_reduce_11, - 1, 29, :_reduce_12, - 1, 29, :_reduce_13, - 1, 29, :_reduce_14, - 3, 31, :_reduce_15, - 6, 31, :_reduce_16, - 5, 31, :_reduce_17, - 5, 31, :_reduce_18, - 1, 33, :_reduce_none, - 1, 33, :_reduce_none, - 1, 30, :_reduce_none, - 1, 30, :_reduce_none, - 1, 30, :_reduce_none, - 5, 30, :_reduce_24, - 3, 30, :_reduce_25, - 2, 32, :_reduce_26, - 2, 32, :_reduce_27, - 1, 32, :_reduce_none, - 1, 32, :_reduce_none, - 4, 34, :_reduce_30, - 4, 34, :_reduce_31, - 4, 34, :_reduce_32, - 3, 34, :_reduce_33, - 1, 35, :_reduce_34, - 1, 35, :_reduce_35, - 1, 35, :_reduce_36 ] - -racc_reduce_n = 37 - -racc_shift_n = 68 - -racc_token_table = { - false => 0, - :error => 1, - :BAR => 2, - :LBRACK => 3, - :CTYPE => 4, - :RBRACK => 5, - :NEGATE => 6, - :CCLASS => 7, - :DOT => 8, - :CHAR => 9, - :LPAREN => 10, - :RPAREN => 11, - :QMARK => 12, - :COLON => 13, - :NAME => 14, - :L_ANCHOR => 15, - :R_ANCHOR => 16, - :STAR => 17, - :PLUS => 18, - :LCURLY => 19, - :RCURLY => 20, - :MINUS => 21, - :MULTILINE => 22, - :IGNORECASE => 23, - :EXTENDED => 24 } - -racc_nt_base = 25 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ - "$end", - "error", - "BAR", - "LBRACK", - "CTYPE", - "RBRACK", - "NEGATE", - "CCLASS", - "DOT", - "CHAR", - "LPAREN", - "RPAREN", - "QMARK", - "COLON", - "NAME", - "L_ANCHOR", - "R_ANCHOR", - "STAR", - "PLUS", - "LCURLY", - "RCURLY", - "MINUS", - "MULTILINE", - "IGNORECASE", - "EXTENDED", - "$start", - "expression", - "subexpression", - "quantified_atom", - "atom", - "quantifier", - "group", - "bracket_expression", - "anchor", - "options", - "modifier" ] - -Racc_debug_parser = false - -##### State transition tables end ##### - -# reduce 0 omitted - -def _reduce_1(val, _values, result) - # TODO remove this conditional by breaking - # it into another production - if val[0][0].is_a?(Regin::Alternation) - alt = val[0][0] + [Expression.new(val[2])] - else - alt = Alternation.new(val[0], Expression.new(val[2])) - end - result = Expression.new(alt) - - result -end - -def _reduce_2(val, _values, result) - result = Expression.new(val[0]) - result -end - -def _reduce_3(val, _values, result) - result = val[0] + [val[1]] - result -end - -def _reduce_4(val, _values, result) - result = [val[0]] - result -end - -def _reduce_5(val, _values, result) - result = val[0].dup(:quantifier => val[1]) - result -end - -# reduce 6 omitted - -# reduce 7 omitted - -def _reduce_8(val, _values, result) - result = CharacterClass.new(val[1]) - result -end - -def _reduce_9(val, _values, result) - result = CharacterClass.new(val[1]) - result -end - -def _reduce_10(val, _values, result) - result = CharacterClass.new(val[2], :negate => true) - result -end - -def _reduce_11(val, _values, result) - result = CharacterClass.new(val[0]) - result -end - -def _reduce_12(val, _values, result) - result = CharacterClass.new('.') - result -end - -def _reduce_13(val, _values, result) - result = Anchor.new(val[0]) - result -end - -def _reduce_14(val, _values, result) - result = Character.new(val[0]) - result -end - -def _reduce_15(val, _values, result) - result = Group.new(val[1], :index => @capture_index_stack.pop) - - result -end - -def _reduce_16(val, _values, result) - result = Group.new(val[4], val[2].merge(:capture => false)) - @options_stack.pop - - result -end - -def _reduce_17(val, _values, result) - result = Group.new(val[3], :capture => false); - - result -end - -def _reduce_18(val, _values, result) - result = Group.new(val[3], :name => val[2], :index => @capture_index_stack.pop); - - result -end - -# reduce 19 omitted - -# reduce 20 omitted - -# reduce 21 omitted - -# reduce 22 omitted - -# reduce 23 omitted - -def _reduce_24(val, _values, result) - result = val.join - result -end - -def _reduce_25(val, _values, result) - result = val.join - result -end - -def _reduce_26(val, _values, result) - result = val.join - result -end - -def _reduce_27(val, _values, result) - result = val.join - result -end - -# reduce 28 omitted - -# reduce 29 omitted - -def _reduce_30(val, _values, result) - @options_stack << result = { val[1] => false, val[2] => false, val[3] => false } - - result -end - -def _reduce_31(val, _values, result) - @options_stack << result = { val[0] => true, val[2] => false, val[3] => false } - - result -end - -def _reduce_32(val, _values, result) - @options_stack << result = { val[0] => true, val[1] => true, val[3] => false } - - result -end - -def _reduce_33(val, _values, result) - @options_stack << result = { val[0] => true, val[1] => true, val[2] => true } - - result -end - -def _reduce_34(val, _values, result) - result = :multiline - result -end - -def _reduce_35(val, _values, result) - result = :ignorecase - result -end - -def _reduce_36(val, _values, result) - result = :extended - result -end - -def _reduce_none(val, _values, result) - val[0] -end - - end # class Parser -end # module Regin - -require 'regin/tokenizer' diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb deleted file mode 100644 index 59e4ffb611..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb +++ /dev/null @@ -1,213 +0,0 @@ -#-- -# DO NOT MODIFY!!!! -# This file is automatically generated by rex 1.0.5.beta1 -# from lexical definition file "lib/regin/tokenizer.rex". -#++ - -require 'racc/parser' -class Regin::Parser < Racc::Parser - require 'strscan' - - class ScanError < StandardError ; end - - attr_reader :lineno - attr_reader :filename - attr_accessor :state - - def scan_setup(str) - @ss = StringScanner.new(str) - @lineno = 1 - @state = nil - end - - def action - yield - end - - def scan_str(str) - scan_setup(str) - do_parse - end - alias :scan :scan_str - - def load_file( filename ) - @filename = filename - open(filename, "r") do |f| - scan_setup(f.read) - end - end - - def scan_file( filename ) - load_file(filename) - do_parse - end - - - def next_token - return if @ss.eos? - - text = @ss.peek(1) - @lineno += 1 if text == "\n" - token = case @state - when nil - case - when (text = @ss.scan(/\\[dDsSwW]/)) - action { [:CCLASS, text] } - - when (text = @ss.scan(/\^|\\A/)) - action { [:L_ANCHOR, text] } - - when (text = @ss.scan(/\$|\\Z/)) - action { [:R_ANCHOR, text] } - - when (text = @ss.scan(/<(\w+)>/)) - action { [:NAME, @ss[1]] } - - when (text = @ss.scan(/\(/)) - action { - @capture_index_stack << @capture_index - @capture_index += 1 - @state = :OPTIONS if @ss.peek(1) == '?'; - [:LPAREN, text] - } - - - when (text = @ss.scan(/\)/)) - action { [:RPAREN, text] } - - when (text = @ss.scan(/\[/)) - action { @state = :CCLASS; [:LBRACK, text] } - - when (text = @ss.scan(/\{/)) - action { [:LCURLY, text] } - - when (text = @ss.scan(/\}/)) - action { [:RCURLY, text] } - - when (text = @ss.scan(/\|/)) - action { [:BAR, text] } - - when (text = @ss.scan(/\./)) - action { [:DOT, text] } - - when (text = @ss.scan(/\?/)) - action { [:QMARK, text] } - - when (text = @ss.scan(/\+(?:\?)/)) - action { [:PLUS, text] } - - when (text = @ss.scan(/\*(?:\?)/)) - action { [:STAR, text] } - - when (text = @ss.scan(/\#/)) - action { - if @options_stack[-1][:extended] - @state = :COMMENT; - next_token - else - [:CHAR, text] - end - } - - - when (text = @ss.scan(/\s|\n/)) - action { - if @options_stack[-1][:extended] - next_token - else - [:CHAR, text] - end - } - - - when (text = @ss.scan(/\\(.)/)) - action { [:CHAR, @ss[1]] } - - when (text = @ss.scan(/./)) - action { [:CHAR, text] } - - else - text = @ss.string[@ss.pos .. -1] - raise ScanError, "can not match: '" + text + "'" - end # if - - when :CCLASS - case - when (text = @ss.scan(/\]/)) - action { @state = nil; [:RBRACK, text] } - - when (text = @ss.scan(/\^/)) - action { [:NEGATE, text] } - - when (text = @ss.scan(/:(alnum|alpha|ascii|blank|cntrl|digit|graph|lower|print|punct|space|upper|word|xdigit):/)) - action { [:CTYPE, text] } - - when (text = @ss.scan(/\\-/)) - action { [:CHAR, text] } - - when (text = @ss.scan(/\\(.)/)) - action { [:CHAR, @ss[1]] } - - when (text = @ss.scan(/./)) - action { [:CHAR, text] } - - else - text = @ss.string[@ss.pos .. -1] - raise ScanError, "can not match: '" + text + "'" - end # if - - when :OPTIONS - case - when (text = @ss.scan(/\?/)) - action { - @state = nil unless @ss.peek(1) =~ /-|m|i|x|:/ - [:QMARK, text] - } - - - when (text = @ss.scan(/\-/)) - action { [:MINUS, text] } - - when (text = @ss.scan(/m/)) - action { [:MULTILINE, text] } - - when (text = @ss.scan(/i/)) - action { [:IGNORECASE, text] } - - when (text = @ss.scan(/x/)) - action { [:EXTENDED, text] } - - when (text = @ss.scan(/\:/)) - action { - @capture_index_stack.pop - @capture_index -= 1 - @state = nil; - [:COLON, text] - } - - - else - text = @ss.string[@ss.pos .. -1] - raise ScanError, "can not match: '" + text + "'" - end # if - - when :COMMENT - case - when (text = @ss.scan(/\n/)) - action { @state = nil; next_token } - - when (text = @ss.scan(/./)) - action { next_token } - - else - text = @ss.string[@ss.pos .. -1] - raise ScanError, "can not match: '" + text + "'" - end # if - - else - raise ScanError, "undefined state: '" + state.to_s + "'" - end # case state - token - end # def next_token - -end # class diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb deleted file mode 100644 index 7ad2a5a25e..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Regin - Version = '0.3.3' -end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb deleted file mode 100644 index a3688b102e..0000000000 --- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Rack - module Mount - Version = '0.6.6.pre' - end -end diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 7a44ef7c77..69383f9a73 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -149,7 +149,7 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This * mime-types-1.16.gem * polyglot-0.3.1.gem * rack-1.1.0.gem -* rack-mount-0.6.5.gem +* rack-mount-0.6.9.gem * rack-test-0.5.4.gem * rails-3.0.0.beta4.gem * railties-3.0.0.beta4.gem -- cgit v1.2.3 From fcb230144b041a95c8e743921e22f8df56949993 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 15 Jul 2010 16:37:12 -0300 Subject: Bump up nokogiri, memcache-client, sqlite3-ruby, fcgi, mail and tzinfo --- Gemfile | 10 +++++----- actionmailer/actionmailer.gemspec | 2 +- actionpack/actionpack.gemspec | 2 +- activerecord/activerecord.gemspec | 2 +- railties/guides/source/initialization.textile | 11 ++++++----- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Gemfile b/Gemfile index fe6ef9aa8a..e915135996 100644 --- a/Gemfile +++ b/Gemfile @@ -16,21 +16,21 @@ end if mri || RUBY_ENGINE == "rbx" gem 'json' gem 'yajl-ruby' - gem "nokogiri", ">= 1.4.0" + gem "nokogiri", ">= 1.4.2" elsif RUBY_ENGINE == "jruby" gem "ruby-debug" gem "jruby-openssl" end # AS -gem "memcache-client", ">= 1.7.5" +gem "memcache-client", ">= 1.8.5" # AM gem "text-format", "~> 1.0.0" # AR if mri || RUBY_ENGINE == "rbx" - gem "sqlite3-ruby", "~> 1.3.0", :require => 'sqlite3' + gem "sqlite3-ruby", "~> 1.3.1", :require => 'sqlite3' group :db do gem "pg", ">= 0.9.0" @@ -46,8 +46,8 @@ elsif RUBY_ENGINE == "jruby" end if ENV['CI'] - gem "nokogiri", ">= 1.4.0" + gem "nokogiri", ">= 1.4.2" # fcgi gem doesn't compile on 1.9 - gem "fcgi", ">= 0.8.7" if RUBY_VERSION < '1.9.0' + gem "fcgi", ">= 0.8.8" if RUBY_VERSION < '1.9.0' end diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 2b7c21b3f2..fa0ee778c9 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -20,5 +20,5 @@ Gem::Specification.new do |s| s.has_rdoc = true s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.2.3') + s.add_dependency('mail', '~> 2.2.5') end diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 3531906341..9ce7ba5f02 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -26,6 +26,6 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.4') s.add_dependency('rack-mount', '~> 0.6.9') - s.add_dependency('tzinfo', '~> 0.3.16') + s.add_dependency('tzinfo', '~> 0.3.22') s.add_dependency('erubis', '~> 2.6.6') end diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 5aea992801..ce10404feb 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) s.add_dependency('arel', '~> 0.4.0') - s.add_dependency('tzinfo', '~> 0.3.16') + s.add_dependency('tzinfo', '~> 0.3.22') end diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 69383f9a73..305602e57d 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -141,20 +141,21 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This * activesupport-3.0.0.beta4.gem * arel-0.4.0.gem * builder-2.1.2.gem -* bundler-1.0.0.beta.2.gem +* bundler-1.0.0.beta.5.gem * erubis-2.6.6.gem * i18n-0.4.1.gem -* mail-2.2.4.gem -* memcache-client-1.8.3.gem +* mail-2.2.5.gem +* memcache-client-1.8.5.gem * mime-types-1.16.gem +* nokogiri-1.4.2.gem * polyglot-0.3.1.gem -* rack-1.1.0.gem +* rack-1.2.1.gem * rack-mount-0.6.9.gem * rack-test-0.5.4.gem * rails-3.0.0.beta4.gem * railties-3.0.0.beta4.gem * rake-0.8.7.gem -* sqlite3-ruby-1.3.0.gem +* sqlite3-ruby-1.3.1.gem * text-format-1.0.0.gem * text-hyphen-1.0.0.gem * thor-0.13.7.gem -- cgit v1.2.3 From 06c74cf5fe7edc55f43e36a750a8c536014a52c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Tue, 13 Jul 2010 09:57:15 +0200 Subject: Bump bundler to 1.0.0.beta.5 [#5102 state:committed] Signed-off-by: Jeremy Kemper --- rails.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rails.gemspec b/rails.gemspec index 2d10ce78a0..e3f811966c 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_dependency('activeresource', version) s.add_dependency('actionmailer', version) s.add_dependency('railties', version) - s.add_dependency('bundler', '>= 1.0.0.beta.3') + s.add_dependency('bundler', '>= 1.0.0.beta.5') end -- cgit v1.2.3 From c09cd192aa118fc8300ac787562a9c23afa93007 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Thu, 15 Jul 2010 18:41:29 -0300 Subject: Small fix in cookie docs and trailing whitespaces --- actionpack/lib/action_dispatch/middleware/cookies.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index d69ba39728..4d33cd3b0c 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -45,10 +45,10 @@ module ActionDispatch # * :value - The cookie's value or list of values (as an array). # * :path - The path for which this cookie applies. Defaults to the root # of the application. - # * :domain - The domain for which this cookie applies so you can - # restrict to the domain level. If you use a schema like www.example.com + # * :domain - The domain for which this cookie applies so you can + # restrict to the domain level. If you use a schema like www.example.com # and want to share session with user.example.com set :domain - # to :all. Make sure to specify the :domain option with + # to :all. Make sure to specify the :domain option with # :all again when deleting keys. # # :domain => nil # Does not sets cookie domain. (default) @@ -63,7 +63,7 @@ module ActionDispatch class Cookies HTTP_HEADER = "Set-Cookie".freeze TOKEN_KEY = "action_dispatch.secret_token".freeze - + # Raised when storing more than 4K of session data. class CookieOverflow < StandardError; end @@ -101,7 +101,7 @@ module ActionDispatch def handle_options(options) #:nodoc: options[:path] ||= "/" - + if options[:domain] == :all @host =~ DOMAIN_REGEXP options[:domain] = ".#{$2}.#{$3}" @@ -122,7 +122,7 @@ module ActionDispatch value = super(key.to_s, value) handle_options(options) - + @set_cookies[key] = options @delete_cookies.delete(key) value @@ -151,7 +151,7 @@ module ActionDispatch # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: # # cookies.permanent.signed[:remember_me] = current_user.id - # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT def permanent @permanent ||= PermanentCookieJar.new(self, @secret) end -- cgit v1.2.3 From ea0bf4e664ca6faf7323d3e2a241b6e6d0b86430 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 16 Jul 2010 05:36:49 +0800 Subject: CI should bundle update instead of install so gems are properly updated --- ci/ci_build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 0a39aa4f87..f9933cb922 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -27,7 +27,7 @@ cd root_dir do puts puts "[CruiseControl] Bundling RubyGems" puts - build_results[:bundle] = system 'rm -rf ~/.bundle; env CI=1 bundle install' + build_results[:bundle] = system 'rm -rf ~/.bundle; env CI=1 bundle update' end cd "#{root_dir}/activesupport" do -- cgit v1.2.3 From 181e0d326de8ce212d951d0e8a8ca0ba478ac5a4 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 16 Jul 2010 00:47:34 +0200 Subject: AS guide: revised the docs of class_attribute and cattr_* macros --- .../source/active_support_core_extensions.textile | 56 ++++++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 097d51e007..a0ed8d6a90 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -981,7 +981,9 @@ h3. Extensions to +Class+ h4. Class Attributes -The method +Class#class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy: +h5. +class_attribute+ + +The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy: class A @@ -1005,17 +1007,50 @@ A.x # => :a B.x # => :b -For example that's the way the +allow_forgery_protection+ flag is implemented for controllers: +For example +ActionMailer::Base+ defines: + + +class_attribute :default_params +self.default_params = { + :mime_version => "1.0", + :charset => "UTF-8", + :content_type => "text/plain", + :parts_order => [ "text/plain", "text/enriched", "text/html" ] +}.freeze + + +They can be also accessed and overridden at the instance level: + + +A.x = 1 + +a1 = A.new +a2 = A.new +a2.x = 2 + +a1.x # => 1, comes from A +a2.x # => 2, overridden in a2 + + +The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to false, as in -class_attribute :allow_forgery_protection -self.allow_forgery_protection = true +module AcitveRecord + class Base + class_attribute :table_name_prefix, :instance_writer => false + self.table_name_prefix = "" + end +end -For convenience +class_attribute+ defines also a predicate, so that declaration also generates +allow_forgery_protection?+. Such predicate returns the double boolean negation of the value. +A model may find that option useful as a way to prevent mass-assignment from setting the attribute. + +For convenience +class_attribute+ defines also an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called +x?+. NOTE: Defined in +active_support/core_ext/class/attribute.rb+ +h5. +cattr_reader+, +cattr_writer+, and +cattr_accessor+ + The macros +cattr_reader+, +cattr_writer+, and +cattr_accessor+ are analogous to their +attr_*+ counterparts but for classes. They initialize a class variable to +nil+ unless it already exists, and generate the corresponding class methods to access it: @@ -1026,17 +1061,18 @@ class MysqlAdapter < AbstractAdapter end -Instance methods are created as well for convenience. For example given +Instance methods are created as well for convenience, they are just proxies to the class attribute. So, instances can change the class attribute, but cannot override it as it happens with +class_attribute+ (see above). For example given -module ActionController +module ActionView class Base - cattr_accessor :logger + cattr_accessor :field_error_proc + @@field_error_proc = Proc.new{ ... } end end -we can access +logger+ in actions. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): +we can access +field_error_proc+ in views. The generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): module ActiveRecord @@ -1047,7 +1083,7 @@ module ActiveRecord end -A model may find that option useful as a way to prevent mass-assignment from setting the attribute. +A model may find that option useful as a way to prevent mass-assignment from setting the attribute. NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+. -- cgit v1.2.3 From 1fcf4e8ecbe4db2443a88d1e06c06667cff5b762 Mon Sep 17 00:00:00 2001 From: David Genord II Date: Tue, 13 Jul 2010 18:14:26 -0400 Subject: JoinDependency#graft does not properly set parent join [#5124 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/associations.rb | 2 +- .../test/cases/associations/cascaded_eager_loading_test.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9fe8d54f02..d67df64f59 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1761,7 +1761,7 @@ module ActiveRecord def graft(*associations) associations.each do |association| join_associations.detect {|a| association == a} || - build(association.reflection.name, association.find_parent_in(self), association.join_class) + build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class) end self end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 9c5dcc2ad9..67eb6403c8 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -46,6 +46,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first end + def test_eager_associatoin_loading_with_join_for_count + authors = Author.joins(:special_posts).includes([:posts, :categorizations]) + + assert_nothing_raised { authors.count } + assert_queries(3) { authors.all } + end + def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id") assert_equal 2, authors.size -- cgit v1.2.3 From 130bf3c9edf89de78203c02c5f76f9ea2b7b46a5 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 15 Jul 2010 23:49:17 +0100 Subject: Fix data loading from the performance script --- activerecord/examples/performance.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb index f7d358337c..a985cfcb66 100644 --- a/activerecord/examples/performance.rb +++ b/activerecord/examples/performance.rb @@ -58,7 +58,7 @@ end sqlfile = File.expand_path("../performance.sql", __FILE__) if File.exists?(sqlfile) - mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 } + mysql_bin = %w[mysql mysql5].detect { |bin| `which #{bin}`.length > 0 } `#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}` else puts 'Generating data...' -- cgit v1.2.3 From c9630d9d87ad0b4a0fc732034b6a106777e257ca Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 16 Jul 2010 09:58:01 -0400 Subject: adding proper markup to comment --- activemodel/lib/active_model/serializers/json.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 918cd0ab76..500b2399a3 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -19,8 +19,8 @@ module ActiveModel # passed through +options+. # # The option ActiveModel::Base.include_root_in_json controls the - # top-level behavior of to_json. It is true by default. When it is true, - # to_json will emit a single root node named after the object's type. For example: + # top-level behavior of to_json. It is true by default. When it is true, + # to_json will emit a single root node named after the object's type. For example: # # konata = User.find(1) # konata.to_json -- cgit v1.2.3 From 1f2a5199cb06b9898c9ba0a10350f4d522035a56 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 15 Jul 2010 08:57:24 -0400 Subject: moving persistence related tests to a new file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before refactoring test result from AR: 2291 tests, 7180 assertions, 0 failures, 0 errors After the refactoring test result from AR: 2291 tests, 7180 assertions, 0 failures, 0 errors Signed-off-by: José Valim --- activerecord/test/cases/base_test.rb | 327 ------------------------- activerecord/test/cases/persistence_test.rb | 357 ++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+), 327 deletions(-) create mode 100644 activerecord/test/cases/persistence_test.rb diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 87f46d2992..831dd446ad 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -161,48 +161,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes end - def test_create - topic = Topic.new - topic.title = "New Topic" - topic.save - topic_reloaded = Topic.find(topic.id) - assert_equal("New Topic", topic_reloaded.title) - end - - def test_save! - topic = Topic.new(:title => "New Topic") - assert topic.save! - - reply = WrongReply.new - assert_raise(ActiveRecord::RecordInvalid) { reply.save! } - end - - def test_save_null_string_attributes - topic = Topic.find(1) - topic.attributes = { "title" => "null", "author_name" => "null" } - topic.save! - topic.reload - assert_equal("null", topic.title) - assert_equal("null", topic.author_name) - end - - def test_save_nil_string_attributes - topic = Topic.find(1) - topic.title = nil - topic.save! - topic.reload - assert_nil topic.title - end - - def test_save_for_record_with_only_primary_key - minimalistic = Minimalistic.new - assert_nothing_raised { minimalistic.save } - end - - def test_save_for_record_with_only_primary_key_that_is_provided - assert_nothing_raised { Minimalistic.create!(:id => 2) } - end - def test_hashes_not_mangled new_topic = { :title => "New Topic" } new_topic_values = { :title => "AnotherTopic" } @@ -214,78 +172,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal new_topic_values[:title], topic.title end - def test_create_many - topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) - assert_equal 2, topics.size - assert_equal "first", topics.first.title - end - - def test_create_columns_not_equal_attributes - topic = Topic.new - topic.title = 'Another New Topic' - topic.send :write_attribute, 'does_not_exist', 'test' - assert_nothing_raised { topic.save } - end - def test_create_through_factory topic = Topic.create("title" => "New Topic") topicReloaded = Topic.find(topic.id) assert_equal(topic, topicReloaded) end - def test_create_through_factory_with_block - topic = Topic.create("title" => "New Topic") do |t| - t.author_name = "David" - end - topicReloaded = Topic.find(topic.id) - assert_equal("New Topic", topic.title) - assert_equal("David", topic.author_name) - end - - def test_create_many_through_factory_with_block - topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t| - t.author_name = "David" - end - assert_equal 2, topics.size - topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id) - assert_equal "first", topic1.title - assert_equal "David", topic1.author_name - assert_equal "second", topic2.title - assert_equal "David", topic2.author_name - end - - def test_update - topic = Topic.new - topic.title = "Another New Topic" - topic.written_on = "2003-12-12 23:23:00" - topic.save - topicReloaded = Topic.find(topic.id) - assert_equal("Another New Topic", topicReloaded.title) - - topicReloaded.title = "Updated topic" - topicReloaded.save - - topicReloadedAgain = Topic.find(topic.id) - - assert_equal("Updated topic", topicReloadedAgain.title) - end - - def test_update_columns_not_equal_attributes - topic = Topic.new - topic.title = "Still another topic" - topic.save - - topicReloaded = Topic.find(topic.id) - topicReloaded.title = "A New Topic" - topicReloaded.send :write_attribute, 'does_not_exist', 'test' - assert_nothing_raised { topicReloaded.save } - end - - def test_update_for_record_with_only_primary_key - minimalistic = minimalistics(:first) - assert_nothing_raised { minimalistic.save } - end - def test_write_attribute topic = Topic.new topic.send(:write_attribute, :title, "Still another topic") @@ -393,7 +285,6 @@ class BasicsTest < ActiveRecord::TestCase assert !object.int_value? end - def test_non_attribute_access_and_assignment topic = Topic.new assert !topic.respond_to?("mumbo") @@ -497,29 +388,6 @@ class BasicsTest < ActiveRecord::TestCase assert topic.instance_variable_get("@custom_approved") end - def test_delete - topic = Topic.find(1) - assert_equal topic, topic.delete, 'topic.delete did not return self' - assert topic.frozen?, 'topic not frozen after delete' - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } - end - - def test_delete_doesnt_run_callbacks - Topic.find(1).delete - assert_not_nil Topic.find(2) - end - - def test_destroy - topic = Topic.find(1) - assert_equal topic, topic.destroy, 'topic.destroy did not return self' - assert topic.frozen?, 'topic not frozen after destroy' - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } - end - - def test_record_not_found_exception - assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) } - end - def test_initialize_with_attributes topic = Topic.new({ "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23" @@ -688,33 +556,6 @@ class BasicsTest < ActiveRecord::TestCase assert Topic.find(2).approved? end - def test_update_all - assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'") - assert_equal "bulk updated!", Topic.find(1).content - assert_equal "bulk updated!", Topic.find(2).content - - assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!']) - assert_equal "bulk updated again!", Topic.find(1).content - assert_equal "bulk updated again!", Topic.find(2).content - - assert_equal Topic.count, Topic.update_all(['content = ?', nil]) - assert_nil Topic.find(1).content - end - - def test_update_all_with_hash - assert_not_nil Topic.find(1).last_read - assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil) - assert_equal "bulk updated with hash!", Topic.find(1).content - assert_equal "bulk updated with hash!", Topic.find(2).content - assert_nil Topic.find(1).last_read - assert_nil Topic.find(2).last_read - end - - def test_update_all_with_non_standard_table_name - assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1]) - assert_equal 0, WarehouseThing.find(1).value - end - if current_adapter?(:MysqlAdapter) def test_update_all_with_order_and_limit assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC') @@ -861,119 +702,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end - def test_delete_new_record - client = Client.new - client.delete - assert client.frozen? - end - - def test_delete_record_with_associations - client = Client.find(3) - client.delete - assert client.frozen? - assert_kind_of Firm, client.firm - assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } - end - - def test_destroy_new_record - client = Client.new - client.destroy - assert client.frozen? - end - - def test_destroy_record_with_associations - client = Client.find(3) - client.destroy - assert client.frozen? - assert_kind_of Firm, client.firm - assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } - end - - def test_update_attribute - assert !Topic.find(1).approved? - Topic.find(1).update_attribute("approved", true) - assert Topic.find(1).approved? - - Topic.find(1).update_attribute(:approved, false) - assert !Topic.find(1).approved? - end - - def test_update_attribute_with_one_changed_and_one_updated - t = Topic.order('id').limit(1).first - title, author_name = t.title, t.author_name - t.author_name = 'John' - t.update_attribute(:title, 'super_title') - assert_equal 'John', t.author_name - assert_equal 'super_title', t.title - assert t.changed?, "topic should have changed" - assert t.author_name_changed?, "author_name should have changed" - assert !t.title_changed?, "title should not have changed" - assert_nil t.title_change, 'title change should be nil' - assert_equal ['author_name'], t.changed - - t.reload - assert_equal 'David', t.author_name - assert_equal 'super_title', t.title - end - - def test_update_attribute_with_one_updated - t = Topic.first - title = t.title - t.update_attribute(:title, 'super_title') - assert_equal 'super_title', t.title - assert !t.changed?, "topic should not have changed" - assert !t.title_changed?, "title should not have changed" - assert_nil t.title_change, 'title change should be nil' - - t.reload - assert_equal 'super_title', t.title - end - - def test_update_attribute_for_udpated_at_on - developer = Developer.find(1) - updated_at = developer.updated_at - developer.update_attribute(:salary, 80001) - assert_not_equal updated_at, developer.updated_at - developer.reload - assert_not_equal updated_at, developer.updated_at - end - - def test_update_attributes - topic = Topic.find(1) - assert !topic.approved? - assert_equal "The First Topic", topic.title - - topic.update_attributes("approved" => true, "title" => "The First Topic Updated") - topic.reload - assert topic.approved? - assert_equal "The First Topic Updated", topic.title - - topic.update_attributes(:approved => false, :title => "The First Topic") - topic.reload - assert !topic.approved? - assert_equal "The First Topic", topic.title - end - - def test_update_attributes! - Reply.validates_presence_of(:title) - reply = Reply.find(2) - assert_equal "The Second Topic of the day", reply.title - assert_equal "Have a nice day", reply.content - reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening") - reply.reload - assert_equal "The Second Topic of the day updated", reply.title - assert_equal "Have a nice evening", reply.content - - reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day") - reply.reload - assert_equal "The Second Topic of the day", reply.title - assert_equal "Have a nice day", reply.content - - assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") } - ensure - Reply.reset_callbacks(:validate) - end def test_readonly_attributes assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes @@ -1234,35 +963,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal false, Topic.find(1).new_record? end - def test_destroyed_returns_boolean - developer = Developer.first - assert_equal false, developer.destroyed? - developer.destroy - assert_equal true, developer.destroyed? - - developer = Developer.last - assert_equal false, developer.destroyed? - developer.delete - assert_equal true, developer.destroyed? - end - - def test_persisted_returns_boolean - developer = Developer.new(:name => "Jose") - assert_equal false, developer.persisted? - developer.save! - assert_equal true, developer.persisted? - - developer = Developer.first - assert_equal true, developer.persisted? - developer.destroy - assert_equal false, developer.persisted? - - developer = Developer.last - assert_equal true, developer.persisted? - developer.delete - assert_equal false, developer.persisted? - end - def test_clone topic = Topic.find(1) cloned_topic = nil @@ -1605,24 +1305,6 @@ class BasicsTest < ActiveRecord::TestCase end end - def test_class_level_destroy - should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") - Topic.find(1).replies << should_be_destroyed_reply - - Topic.destroy(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } - assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } - end - - def test_class_level_delete - should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") - Topic.find(1).replies << should_be_destroyed_reply - - Topic.delete(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } - assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) } - end - def test_increment_attribute assert_equal 50, accounts(:signals37).credit_limit accounts(:signals37).increment! :credit_limit @@ -2235,15 +1917,6 @@ class BasicsTest < ActiveRecord::TestCase ActiveRecord::Base.logger = original_logger end - def test_create_with_custom_timestamps - custom_datetime = 1.hour.ago.beginning_of_day - - %w(created_at created_on updated_at updated_on).each do |attribute| - parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime) - assert_equal custom_datetime, parrot[attribute] - end - end - def test_dup assert !Minimalistic.new.freeze.dup.frozen? end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb new file mode 100644 index 0000000000..fbfdd7b041 --- /dev/null +++ b/activerecord/test/cases/persistence_test.rb @@ -0,0 +1,357 @@ +require "cases/helper" +require 'models/post' +require 'models/author' +require 'models/topic' +require 'models/reply' +require 'models/category' +require 'models/company' +require 'models/customer' +require 'models/developer' +require 'models/project' +require 'models/default' +require 'models/auto_id' +require 'models/column_name' +require 'models/subscriber' +require 'models/keyboard' +require 'models/comment' +require 'models/minimalistic' +require 'models/warehouse_thing' +require 'models/parrot' +require 'models/loose_person' +require 'rexml/document' +require 'active_support/core_ext/exception' + +class PersistencesTest < ActiveRecord::TestCase + + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts + + def test_create + topic = Topic.new + topic.title = "New Topic" + topic.save + topic_reloaded = Topic.find(topic.id) + assert_equal("New Topic", topic_reloaded.title) + end + + def test_save! + topic = Topic.new(:title => "New Topic") + assert topic.save! + + reply = WrongReply.new + assert_raise(ActiveRecord::RecordInvalid) { reply.save! } + end + + def test_save_null_string_attributes + topic = Topic.find(1) + topic.attributes = { "title" => "null", "author_name" => "null" } + topic.save! + topic.reload + assert_equal("null", topic.title) + assert_equal("null", topic.author_name) + end + + def test_save_nil_string_attributes + topic = Topic.find(1) + topic.title = nil + topic.save! + topic.reload + assert_nil topic.title + end + + def test_save_for_record_with_only_primary_key + minimalistic = Minimalistic.new + assert_nothing_raised { minimalistic.save } + end + + def test_save_for_record_with_only_primary_key_that_is_provided + assert_nothing_raised { Minimalistic.create!(:id => 2) } + end + + def test_create_many + topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) + assert_equal 2, topics.size + assert_equal "first", topics.first.title + end + + def test_create_columns_not_equal_attributes + topic = Topic.new + topic.title = 'Another New Topic' + topic.send :write_attribute, 'does_not_exist', 'test' + assert_nothing_raised { topic.save } + end + + def test_create_through_factory_with_block + topic = Topic.create("title" => "New Topic") do |t| + t.author_name = "David" + end + topicReloaded = Topic.find(topic.id) + assert_equal("New Topic", topic.title) + assert_equal("David", topic.author_name) + end + + def test_create_many_through_factory_with_block + topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t| + t.author_name = "David" + end + assert_equal 2, topics.size + topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id) + assert_equal "first", topic1.title + assert_equal "David", topic1.author_name + assert_equal "second", topic2.title + assert_equal "David", topic2.author_name + end + + def test_update + topic = Topic.new + topic.title = "Another New Topic" + topic.written_on = "2003-12-12 23:23:00" + topic.save + topicReloaded = Topic.find(topic.id) + assert_equal("Another New Topic", topicReloaded.title) + + topicReloaded.title = "Updated topic" + topicReloaded.save + + topicReloadedAgain = Topic.find(topic.id) + + assert_equal("Updated topic", topicReloadedAgain.title) + end + + def test_update_columns_not_equal_attributes + topic = Topic.new + topic.title = "Still another topic" + topic.save + + topicReloaded = Topic.find(topic.id) + topicReloaded.title = "A New Topic" + topicReloaded.send :write_attribute, 'does_not_exist', 'test' + assert_nothing_raised { topicReloaded.save } + end + + def test_update_for_record_with_only_primary_key + minimalistic = minimalistics(:first) + assert_nothing_raised { minimalistic.save } + end + + def test_delete + topic = Topic.find(1) + assert_equal topic, topic.delete, 'topic.delete did not return self' + assert topic.frozen?, 'topic not frozen after delete' + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } + end + + def test_delete_doesnt_run_callbacks + Topic.find(1).delete + assert_not_nil Topic.find(2) + end + + def test_destroy + topic = Topic.find(1) + assert_equal topic, topic.destroy, 'topic.destroy did not return self' + assert topic.frozen?, 'topic not frozen after destroy' + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } + end + + def test_record_not_found_exception + assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) } + end + + def test_update_all + assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'") + assert_equal "bulk updated!", Topic.find(1).content + assert_equal "bulk updated!", Topic.find(2).content + + assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!']) + assert_equal "bulk updated again!", Topic.find(1).content + assert_equal "bulk updated again!", Topic.find(2).content + + assert_equal Topic.count, Topic.update_all(['content = ?', nil]) + assert_nil Topic.find(1).content + end + + def test_update_all_with_hash + assert_not_nil Topic.find(1).last_read + assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil) + assert_equal "bulk updated with hash!", Topic.find(1).content + assert_equal "bulk updated with hash!", Topic.find(2).content + assert_nil Topic.find(1).last_read + assert_nil Topic.find(2).last_read + end + + def test_update_all_with_non_standard_table_name + assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1]) + assert_equal 0, WarehouseThing.find(1).value + end + + def test_delete_new_record + client = Client.new + client.delete + assert client.frozen? + end + + def test_delete_record_with_associations + client = Client.find(3) + client.delete + assert client.frozen? + assert_kind_of Firm, client.firm + assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } + end + + def test_destroy_new_record + client = Client.new + client.destroy + assert client.frozen? + end + + def test_destroy_record_with_associations + client = Client.find(3) + client.destroy + assert client.frozen? + assert_kind_of Firm, client.firm + assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } + end + + def test_update_attribute + assert !Topic.find(1).approved? + Topic.find(1).update_attribute("approved", true) + assert Topic.find(1).approved? + + Topic.find(1).update_attribute(:approved, false) + assert !Topic.find(1).approved? + end + + def test_update_attribute_with_one_changed_and_one_updated + t = Topic.order('id').limit(1).first + title, author_name = t.title, t.author_name + t.author_name = 'John' + t.update_attribute(:title, 'super_title') + assert_equal 'John', t.author_name + assert_equal 'super_title', t.title + assert t.changed?, "topic should have changed" + assert t.author_name_changed?, "author_name should have changed" + assert !t.title_changed?, "title should not have changed" + assert_nil t.title_change, 'title change should be nil' + assert_equal ['author_name'], t.changed + + t.reload + assert_equal 'David', t.author_name + assert_equal 'super_title', t.title + end + + def test_update_attribute_with_one_updated + t = Topic.first + title = t.title + t.update_attribute(:title, 'super_title') + assert_equal 'super_title', t.title + assert !t.changed?, "topic should not have changed" + assert !t.title_changed?, "title should not have changed" + assert_nil t.title_change, 'title change should be nil' + + t.reload + assert_equal 'super_title', t.title + end + + def test_update_attribute_for_udpated_at_on + developer = Developer.find(1) + updated_at = developer.updated_at + developer.update_attribute(:salary, 80001) + assert_not_equal updated_at, developer.updated_at + developer.reload + assert_not_equal updated_at, developer.updated_at + end + + + def test_update_attributes + topic = Topic.find(1) + assert !topic.approved? + assert_equal "The First Topic", topic.title + + topic.update_attributes("approved" => true, "title" => "The First Topic Updated") + topic.reload + assert topic.approved? + assert_equal "The First Topic Updated", topic.title + + topic.update_attributes(:approved => false, :title => "The First Topic") + topic.reload + assert !topic.approved? + assert_equal "The First Topic", topic.title + end + + def test_update_attributes! + Reply.validates_presence_of(:title) + reply = Reply.find(2) + assert_equal "The Second Topic of the day", reply.title + assert_equal "Have a nice day", reply.content + + reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening") + reply.reload + assert_equal "The Second Topic of the day updated", reply.title + assert_equal "Have a nice evening", reply.content + + reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day") + reply.reload + assert_equal "The Second Topic of the day", reply.title + assert_equal "Have a nice day", reply.content + + assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") } + ensure + Reply.reset_callbacks(:validate) + end + + def test_destroyed_returns_boolean + developer = Developer.first + assert_equal false, developer.destroyed? + developer.destroy + assert_equal true, developer.destroyed? + + developer = Developer.last + assert_equal false, developer.destroyed? + developer.delete + assert_equal true, developer.destroyed? + end + + def test_persisted_returns_boolean + developer = Developer.new(:name => "Jose") + assert_equal false, developer.persisted? + developer.save! + assert_equal true, developer.persisted? + + developer = Developer.first + assert_equal true, developer.persisted? + developer.destroy + assert_equal false, developer.persisted? + + developer = Developer.last + assert_equal true, developer.persisted? + developer.delete + assert_equal false, developer.persisted? + end + + def test_class_level_destroy + should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_be_destroyed_reply + + Topic.destroy(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } + end + + def test_class_level_delete + should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_be_destroyed_reply + + Topic.delete(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } + assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) } + end + + def test_create_with_custom_timestamps + custom_datetime = 1.hour.ago.beginning_of_day + + %w(created_at created_on updated_at updated_on).each do |attribute| + parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime) + assert_equal custom_datetime, parrot[attribute] + end + end + +end -- cgit v1.2.3 From 1b2824b6591deee1211f799dc5ce71a7a718953b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 09:13:51 -0700 Subject: fixing typeo. thanks bamnet! --- activerecord/test/cases/associations/cascaded_eager_loading_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 67eb6403c8..f5d59c9a43 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -46,7 +46,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first end - def test_eager_associatoin_loading_with_join_for_count + def test_eager_association_loading_with_join_for_count authors = Author.joins(:special_posts).includes([:posts, :categorizations]) assert_nothing_raised { authors.count } -- cgit v1.2.3 From 041e47b326badfbe1b5e33df172e123386c288ac Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 16 Jul 2010 07:32:40 +0800 Subject: Explain how to change javascript default files --- .../lib/rails/generators/rails/app/templates/config/application.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 67a38ea1d5..589f7e674a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -39,6 +39,9 @@ module <%= app_const_base %> # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + # Add your default javascripts + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + # Configure generators values. Many other options are available, be sure to check the documentation. # config.generators do |g| # g.orm :active_record -- cgit v1.2.3 From ea7f1fb026a384a2b39362aa179ebfa0ba14a816 Mon Sep 17 00:00:00 2001 From: Nick Sieger Date: Tue, 13 Jul 2010 06:14:26 +0800 Subject: Don't rely on implementation-specific order-dependence of array comparisons in unit tests Signed-off-by: Santiago Pastorino --- activesupport/test/core_ext/class_test.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb index 08bb13dd35..60ba3b8f88 100644 --- a/activesupport/test/core_ext/class_test.rb +++ b/activesupport/test/core_ext/class_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/core_ext/class' +require 'set' class ClassTest < Test::Unit::TestCase class Parent; end @@ -12,16 +13,16 @@ class ClassTest < Test::Unit::TestCase class C < B; end def test_descendants - assert_equal [Foo, Bar, Baz, A, B, C], Parent.descendants - assert_equal [Bar, Baz], Foo.descendants + assert_equal [Foo, Bar, Baz, A, B, C].to_set, Parent.descendants.to_set + assert_equal [Bar, Baz].to_set, Foo.descendants.to_set assert_equal [Baz], Bar.descendants assert_equal [], Baz.descendants end def test_subclasses - assert_equal [Foo, A], Parent.subclasses + assert_equal [Foo, A].to_set, Parent.subclasses.to_set assert_equal [Bar], Foo.subclasses assert_equal [Baz], Bar.subclasses assert_equal [], Baz.subclasses end -end \ No newline at end of file +end -- cgit v1.2.3 From 114fa4d43121cd05fc18a8449a4d1abd92978a72 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 16 Jul 2010 22:53:37 +0800 Subject: Adds application.js when app is generated with -J and clear javascript_expansions[:defaults] --- railties/lib/rails/generators/rails/app/app_generator.rb | 1 + .../lib/rails/generators/rails/app/templates/config/application.rb | 4 ++++ railties/test/generators/app_generator_test.rb | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 7d50e7da67..c99aa3c0cd 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -115,6 +115,7 @@ module Rails directory "public/javascripts" else empty_directory_with_gitkeep "public/javascripts" + create_file "public/javascripts/application.js" end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 589f7e674a..ad8f523c28 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -40,7 +40,11 @@ module <%= app_const_base %> # config.i18n.default_locale = :de # Add your default javascripts +<% if options[:skip_prototype] -%> + config.action_view.javascript_expansions[:defaults] = [] +<% else -%> # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) +<% end -%> # Configure generators values. Many other options are available, be sure to check the documentation. # config.generators do |g| diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 6a3b5de9de..9d7a976afd 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -129,14 +129,19 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_prototype_and_test_unit_are_added_by_default run_generator + assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/ + assert_file "public/javascripts/application.js" assert_file "public/javascripts/prototype.js" + assert_file "public/javascripts/rails.js" assert_file "test" end def test_prototype_and_test_unit_are_skipped_if_required run_generator [destination_root, "--skip-prototype", "--skip-testunit"] + assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+\[\]/ + assert_file "public/javascripts/application.js" assert_no_file "public/javascripts/prototype.js" - assert_file "public/javascripts" + assert_no_file "public/javascripts/rails.js" assert_no_file "test" end -- cgit v1.2.3 From 79d9e0f15522c981fcd1005cec9d441ff96725f5 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 17 Jul 2010 02:35:02 +0800 Subject: Makes this test don't trust on the speed of execution --- activerecord/test/cases/persistence_test.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index fbfdd7b041..4ea5df0945 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -253,14 +253,15 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_attribute_for_udpated_at_on developer = Developer.find(1) - updated_at = developer.updated_at + prev_month = Time.now.prev_month + developer.update_attribute(:updated_at, prev_month) + assert_equal prev_month, developer.updated_at developer.update_attribute(:salary, 80001) - assert_not_equal updated_at, developer.updated_at + assert_not_equal prev_month, developer.updated_at developer.reload - assert_not_equal updated_at, developer.updated_at + assert_not_equal prev_month, developer.updated_at end - def test_update_attributes topic = Topic.find(1) assert !topic.approved? -- cgit v1.2.3 From b952470cc228ce4438226e180454bb141063b0ca Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 11:15:55 -0700 Subject: use an attr_reader for performance --- .../active_record/connection_adapters/abstract/connection_pool.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index c2d79a421d..5dcc625ade 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -290,14 +290,12 @@ module ActiveRecord # ActiveRecord::Base.connection_handler. Active Record models use this to # determine that connection pool that they should use. class ConnectionHandler + attr_reader :connection_pools + def initialize(pools = {}) @connection_pools = pools end - def connection_pools - @connection_pools ||= {} - end - def establish_connection(name, spec) @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec) end -- cgit v1.2.3 From 04ef434b21f2143c3bea330d6861eac3696666ea Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 11:26:02 -0700 Subject: only test for existence of +pool+ once --- .../active_record/connection_adapters/abstract/connection_pool.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 5dcc625ade..1b4ee0368e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -343,9 +343,11 @@ module ActiveRecord # re-establishing the connection. def remove_connection(klass) pool = @connection_pools[klass.name] + return nil unless pool + @connection_pools.delete_if { |key, value| value == pool } - pool.disconnect! if pool - pool.spec.config if pool + pool.disconnect! + pool.spec.config end def retrieve_connection_pool(klass) -- cgit v1.2.3 From ef165b355d461dea476875c2f1b7bac18eee3046 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 16 Jul 2010 21:54:13 +0200 Subject: minor pass to generated code in application.rb related to :defaults --- .../lib/rails/generators/rails/app/templates/config/application.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index ad8f523c28..7a94d6e05c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -22,7 +22,7 @@ module <%= app_const_base %> # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - # config.autoload_paths += %W( #{config.root}/extras ) + # config.autoload_paths += %W(#{config.root}/extras) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -39,9 +39,9 @@ module <%= app_const_base %> # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de - # Add your default javascripts + # JavaScript files you want as :defaults (application.js is always included). <% if options[:skip_prototype] -%> - config.action_view.javascript_expansions[:defaults] = [] + config.action_view.javascript_expansions[:defaults] = %w() <% else -%> # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) <% end -%> -- cgit v1.2.3 From 2244bb00131f2f5ec4d1912f7dd6378ba0f3b58f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 13:50:58 -0700 Subject: fixing whitespace errors --- activerecord/test/cases/connection_pool_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index cc9b2a45f4..c535119972 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -5,21 +5,21 @@ class ConnectionManagementTest < ActiveRecord::TestCase @env = {} @app = stub('App') @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app) - + @connections_cleared = false ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true } end - + test "clears active connections after each call" do @app.expects(:call).with(@env) @management.call(@env) assert @connections_cleared end - + test "doesn't clear active connections when running in a test case" do @env['rack.test'] = true @app.expects(:call).with(@env) @management.call(@env) assert !@connections_cleared end -end \ No newline at end of file +end -- cgit v1.2.3 From 7ba54ff71177e97cac16ec37fd1cb562c4bbb2bb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 13:54:14 -0700 Subject: fixing test file name --- .../test/cases/connection_management_test.rb | 25 ++++++++++++++++++++++ activerecord/test/cases/connection_pool_test.rb | 25 ---------------------- 2 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 activerecord/test/cases/connection_management_test.rb delete mode 100644 activerecord/test/cases/connection_pool_test.rb diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb new file mode 100644 index 0000000000..c535119972 --- /dev/null +++ b/activerecord/test/cases/connection_management_test.rb @@ -0,0 +1,25 @@ +require "cases/helper" + +class ConnectionManagementTest < ActiveRecord::TestCase + def setup + @env = {} + @app = stub('App') + @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app) + + @connections_cleared = false + ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true } + end + + test "clears active connections after each call" do + @app.expects(:call).with(@env) + @management.call(@env) + assert @connections_cleared + end + + test "doesn't clear active connections when running in a test case" do + @env['rack.test'] = true + @app.expects(:call).with(@env) + @management.call(@env) + assert !@connections_cleared + end +end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb deleted file mode 100644 index c535119972..0000000000 --- a/activerecord/test/cases/connection_pool_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require "cases/helper" - -class ConnectionManagementTest < ActiveRecord::TestCase - def setup - @env = {} - @app = stub('App') - @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app) - - @connections_cleared = false - ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true } - end - - test "clears active connections after each call" do - @app.expects(:call).with(@env) - @management.call(@env) - assert @connections_cleared - end - - test "doesn't clear active connections when running in a test case" do - @env['rack.test'] = true - @app.expects(:call).with(@env) - @management.call(@env) - assert !@connections_cleared - end -end -- cgit v1.2.3 From 8d17f533187a791b4a42f45542c56c5f965e6fad Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 14:39:40 -0700 Subject: adding a test for clearing stale connections --- activerecord/test/cases/connection_pool_test.rb | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 activerecord/test/cases/connection_pool_test.rb diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb new file mode 100644 index 0000000000..82b3c36ed2 --- /dev/null +++ b/activerecord/test/cases/connection_pool_test.rb @@ -0,0 +1,31 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ConnectionPoolTest < ActiveRecord::TestCase + def test_clear_stale_cached_connections! + pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec + + threads = [ + Thread.new { pool.connection }, + Thread.new { pool.connection }] + + threads.map { |t| t.join } + + pool.extend Module.new { + attr_accessor :checkins + def checkin conn + @checkins << conn + conn.object_id + end + } + pool.checkins = [] + + cleared_threads = pool.clear_stale_cached_connections! + assert((cleared_threads - threads.map { |x| x.object_id }).empty?, + "threads should have been removed") + assert_equal pool.checkins.length, threads.length + end + end + end +end -- cgit v1.2.3 From 42be67e8d4470b0bdc6ba62fceaa6cdb1c21a097 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 16 Jul 2010 14:48:50 -0700 Subject: unfactoring clear_stale_cached_connections! --- .../abstract/connection_pool.rb | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 1b4ee0368e..9d0251dda3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -161,8 +161,13 @@ module ActiveRecord # Return any checked-out connections back to the pool by threads that # are no longer alive. def clear_stale_cached_connections! - remove_stale_cached_threads!(@reserved_connections) do |name, conn| - checkin conn + keys = @reserved_connections.keys - Thread.list.find_all { |t| + t.alive? + }.map { |thread| thread.object_id } + + keys.each do |key| + checkin @reserved_connections[key] + @reserved_connections.delete(key) end end @@ -232,20 +237,6 @@ module ActiveRecord Thread.current.object_id end - # Remove stale threads from the cache. - def remove_stale_cached_threads!(cache, &block) - keys = Set.new(cache.keys) - - Thread.list.each do |thread| - keys.delete(thread.object_id) if thread.alive? - end - keys.each do |key| - next unless cache.has_key?(key) - block.call(key, cache[key]) - cache.delete(key) - end - end - def checkout_new_connection c = new_connection @connections << c -- cgit v1.2.3 From 38f1ea8fe267947f4baf832d3b15ee5b83f18f71 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 17 Jul 2010 05:22:48 +0800 Subject: Makes test use the new config.action_view.javascript_expansions[:defaults] initialization syntax --- railties/test/generators/app_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 9d7a976afd..7018816af0 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -138,7 +138,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_prototype_and_test_unit_are_skipped_if_required run_generator [destination_root, "--skip-prototype", "--skip-testunit"] - assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+\[\]/ + assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/ assert_file "public/javascripts/application.js" assert_no_file "public/javascripts/prototype.js" assert_no_file "public/javascripts/rails.js" -- cgit v1.2.3 From c6e20586372743ce200449bf0ac21aed04c6b81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 17 Jul 2010 09:54:34 +0200 Subject: Add skip_eager_load!, skip_autoload! and friends to path objects. --- railties/lib/rails/paths.rb | 40 ++++++++++++---------------------------- railties/test/paths_test.rb | 24 ++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 7a65188a9a..d303212f52 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -116,36 +116,20 @@ module Rails @paths.concat paths end - def autoload_once! - @autoload_once = true - end - - def autoload_once? - @autoload_once - end - - def eager_load! - @eager_load = true - end - - def eager_load? - @eager_load - end - - def autoload! - @autoload = true - end - - def autoload? - @autoload - end + %w(autoload_once eager_load autoload load_path).each do |m| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{m}! + @#{m} = true + end - def load_path! - @load_path = true - end + def skip_#{m}! + @#{m} = false + end - def load_path? - @load_path + def #{m}? + @#{m} + end + RUBY end def paths diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 008247cb1a..80fae8c543 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -118,13 +118,23 @@ class PathsTest < ActiveSupport::TestCase assert_raise(RuntimeError) { @root << "/biz" } end - test "it is possible to add a path that should be loaded only once" do + test "it is possible to add a path that should be autoloaded only once" do @root.app = "/app" @root.app.autoload_once! assert @root.app.autoload_once? assert @root.autoload_once.include?(@root.app.paths.first) end + test "it is possible to remove a path that should be autoloaded only once" do + @root.app = "/app" + @root.app.autoload_once! + assert @root.app.autoload_once? + + @root.app.skip_autoload_once! + assert !@root.app.autoload_once? + assert !@root.autoload_once.include?(@root.app.paths.first) + end + test "it is possible to add a path without assignment and specify it should be loaded only once" do @root.app "/app", :autoload_once => true assert @root.app.autoload_once? @@ -152,13 +162,23 @@ class PathsTest < ActiveSupport::TestCase assert_equal 2, @root.autoload_once.size end - test "it is possible to mark a path as eager" do + test "it is possible to mark a path as eager loaded" do @root.app = "/app" @root.app.eager_load! assert @root.app.eager_load? assert @root.eager_load.include?(@root.app.paths.first) end + test "it is possible to skip a path from eager loading" do + @root.app = "/app" + @root.app.eager_load! + assert @root.app.eager_load? + + @root.app.skip_eager_load! + assert !@root.app.eager_load? + assert !@root.eager_load.include?(@root.app.paths.first) + end + test "it is possible to add a path without assignment and mark it as eager" do @root.app "/app", :eager_load => true assert @root.app.eager_load? -- cgit v1.2.3 From caddee253c7a865100af157525861ab58afd4715 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 17 Jul 2010 19:42:44 +0200 Subject: new guide: API Documentation Guidelines, ported and revised from the docrails github wiki --- .../source/api_documentation_guidelines.textile | 187 +++++++++++++++++++++ railties/guides/source/index.html.erb | 16 +- railties/guides/source/layout.html.erb | 5 +- 3 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 railties/guides/source/api_documentation_guidelines.textile diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile new file mode 100644 index 0000000000..d9a0d39d9d --- /dev/null +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -0,0 +1,187 @@ +h2. API Documentation Guidelines + +This guide documents the Ruby on Rails API documentation guidelines. + +endprologue. + +h3. RDoc + +The Rails API documentation is generated with RDoc 2.5. Please consult the "RDoc documentation":http://rdoc.rubyforge.org/RDoc.htmlFor for help with its markup. + +h3. Wording + +Write simple, declarative sentences. Brevity is a plus: get to the point. + +Write in present tense: "Returns a hash that...", rather than "Returned a hash that..." or "Will return a hash that...". + +Start comments in upper case, follow regular punctuation rules: + + +# Declares an attribute reader backed by an internally-named instance variable. +def attr_internal_reader(*attrs) + ... +end + + +Communicate to the reader the current way of doing things, both explicitly and implicitly. Use the recommended idioms in edge, reorder sections to emphasize favored approaches if needed, etc. The documentation should be a model for best practices and canonical, modern Rails usage. + +Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil? + +The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be fenomenal :). + +Spell names correctly: HTML, MySQL, JavaScript, ERb. + +h3. Example Code + +Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas. + +Use two spaces to indent chunks of code.—that is two spaces with respect to the left margin; the examples +themselves should use "Rails code conventions":http://rails.lighthouseapp.com/projects/8994/source-style. + +Short docs do not need an explicit "Examples" label to introduce snippets, they just follow paragraphs: + + +# Converts a collection of elements into a formatted string by calling +# to_s on all elements and joining them. +# +# Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post" + + +On the other hand big chunks of structured documentation may have a separate "Examples" section: + + +# ==== Examples +# +# Person.exists?(5) +# Person.exists?('5') +# Person.exists?(:name => "David") +# Person.exists?(['name LIKE ?', "%#{query}%"]) + + +The result of expressions follow them and are introduced by "# => ", vertically aligned: + + +# For checking if a fixnum is even or odd. +# +# 1.even? # => false +# 1.odd? # => true +# 2.even? # => true +# 2.odd? # => false + + +If a line is too long, the comment may be placed on the next line: + + + # label(:post, :title) + # # => + # + # label(:post, :title, "A short title") + # # => + # + # label(:post, :title, "A short title", :class => "title_label") + # # => + + +Avoid using any printing methods like +puts+ or +p+ for that purpose. + +On the other hand, regular comments do not use an arrow: + + +# polymorphic_url(record) # same as comment_url(record) + + +h3. Filenames + +As a rule of thumb use filenames relative to the application root: + + +config/routes.rb # YES +routes.rb # NO +RAILS_ROOT/config/routes.rb # NO + + + +h3. Fonts + +h4. Fixed-width Font + +Use fixed-width fonts for: +* constants, in particular class and module names +* method names +* literals like +nil+, +false+, +true+, +self+ +* symbols +* method parameters +* file names + + +# Copies the instance variables of +object+ into +self+. +# +# Instance variable names in the +exclude+ array are ignored. If +object+ +# responds to protected_instance_variables the ones returned are +# also ignored. For example, Rails controllers implement that method. +# ... +def copy_instance_variables_from(object, exclude = []) + ... +end + + +WARNING: Using a pair of ++...++ for fixed-width font only works with *words*; that is: anything matching \A\w+\z. For anything else use +<tt>...</tt>+, notably symbols, setters, inline snippets, etc: + +h4. Regular Font + +When "true" and "false" are English words rather than Ruby keywords use a regular font: + + +# If reload_plugins? is false, add this to your plugin's init.rb +# to make it reloadable: +# +# Dependencies.load_once_paths.delete lib_path + + +h3. Description Lists + +In lists of options, parameters, etc. use a hyphen between the item and its description (reads better than a colon because normally options are symbols): + + +# * :allow_nil - Skip validation if attribute is +nil+. + + +The description starts in upper case and ends with a full stop—it's standard English. + +h3. Dynamically Generated Methods + +Methods created with +(module|class)_eval(STRING)+ have a comment by their side with an instance of the generated code. That comment is 2 spaces apart from the template: + + +for severity in Severity.constants + class_eval <<-EOT, __FILE__, __LINE__ + def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block) + add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block) + end # end + # + def #{severity.downcase}? # def debug? + #{severity} >= @level # DEBUG >= @level + end # end + EOT +end + + +If the resulting lines are too wide, say 200 columns or more, we put the comment above the call: + + +# def self.find_by_login_and_activated(*args) +# options = args.extract_options! +# ... +# end +self.class_eval %{ + def self.#{method_id}(*args) + options = args.extract_options! + ... + end +} + + +h3. Changelog + +* July 17, 2010: ported from the docrails wiki and revised by "Xavier Noria":credits.html#fxn + diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb index a930db0f1d..254ee91ab4 100644 --- a/railties/guides/source/index.html.erb +++ b/railties/guides/source/index.html.erb @@ -123,10 +123,6 @@ Ruby on Rails Guides <%= guide("Caching with Rails", 'caching_with_rails.html', :ticket => 10) do %>

Various caching techniques provided by Rails.

<% end %> - -<%= guide("Contributing to Rails", 'contributing_to_rails.html') do %> -

Rails is not "somebody else's framework." This guide covers a variety of ways that you can get involved in the ongoing development of Rails.

-<% end %>

Extending Rails

@@ -147,6 +143,18 @@ Ruby on Rails Guides <% end %> +

Contributing to Rails

+ +
+ <%= guide("Contributing to Rails", 'contributing_to_rails.html') do %> +

Rails is not "somebody else's framework." This guide covers a variety of ways that you can get involved in the ongoing development of Rails.

+ <% end %> + + <%= guide('API Documentation Guidelines', 'api_documentation_guidelines.html') do %> +

This guide documents the Ruby on Rails API documentation guidelines.

+ <% end %> +
+

Release Notes

diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index 501d8fef6d..c4758316ea 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -71,13 +71,16 @@
Configuring Rails Applications
Rails Command Line Tools and Rake Tasks
Caching with Rails
-
Contributing to Rails
Extending Rails
The Basics of Creating Rails Plugins
Rails on Rack
Adding a Generator to Your Plugin
+
Contributing to Rails
+
Contributing to Rails
+
API Documentation Guidelines
+
Release Notes
Ruby on Rails 3.0 Release Notes
Ruby on Rails 2.3 Release Notes
-- cgit v1.2.3 From 9c80f5b3910ca0573f6e40aaccf3102c260986b6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 17 Jul 2010 13:14:38 -0700 Subject: use === to avoid regular expression creation, and speed up string comparison --- activesupport/lib/active_support/notifications/fanout.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 300ec842a9..b27713e4ad 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -39,13 +39,7 @@ module ActiveSupport class Binding #:nodoc: def initialize(queue, pattern) @queue = queue - @pattern = - case pattern - when Regexp, NilClass - pattern - else - /^#{Regexp.escape(pattern.to_s)}$/ - end + @pattern = pattern end def subscribe(&block) @@ -70,13 +64,13 @@ module ActiveSupport end def subscribed_to?(name) - !@pattern || @pattern =~ name.to_s + !@pattern || @pattern === name.to_s end def matches?(subscriber_or_name) case subscriber_or_name when String - @pattern && @pattern =~ subscriber_or_name + @pattern && @pattern === subscriber_or_name when self true end -- cgit v1.2.3 From 4226c93779dae53c6921f8ce93d6af000a24e2d1 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 17 Jul 2010 14:35:44 -0700 Subject: removing Binding class --- .../lib/active_support/notifications/fanout.rb | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index b27713e4ad..fa469c8b79 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -6,13 +6,15 @@ module ActiveSupport def initialize @subscribers = [] @listeners_for = {} + @pattern = nil end def bind(pattern) - Binding.new(self, pattern) + @pattern = pattern + self end - def subscribe(pattern = nil, &block) + def subscribe(pattern = @pattern, &block) @listeners_for.clear @subscribers << Subscriber.new(pattern, &block) @subscribers.last @@ -35,18 +37,6 @@ module ActiveSupport def wait end - # Used for internal implementation only. - class Binding #:nodoc: - def initialize(queue, pattern) - @queue = queue - @pattern = pattern - end - - def subscribe(&block) - @queue.subscribe(@pattern, &block) - end - end - class Subscriber #:nodoc: def initialize(pattern, &block) @pattern = pattern -- cgit v1.2.3 From fa73e777a1dac2daaa14f781b8a17102ca47ea8b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 17 Jul 2010 14:44:29 -0700 Subject: private method is not needed --- activesupport/lib/active_support/notifications/fanout.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index fa469c8b79..6dfc3c0a99 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -45,7 +45,7 @@ module ActiveSupport def publish(*args) return unless subscribed_to?(args.first) - push(*args) + @block.call(*args) true end @@ -58,19 +58,9 @@ module ActiveSupport end def matches?(subscriber_or_name) - case subscriber_or_name - when String + self === subscriber_or_name || @pattern && @pattern === subscriber_or_name - when self - true - end end - - private - - def push(*args) - @block.call(*args) - end end end end -- cgit v1.2.3 From 606d8fdfc8b86800fb738854c240c1c15bd272bb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 17 Jul 2010 14:45:59 -0700 Subject: drained? is never called --- activesupport/lib/active_support/notifications/fanout.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 6dfc3c0a99..7eefb7f20a 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -49,10 +49,6 @@ module ActiveSupport true end - def drained? - true - end - def subscribed_to?(name) !@pattern || @pattern === name.to_s end -- cgit v1.2.3 From 7aec9f9c028dd7b53f2a389d2d8bd7d27a770461 Mon Sep 17 00:00:00 2001 From: Nick Quaranto Date: Sun, 18 Jul 2010 05:47:34 +0800 Subject: Removing ActionDispatch::Http::FilterParameters#fitered_parameters alias --- actionpack/lib/action_dispatch/http/filter_parameters.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 152aaa2e67..47643ce130 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -36,7 +36,6 @@ module ActionDispatch parameters.dup end end - alias :fitered_params :filtered_parameters # Return a hash of request.env with all sensitive data replaced. def filtered_env @@ -110,4 +109,4 @@ module ActionDispatch end end -end \ No newline at end of file +end -- cgit v1.2.3 From cebe5c2fac708885c018550f7ef4610df613e5af Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 18 Jul 2010 04:55:59 +0800 Subject: It's not needed to initialize the attr when calling mattr_writer --- .../lib/active_support/core_ext/module/attribute_accessors.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 9c4d5fae26..2d88cb57e5 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -5,9 +5,7 @@ class Module options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} - @@#{sym} = nil - end + @@#{sym} = nil unless defined? @@#{sym} def self.#{sym} @@#{sym} @@ -28,10 +26,6 @@ class Module options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} - @@#{sym} = nil - end - def self.#{sym}=(obj) @@#{sym} = obj end -- cgit v1.2.3 From cfca55949f51bf3970bae7c506807db97dbcf05f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 17 Jul 2010 15:53:22 -0700 Subject: convert duration to an attr_reader --- activesupport/lib/active_support/notifications/instrumenter.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 7e89402822..34bccb83d0 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -33,7 +33,7 @@ module ActiveSupport end class Event - attr_reader :name, :time, :end, :transaction_id, :payload + attr_reader :name, :time, :end, :transaction_id, :payload, :duration def initialize(name, start, ending, transaction_id, payload) @name = name @@ -41,14 +41,11 @@ module ActiveSupport @time = start @transaction_id = transaction_id @end = ending - end - - def duration - @duration ||= 1000.0 * (@end - @time) + @duration = 1000.0 * (@end - @time) end def parent_of?(event) - start = (self.time - event.time) * 1000 + start = (time - event.time) * 1000 start <= 0 && (start + duration >= event.duration) end end -- cgit v1.2.3 From fa98eca75bd8666719bf3d061c87638850a20fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 17 Jul 2010 10:59:41 +0200 Subject: Add console hook to force ActiveRecord::Base to be loaded when console starts avoiding reference loops. --- activerecord/lib/active_record/railtie.rb | 6 ++++++ railties/lib/rails/application.rb | 13 +++++++++++++ railties/lib/rails/commands/console.rb | 5 +---- railties/lib/rails/railtie.rb | 18 ++++++++++-------- railties/test/application/console_test.rb | 21 ++++++++++++++++++--- railties/test/railties/railtie_test.rb | 16 ++++++++++++++++ 6 files changed, 64 insertions(+), 15 deletions(-) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index eff51a7c87..78fdb77216 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -22,6 +22,12 @@ module ActiveRecord load "active_record/railties/databases.rake" end + # When loading console, force ActiveRecord to be loaded to avoid cross + # references when loading a constant for the first time. + console do + ActiveRecord::Base + end + initializer "active_record.initialize_timezone" do ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 458177b954..3f9bca0bd6 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -149,6 +149,13 @@ module Rails self end + def load_console(sandbox=false) + initialize_console(sandbox) + railties.all { |r| r.load_console } + super() + self + end + def app @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) @@ -212,5 +219,11 @@ module Rails def initialize_generators require "rails/generators" end + + def initialize_console(sandbox=false) + require "rails/console/app" + require "rails/console/sandbox" if sandbox + require "rails/console/helpers" + end end end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 50df6ba405..834a120c01 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -23,10 +23,7 @@ module Rails opt.parse!(ARGV) end - @app.initialize! - require "rails/console/app" - require "rails/console/sandbox" if options[:sandbox] - require "rails/console/helpers" + @app.load_console(options[:sandbox]) if options[:debugger] begin diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index dbdbfea509..1d6a2de87d 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -156,6 +156,12 @@ module Rails @rake_tasks end + def console(&blk) + @load_console ||= [] + @load_console << blk if blk + @load_console + end + def generators(&blk) @generators ||= [] @generators << blk if blk @@ -170,20 +176,16 @@ module Rails def eager_load! end - def rake_tasks - self.class.rake_tasks - end - - def generators - self.class.generators + def load_console + self.class.console.each(&:call) end def load_tasks - rake_tasks.each { |blk| blk.call } + self.class.rake_tasks.each(&:call) end def load_generators - generators.each { |blk| blk.call } + self.class.generators.each(&:call) end end end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 8ff69f0208..a72e6916dd 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -9,10 +9,8 @@ class ConsoleTest < Test::Unit::TestCase end def load_environment - # Load steps taken from rails/commands/console.rb require "#{rails_root}/config/environment" - require 'rails/console/app' - require 'rails/console/helpers' + Rails.application.load_console end def test_app_method_should_return_integration_session @@ -75,4 +73,21 @@ class ConsoleTest < Test::Unit::TestCase assert_equal 'Once upon a time in a world...', helper.truncate('Once upon a time in a world far far away') end + + def test_active_record_does_not_panic_when_referencing_an_observed_constant + add_to_config "config.active_record.observers = :user_observer" + + app_file "app/models/user.rb", <<-MODEL + class User < ActiveRecord::Base + end + MODEL + + app_file "app/models/user_observer.rb", <<-MODEL + class UserObserver < ActiveRecord::Observer + end + MODEL + + load_environment + assert_nothing_raised { User } + end end diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index c74cc01dc1..db0fd87491 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -103,6 +103,22 @@ module RailtiesTest assert $ran_block end + test "console block is executed when MyApp.load_console is called" do + $ran_block = false + + class MyTie < Rails::Railtie + console do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert !$ran_block + AppTemplate::Application.load_console + assert $ran_block + end + test "railtie can add initializers" do $ran_block = false -- cgit v1.2.3 From e210895ba95e498b9debbf43a3e5ae588bca81f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 18 Jul 2010 11:01:32 +0200 Subject: Avoid uneeded queries in session stores if sid is not given. --- .../lib/action_dispatch/middleware/session/abstract_store.rb | 7 +++++-- .../lib/action_dispatch/middleware/session/mem_cache_store.rb | 1 - activerecord/lib/active_record/session_store.rb | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 64f4d1d532..08c969c449 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -191,8 +191,11 @@ module ActionDispatch def load_session(env) stale_session_check! do - sid = current_session_id(env) - sid, session = get_session(env, sid) + if sid = current_session_id(env) + sid, session = get_session(env, sid) + else + sid, session = generate_sid, {} + end [sid, session] end end diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index 28e3dbd732..5304440418 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -25,7 +25,6 @@ module ActionDispatch private def get_session(env, sid) - sid ||= generate_sid begin session = @pool.get(sid) || {} rescue MemCache::MemCacheError, Errno::ECONNREFUSED diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index df2f429c5d..7ea7fb5c51 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -294,7 +294,6 @@ module ActiveRecord private def get_session(env, sid) Base.silence do - sid ||= generate_sid session = find_session(sid) env[SESSION_RECORD_KEY] = session [sid, session.data] -- cgit v1.2.3 From 4a0d7c1a439c6ad8d35bf514761824e51fa07df2 Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Wed, 14 Jul 2010 04:39:54 -0400 Subject: save on parent should not cascade to child unless child changed [#3353 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/autosave_association.rb | 4 +++- activerecord/test/cases/autosave_association_test.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 7517896235..6af384367f 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -372,7 +372,9 @@ module ActiveRecord if autosave && association.marked_for_destruction? association.destroy elsif autosave != false - saved = association.save(:validate => !autosave) if association.new_record? || autosave + if association.new_record? || ( autosave && association.changed? ) + saved = association.save(:validate => !autosave) + end if association.updated? association_id = association.send(reflection.options[:primary_key] || :id) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 3b89c12a3f..48479bb429 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -632,6 +632,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent + #association save method only trigged when association is changed + @ship.pirate.catchphrase = "new catch phrase" # Stub the save method of the @ship.pirate instance to destroy and then raise an exception class << @ship.pirate def save(*args) @@ -880,6 +882,22 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") end + def test_should_not_call_belongs_to_after_save_callbacks_if_no_changes + @ship.attributes = { :name => "Titanic", :pirate_attributes => {:id => @pirate.id} } + #here there are no changes to pirate so if save on ship causes save on pirate + #this callback will fail pirate save.(pirate save shouldn't happen) + @ship.pirate.cancel_save_from_callback = true + @ship.save + assert_equal 'Titanic', @ship.reload.name + end + + def test_should_call_belongs_to_save_if_belongs_to_has_changes + @ship.attributes = { :name => "Titanic", :pirate_attributes => { :catchphrase => 'Jack', :id => @pirate.id} } + @ship.save + assert_equal 'Titanic', @ship.reload.name + assert_equal 'Jack', @pirate.reload.catchphrase + end + def test_should_still_work_without_an_associated_model @pirate.destroy @ship.reload.name = "The Vile Insanity" -- cgit v1.2.3 From 1f499e6d4cb1055de952957e3c9bd770e0219cc1 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Sat, 17 Jul 2010 16:08:17 -0400 Subject: fixing the ternary operation where the logic is very confusing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5136 state:resolved] Signed-off-by: José Valim --- actionpack/lib/action_view/helpers/date_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index f097b9a5a3..8050669adb 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -800,7 +800,8 @@ module ActionView start = options.delete(:start) || 0 stop = options.delete(:end) || 59 step = options.delete(:step) || 1 - leading_zeros = options.delete(:leading_zeros).nil? ? true : false + options.reverse_merge!({:leading_zeros => true}) + leading_zeros = options.delete(:leading_zeros) select_options = [] start.step(stop, step) do |i| -- cgit v1.2.3 From cdfa11409c6196d35e890cf1766e1e2cc6f3d7d7 Mon Sep 17 00:00:00 2001 From: Ivan Torres Date: Wed, 14 Jul 2010 01:23:41 -0500 Subject: select :include_blank or :prompt should return escaped string [#5099 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../lib/action_view/helpers/form_options_helper.rb | 6 +++--- .../test/template/form_options_helper_test.rb | 23 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 6f9d14de8b..ee34452769 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -447,7 +447,7 @@ module ActionView # wrap the output in an appropriate \n\n\n", + select("post", "category", %w( abe hest), :include_blank => '') + ) + end + def test_select_with_default_prompt @post = Post.new @post.category = "" @@ -394,6 +409,14 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_given_prompt_escaped + @post = Post.new + assert_dom_equal( + "", + select("post", "category", %w( abe hest), :prompt => '') + ) + end + def test_select_with_prompt_and_blank @post = Post.new @post.category = "" -- cgit v1.2.3 From 291adcd588e86746145e4ba9ab2ea4d0de26279f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 18 Jul 2010 12:51:03 +0200 Subject: Set session options id to nil is respected and cancels lazy loading. --- .../middleware/session/abstract_store.rb | 4 ++-- .../test/dispatch/session/cookie_store_test.rb | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 08c969c449..ad98249468 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -24,9 +24,9 @@ module ActionDispatch def [](key) if key == :id - load_session_id! unless super(:id) || has_session_id? + load_session_id! unless key?(:id) || has_session_id? end - super(key) + super end private diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index f0e01bfff0..3864821ef0 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -44,7 +44,12 @@ class CookieStoreTest < ActionController::IntegrationTest session[:foo] = 'bye!' * 1024 head :ok end - + + def change_session_id + request.session_options[:id] = nil + get_session_id + end + def rescue_action(e) raise end end @@ -212,6 +217,19 @@ class CookieStoreTest < ActionController::IntegrationTest end end + def test_setting_session_id_to_nil_is_respected + with_test_route_set do + cookies[SessionKey] = SignedBar + + get "/get_session_id" + sid = response.body + assert_equal sid.size, 36 + + get "/change_session_id" + assert_not_equal sid, response.body + end + end + def test_session_store_with_expire_after with_test_route_set(:expire_after => 5.hours) do # First request accesses the session -- cgit v1.2.3 From df6aa8e246bed1961ee042aa9ab8a5209e2ce7f3 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 16 Jul 2010 22:18:50 -0400 Subject: removing extra whitespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/aggregations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 74588b4f47..9e285e57dc 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -125,7 +125,7 @@ class OverridingAggregationsTest < ActiveRecord::TestCase class Name; end class DifferentName; end - class Person < ActiveRecord::Base + class Person < ActiveRecord::Base composed_of :composed_of, :mapping => %w(person_first_name first_name) end -- cgit v1.2.3 From b58e1c52f75130a237b53c0e488341832487dd54 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 16 Jul 2010 22:19:29 -0400 Subject: fixing typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/ar_schema_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 665c387d5d..588adc38e3 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 7, ActiveRecord::Migrator::current_version end - def test_schema_raises_an_error_for_invalid_column_ntype + def test_schema_raises_an_error_for_invalid_column_type assert_raise NoMethodError do ActiveRecord::Schema.define(:version => 8) do create_table :vegetables do |t| -- cgit v1.2.3 From 6caf943ace63c5d53ad95f4835ea58caea7eb22e Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 16 Jul 2010 22:21:05 -0400 Subject: primary_keys_test reads better than pk_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/pk_test.rb | 139 --------------------------- activerecord/test/cases/primary_keys_test.rb | 139 +++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 139 deletions(-) delete mode 100644 activerecord/test/cases/pk_test.rb create mode 100644 activerecord/test/cases/primary_keys_test.rb diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/pk_test.rb deleted file mode 100644 index 73f4b3848c..0000000000 --- a/activerecord/test/cases/pk_test.rb +++ /dev/null @@ -1,139 +0,0 @@ -require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/subscriber' -require 'models/movie' -require 'models/keyboard' -require 'models/mixed_case_monkey' - -class PrimaryKeysTest < ActiveRecord::TestCase - fixtures :topics, :subscribers, :movies, :mixed_case_monkeys - - def test_to_key_with_default_primary_key - topic = Topic.new - assert topic.to_key.nil? - topic = Topic.find(1) - assert_equal topic.to_key, [1] - end - - def test_to_key_with_customized_primary_key - keyboard = Keyboard.new - assert_nil keyboard.to_key - keyboard.save - assert_equal keyboard.to_key, [keyboard.id] - end - - def test_to_key_with_primary_key_after_destroy - topic = Topic.find(1) - topic.destroy - assert_equal topic.to_key, [1] - end - - def test_integer_key - topic = Topic.find(1) - assert_equal(topics(:first).author_name, topic.author_name) - topic = Topic.find(2) - assert_equal(topics(:second).author_name, topic.author_name) - - topic = Topic.new - topic.title = "New Topic" - assert_nil topic.id - assert_nothing_raised { topic.save! } - id = topic.id - - topicReloaded = Topic.find(id) - assert_equal("New Topic", topicReloaded.title) - end - - def test_customized_primary_key_auto_assigns_on_save - Keyboard.delete_all - keyboard = Keyboard.new(:name => 'HHKB') - assert_nothing_raised { keyboard.save! } - assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id - end - - def test_customized_primary_key_can_be_get_before_saving - keyboard = Keyboard.new - assert_nil keyboard.id - assert_nothing_raised { assert_nil keyboard.key_number } - end - - def test_customized_string_primary_key_settable_before_save - subscriber = Subscriber.new - assert_nothing_raised { subscriber.id = 'webster123' } - assert_equal 'webster123', subscriber.id - assert_equal 'webster123', subscriber.nick - end - - def test_string_key - subscriber = Subscriber.find(subscribers(:first).nick) - assert_equal(subscribers(:first).name, subscriber.name) - subscriber = Subscriber.find(subscribers(:second).nick) - assert_equal(subscribers(:second).name, subscriber.name) - - subscriber = Subscriber.new - subscriber.id = "jdoe" - assert_equal("jdoe", subscriber.id) - subscriber.name = "John Doe" - assert_nothing_raised { subscriber.save! } - assert_equal("jdoe", subscriber.id) - - subscriberReloaded = Subscriber.find("jdoe") - assert_equal("John Doe", subscriberReloaded.name) - end - - def test_find_with_more_than_one_string_key - assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length - end - - def test_primary_key_prefix - ActiveRecord::Base.primary_key_prefix_type = :table_name - Topic.reset_primary_key - assert_equal "topicid", Topic.primary_key - - ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore - Topic.reset_primary_key - assert_equal "topic_id", Topic.primary_key - - ActiveRecord::Base.primary_key_prefix_type = nil - Topic.reset_primary_key - assert_equal "id", Topic.primary_key - end - - def test_delete_should_quote_pkey - assert_nothing_raised { MixedCaseMonkey.delete(1) } - end - def test_update_counters_should_quote_pkey_and_quote_counter_columns - assert_nothing_raised { MixedCaseMonkey.update_counters(1, :fleaCount => 99) } - end - def test_find_with_one_id_should_quote_pkey - assert_nothing_raised { MixedCaseMonkey.find(1) } - end - def test_find_with_multiple_ids_should_quote_pkey - assert_nothing_raised { MixedCaseMonkey.find([1,2]) } - end - def test_instance_update_should_quote_pkey - assert_nothing_raised { MixedCaseMonkey.find(1).save } - end - def test_instance_destroy_should_quote_pkey - assert_nothing_raised { MixedCaseMonkey.find(1).destroy } - end - - def test_supports_primary_key - assert_nothing_raised NoMethodError do - ActiveRecord::Base.connection.supports_primary_key? - end - end - - def test_primary_key_returns_value_if_it_exists - if ActiveRecord::Base.connection.supports_primary_key? - assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers') - end - end - - def test_primary_key_returns_nil_if_it_does_not_exist - if ActiveRecord::Base.connection.supports_primary_key? - assert_nil ActiveRecord::Base.connection.primary_key('developers_projects') - end - end -end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb new file mode 100644 index 0000000000..73f4b3848c --- /dev/null +++ b/activerecord/test/cases/primary_keys_test.rb @@ -0,0 +1,139 @@ +require "cases/helper" +require 'models/topic' +require 'models/reply' +require 'models/subscriber' +require 'models/movie' +require 'models/keyboard' +require 'models/mixed_case_monkey' + +class PrimaryKeysTest < ActiveRecord::TestCase + fixtures :topics, :subscribers, :movies, :mixed_case_monkeys + + def test_to_key_with_default_primary_key + topic = Topic.new + assert topic.to_key.nil? + topic = Topic.find(1) + assert_equal topic.to_key, [1] + end + + def test_to_key_with_customized_primary_key + keyboard = Keyboard.new + assert_nil keyboard.to_key + keyboard.save + assert_equal keyboard.to_key, [keyboard.id] + end + + def test_to_key_with_primary_key_after_destroy + topic = Topic.find(1) + topic.destroy + assert_equal topic.to_key, [1] + end + + def test_integer_key + topic = Topic.find(1) + assert_equal(topics(:first).author_name, topic.author_name) + topic = Topic.find(2) + assert_equal(topics(:second).author_name, topic.author_name) + + topic = Topic.new + topic.title = "New Topic" + assert_nil topic.id + assert_nothing_raised { topic.save! } + id = topic.id + + topicReloaded = Topic.find(id) + assert_equal("New Topic", topicReloaded.title) + end + + def test_customized_primary_key_auto_assigns_on_save + Keyboard.delete_all + keyboard = Keyboard.new(:name => 'HHKB') + assert_nothing_raised { keyboard.save! } + assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id + end + + def test_customized_primary_key_can_be_get_before_saving + keyboard = Keyboard.new + assert_nil keyboard.id + assert_nothing_raised { assert_nil keyboard.key_number } + end + + def test_customized_string_primary_key_settable_before_save + subscriber = Subscriber.new + assert_nothing_raised { subscriber.id = 'webster123' } + assert_equal 'webster123', subscriber.id + assert_equal 'webster123', subscriber.nick + end + + def test_string_key + subscriber = Subscriber.find(subscribers(:first).nick) + assert_equal(subscribers(:first).name, subscriber.name) + subscriber = Subscriber.find(subscribers(:second).nick) + assert_equal(subscribers(:second).name, subscriber.name) + + subscriber = Subscriber.new + subscriber.id = "jdoe" + assert_equal("jdoe", subscriber.id) + subscriber.name = "John Doe" + assert_nothing_raised { subscriber.save! } + assert_equal("jdoe", subscriber.id) + + subscriberReloaded = Subscriber.find("jdoe") + assert_equal("John Doe", subscriberReloaded.name) + end + + def test_find_with_more_than_one_string_key + assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length + end + + def test_primary_key_prefix + ActiveRecord::Base.primary_key_prefix_type = :table_name + Topic.reset_primary_key + assert_equal "topicid", Topic.primary_key + + ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + Topic.reset_primary_key + assert_equal "topic_id", Topic.primary_key + + ActiveRecord::Base.primary_key_prefix_type = nil + Topic.reset_primary_key + assert_equal "id", Topic.primary_key + end + + def test_delete_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.delete(1) } + end + def test_update_counters_should_quote_pkey_and_quote_counter_columns + assert_nothing_raised { MixedCaseMonkey.update_counters(1, :fleaCount => 99) } + end + def test_find_with_one_id_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find(1) } + end + def test_find_with_multiple_ids_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find([1,2]) } + end + def test_instance_update_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find(1).save } + end + def test_instance_destroy_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find(1).destroy } + end + + def test_supports_primary_key + assert_nothing_raised NoMethodError do + ActiveRecord::Base.connection.supports_primary_key? + end + end + + def test_primary_key_returns_value_if_it_exists + if ActiveRecord::Base.connection.supports_primary_key? + assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers') + end + end + + def test_primary_key_returns_nil_if_it_does_not_exist + if ActiveRecord::Base.connection.supports_primary_key? + assert_nil ActiveRecord::Base.connection.primary_key('developers_projects') + end + end +end -- cgit v1.2.3 From 0fce4ae57fa356e9eca7059a4ff6a67e68c37961 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 16 Jul 2010 22:24:18 -0400 Subject: expected value should come first in assert_equal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/primary_keys_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 73f4b3848c..1e44237e0a 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -13,7 +13,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase topic = Topic.new assert topic.to_key.nil? topic = Topic.find(1) - assert_equal topic.to_key, [1] + assert_equal [1], topic.to_key end def test_to_key_with_customized_primary_key @@ -26,7 +26,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_to_key_with_primary_key_after_destroy topic = Topic.find(1) topic.destroy - assert_equal topic.to_key, [1] + assert_equal [1], topic.to_key end def test_integer_key -- cgit v1.2.3 From 387036609268c4cf68401a1333715f2ee4aecffc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 18 Jul 2010 14:19:34 -0300 Subject: Float comparison adjustment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activesupport/test/notifications_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 3e16e01d89..73c85be87c 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -200,7 +200,7 @@ module Notifications assert_equal :foo, event.name assert_equal time, event.time - assert_in_delta 10.0, event.duration, 0.00000000000001 + assert_in_delta 10.0, event.duration, 0.00001 end def test_events_consumes_information_given_as_payload -- cgit v1.2.3 From d1037a473b2099b0a96a0b2aad26f33747da49f8 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Sun, 18 Jul 2010 18:02:40 -0400 Subject: replacing around with for in the comments for callbacks --- activemodel/lib/active_model/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index e7aad17021..8c10c54b54 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -19,7 +19,7 @@ module ActiveModel # # define_model_callbacks :create, :update # - # This will provide all three standard callbacks (before, around and after) around + # This will provide all three standard callbacks (before, around and after) for # both the :create and :update methods. To implement, you need to wrap the methods # you want callbacks on in a block so that the callbacks get a chance to fire: # -- cgit v1.2.3 From 2cbef6996c41c785a626291ce29b934797359fd2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 18 Jul 2010 15:37:23 -0700 Subject: bind method is not needed, so goodbye! <3 <3 <3 --- activesupport/lib/active_support/notifications.rb | 2 +- activesupport/lib/active_support/notifications/fanout.rb | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 1444fc1609..bb5f497c83 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -65,7 +65,7 @@ module ActiveSupport end def subscribe(pattern = nil, &block) - @queue.bind(pattern).subscribe(&block) + @queue.subscribe(pattern, &block) end def unsubscribe(subscriber) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 7eefb7f20a..48ccb156ae 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -6,15 +6,9 @@ module ActiveSupport def initialize @subscribers = [] @listeners_for = {} - @pattern = nil end - def bind(pattern) - @pattern = pattern - self - end - - def subscribe(pattern = @pattern, &block) + def subscribe(pattern = nil, &block) @listeners_for.clear @subscribers << Subscriber.new(pattern, &block) @subscribers.last -- cgit v1.2.3 From 234b9699463ba435086aa253ee143014a835bbe6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 18 Jul 2010 15:39:32 -0700 Subject: tap the subscriber for easier return value --- activesupport/lib/active_support/notifications/fanout.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 48ccb156ae..6e4ff40dc3 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -10,8 +10,9 @@ module ActiveSupport def subscribe(pattern = nil, &block) @listeners_for.clear - @subscribers << Subscriber.new(pattern, &block) - @subscribers.last + Subscriber.new(pattern, &block).tap do |s| + @subscribers << s + end end def unsubscribe(subscriber) -- cgit v1.2.3 From b2c8a5fd3ea2268022915bb8a7ab449a1502b90d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 18 Jul 2010 16:49:29 -0700 Subject: Notifier API == Fanout API, so replace Notifier with Fanout as they quack the same --- .../active_support/log_subscriber/test_helper.rb | 8 ++------ activesupport/lib/active_support/notifications.rb | 24 +--------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 96506a4b2b..a3fb92778b 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -33,7 +33,7 @@ module ActiveSupport module TestHelper def setup @logger = MockLogger.new - @notifier = ActiveSupport::Notifications::Notifier.new(queue) + @notifier = ActiveSupport::Notifications::Fanout.new ActiveSupport::LogSubscriber.colorize_logging = false @@ -81,10 +81,6 @@ module ActiveSupport def set_logger(logger) ActiveSupport::LogSubscriber.logger = logger end - - def queue - ActiveSupport::Notifications::Fanout.new - end end end -end \ No newline at end of file +end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index bb5f497c83..93d1907edc 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -47,34 +47,12 @@ module ActiveSupport delegate :instrument, :to => :instrumenter def notifier - @notifier ||= Notifier.new + @notifier ||= Fanout.new end def instrumenter Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier) end end - - class Notifier - def initialize(queue = Fanout.new) - @queue = queue - end - - def publish(*args) - @queue.publish(*args) - end - - def subscribe(pattern = nil, &block) - @queue.subscribe(pattern, &block) - end - - def unsubscribe(subscriber) - @queue.unsubscribe(subscriber) - end - - def wait - @queue.wait - end - end end end -- cgit v1.2.3 From 8cbb89c0bf33e6daad3f91c6debd283b979d800c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 18 Jul 2010 17:20:20 -0700 Subject: subscriber does not need to be a block, but an object that responds to #call --- activesupport/lib/active_support/notifications/fanout.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 6e4ff40dc3..3bbb9cca44 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -8,9 +8,9 @@ module ActiveSupport @listeners_for = {} end - def subscribe(pattern = nil, &block) + def subscribe(pattern = nil, block = Proc.new) @listeners_for.clear - Subscriber.new(pattern, &block).tap do |s| + Subscriber.new(pattern, block).tap do |s| @subscribers << s end end @@ -33,14 +33,14 @@ module ActiveSupport end class Subscriber #:nodoc: - def initialize(pattern, &block) + def initialize(pattern, delegate) @pattern = pattern - @block = block + @delegate = delegate end def publish(*args) return unless subscribed_to?(args.first) - @block.call(*args) + @delegate.call(*args) true end -- cgit v1.2.3 From ad8f4dfc50fe3858b80aeceb6d9240d2af4a2fea Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 18 Jul 2010 17:37:39 -0700 Subject: avoid proc activation every time a log message is made --- activesupport/lib/active_support/log_subscriber.rb | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 891d718af3..7611aff964 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -63,15 +63,9 @@ module ActiveSupport @@flushable_loggers = nil log_subscriber.public_methods(false).each do |event| - notifier.subscribe("#{event}.#{namespace}") do |*args| - next if log_subscriber.logger.nil? - - begin - log_subscriber.send(event, ActiveSupport::Notifications::Event.new(*args)) - rescue Exception => e - log_subscriber.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}" - end - end + next if 'call' == event.to_s + + notifier.subscribe("#{event}.#{namespace}", log_subscriber) end end @@ -92,6 +86,17 @@ module ActiveSupport flushable_loggers.each(&:flush) end + def call(message, *args) + return unless logger + + method = message.split('.').first + begin + send(method, ActiveSupport::Notifications::Event.new(message, *args)) + rescue Exception => e + logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message}" + end + end + protected %w(info debug warn error fatal unknown).each do |level| -- cgit v1.2.3 From bcec0f6528839616cad09374f77014a3969eb63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Mon, 19 Jul 2010 12:06:19 +0200 Subject: correct typos in Routing examples --- actionpack/lib/action_dispatch/routing.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index da62b14f9b..8afc685fdf 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -106,7 +106,7 @@ module ActionDispatch # You can specify a regular expression to define a format for a parameter. # # controller 'geocode' do - # match 'geocode/:postalcode' => :show', :constraints => { + # match 'geocode/:postalcode' => :show, :constraints => { # :postalcode => /\d{5}(-\d{4})?/ # } # @@ -114,13 +114,13 @@ module ActionDispatch # expression modifiers: # # controller 'geocode' do - # match 'geocode/:postalcode' => :show', :constraints => { + # match 'geocode/:postalcode' => :show, :constraints => { # :postalcode => /hx\d\d\s\d[a-z]{2}/i # } # end # # controller 'geocode' do - # match 'geocode/:postalcode' => :show', :constraints => { + # match 'geocode/:postalcode' => :show, :constraints => { # :postalcode => /# Postcode format # \d{5} #Prefix # (-\d{4})? #Suffix -- cgit v1.2.3 From 33c5689e2d04aa08759903bc5d1e4de3bf6c35dd Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 8 Jun 2010 11:59:09 -0400 Subject: Exceptions from views should be rescued based on the original exception. If a handler for original exception is missing then apply ActiveView::TemplateError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#2034 state:resolved] Signed-off-by: José Valim --- actionpack/lib/action_controller.rb | 1 + actionpack/lib/action_controller/base.rb | 6 +++-- .../action_controller/metal/rescue_with_helper.rb | 14 +++++++++++ actionpack/test/controller/rescue_test.rb | 27 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 actionpack/lib/action_controller/metal/rescue_with_helper.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 1bd4572a47..1b23a8e916 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -28,6 +28,7 @@ module ActionController autoload :Rendering autoload :RequestForgeryProtection autoload :Rescue + autoload :RescueWithHelper autoload :Responder autoload :SessionManagement autoload :Streaming diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 1a2cbaab65..9616aa1639 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -49,7 +49,9 @@ module ActionController AbstractController::Callbacks, # The same with rescue, append it at the end to wrap as much as possible. - Rescue + Rescue, + + RescueWithHelper ] MODULES.each do |mod| @@ -69,4 +71,4 @@ module ActionController end end -require "action_controller/deprecated/base" \ No newline at end of file +require "action_controller/deprecated/base" diff --git a/actionpack/lib/action_controller/metal/rescue_with_helper.rb b/actionpack/lib/action_controller/metal/rescue_with_helper.rb new file mode 100644 index 0000000000..7894deaeef --- /dev/null +++ b/actionpack/lib/action_controller/metal/rescue_with_helper.rb @@ -0,0 +1,14 @@ +module ActionController #:nodoc: + module RescueWithHelper + + def rescue_with_handler(exception) + if ((exception.class == ActionView::TemplateError) && + (orig_exception = exception.original_exception) && + (orig_handler = handler_for_rescue(orig_exception))) + exception = orig_exception + end + super(exception) + end + + end +end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 0f64b77647..a24de62b19 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -79,6 +79,14 @@ class RescueController < ActionController::Base render :text => 'no way' end + rescue_from ActionView::TemplateError do + render :text => 'action_view templater error' + end + + rescue_from IOError do + render :text => 'io error' + end + before_filter(:only => :before_filter_raises) { raise 'umm nice' } def before_filter_raises @@ -141,6 +149,14 @@ class RescueController < ActionController::Base def missing_template end + + def io_error_in_view + raise ActionView::TemplateError.new(nil, {}, IOError.new('this is io error')) + end + + def zero_division_error_in_view + raise ActionView::TemplateError.new(nil, {}, ZeroDivisionError.new('this is zero division error')) + end protected def deny_access @@ -228,6 +244,17 @@ class ControllerInheritanceRescueControllerTest < ActionController::TestCase end class RescueControllerTest < ActionController::TestCase + + def test_io_error_in_view + get :io_error_in_view + assert_equal 'io error', @response.body + end + + def test_zero_division_error_in_view + get :zero_division_error_in_view + assert_equal 'action_view templater error', @response.body + end + def test_rescue_handler get :not_authorized assert_response :forbidden -- cgit v1.2.3 From ab6ff859067b35bda62e9a92fcd710cf925ba2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 19 Jul 2010 14:49:20 +0200 Subject: No need to create a new module in the previous commit. --- actionpack/lib/action_controller.rb | 1 - actionpack/lib/action_controller/base.rb | 4 +--- actionpack/lib/action_controller/metal/rescue.rb | 9 +++++++++ .../lib/action_controller/metal/rescue_with_helper.rb | 14 -------------- 4 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 actionpack/lib/action_controller/metal/rescue_with_helper.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 1b23a8e916..1bd4572a47 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -28,7 +28,6 @@ module ActionController autoload :Rendering autoload :RequestForgeryProtection autoload :Rescue - autoload :RescueWithHelper autoload :Responder autoload :SessionManagement autoload :Streaming diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9616aa1639..cfe6b30add 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -49,9 +49,7 @@ module ActionController AbstractController::Callbacks, # The same with rescue, append it at the end to wrap as much as possible. - Rescue, - - RescueWithHelper + Rescue ] MODULES.each do |mod| diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb index bbca1b2179..cc2b020e03 100644 --- a/actionpack/lib/action_controller/metal/rescue.rb +++ b/actionpack/lib/action_controller/metal/rescue.rb @@ -3,6 +3,15 @@ module ActionController #:nodoc: extend ActiveSupport::Concern include ActiveSupport::Rescuable + def rescue_with_handler(exception) + if ((exception.respond_to?(:original_exception)) && + (orig_exception = exception.original_exception) && + (orig_handler = handler_for_rescue(orig_exception))) + exception = orig_exception + end + super(exception) + end + private def process_action(*args) super diff --git a/actionpack/lib/action_controller/metal/rescue_with_helper.rb b/actionpack/lib/action_controller/metal/rescue_with_helper.rb deleted file mode 100644 index 7894deaeef..0000000000 --- a/actionpack/lib/action_controller/metal/rescue_with_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActionController #:nodoc: - module RescueWithHelper - - def rescue_with_handler(exception) - if ((exception.class == ActionView::TemplateError) && - (orig_exception = exception.original_exception) && - (orig_handler = handler_for_rescue(orig_exception))) - exception = orig_exception - end - super(exception) - end - - end -end -- cgit v1.2.3 From 70f7ba3e3bf10e1a646ef6ace09da7b2918ba75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 19 Jul 2010 14:56:09 +0200 Subject: There is absolutely no need to use __send__ here. --- actionpack/lib/action_controller/polymorphic_routes.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index bee50a7a3b..b4837c610a 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -110,7 +110,7 @@ module ActionController args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end - __send__(named_route, *args) + send(named_route, *args) end # Returns the path component of a URL for the given record. It uses @@ -154,7 +154,7 @@ module ActionController if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << RecordIdentifier.__send__("plural_class_name", parent).singularize + string << RecordIdentifier.plural_class_name(parent).singularize string << "_" end end @@ -163,7 +163,7 @@ module ActionController if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << RecordIdentifier.__send__("plural_class_name", record) + route << RecordIdentifier.plural_class_name(record) route = route.singularize if inflection == :singular route << "_" route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural -- cgit v1.2.3 From b22c11fa533fd523e8cadd36e75dd76b6a9f0488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 19 Jul 2010 15:14:26 +0200 Subject: Add missing entries and tidy up CHANGELOG. --- actionpack/CHANGELOG | 2 ++ activemodel/CHANGELOG | 5 +++++ activerecord/CHANGELOG | 2 ++ railties/CHANGELOG | 14 ++++++++------ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 32aba2091a..6e07516a58 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0.0 [Release Candidate] (unreleased)* +* Allow stylesheet/javascript extensions to be changed through railties. [Josh Kalderimis] + * link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn] * url_for returns always unescaped strings, and the :escape option is gone. [fxn] diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index a5e7f300d9..33602657f5 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -1,3 +1,8 @@ +*Rails 3.0.0 [Release Candidate] (unreleased)* + +* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis] + + *Rails 3.0.0 [beta 4] (June 8th, 2010)* * JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh] diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index a1a82fdff5..679fdafae8 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0.0 [RC1] (unreleased)* +* Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh] + * Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim] * New rake task, db:migrate:status, displays status of migrations #4947 [Kevin Skoglund] diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 8e2bac7f00..bdfaa0decf 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0.0 [Release Candidate] (unreleased)* +* Added console to Rails::Railtie as a hook called just after console starts. [José Valim] + * Rails no longer autoload code in lib for application. You need to explicitly require it. [José Valim] * Rails::LogSubscriber was renamed to ActiveSupport::LogSubscriber [José Valim] @@ -13,26 +15,26 @@ *Rails 3.0.0 [beta 4] (June 8th, 2010)* -* Version bump -* Removed Rails Metal [YK & JV]. +* Removed Rails Metal [Yehuda Kayz, José Valim]. + *Rails 3.0.0 [beta 3] (April 13th, 2010)* -* Renamed config.cookie_secret to config.secret_token and pass it as env key. [JV] +* Renamed config.cookie_secret to config.secret_token and pass it as env key. [José Valim] *Rails 3.0.0 [beta 2] (April 1st, 2010)* -* Session store configuration has changed [YK & CL] +* Session store configuration has changed [Yehuda Katz, Carl Lerche] config.session_store :cookie_store, {:key => "..."} config.cookie_secret = "fdsfhisdghfidugnfdlg" * railtie_name and engine_name are deprecated. You can now add any object to - the configuration object: config.your_plugin = {} [JV] + the configuration object: config.your_plugin = {} [José Valim] * Added config.generators.templates to provide alternative paths for the generators - to look for templates [JV] + to look for templates [José Valim] *Rails 3.0.0 [beta 1] (February 4, 2010)* -- cgit v1.2.3 From 247886e1b4256aeebc6b5fde0549400240b04e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Thu, 15 Jul 2010 03:43:59 +0800 Subject: Removed warnings when a variable is shadowed --- activerecord/lib/active_record/association_preload.rb | 4 ++-- .../lib/active_record/associations/association_collection.rb | 6 +++--- activerecord/test/cases/associations/inverse_associations_test.rb | 4 ++-- activerecord/test/cases/base_test.rb | 4 ++-- activerecord/test/cases/fixtures_test.rb | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index cbec5789fd..9172ab2a20 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -112,13 +112,13 @@ module ActiveRecord # Not all records have the same class, so group then preload # group on the reflection itself so that if various subclass share the same association then we do not split them # unnecessarily - records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records| + records.group_by { |record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, _records| raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus, # the following could call 'preload_belongs_to_association', # 'preload_has_many_association', etc. - send("preload_#{reflection.macro}_association", records, reflection, preload_options) + send("preload_#{reflection.macro}_association", _records, reflection, preload_options) end end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index f4e34657a5..7abb738a74 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -218,9 +218,9 @@ module ActiveRecord # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) - remove_records(records) do |records, old_records| + remove_records(records) do |_records, old_records| delete_records(old_records) if old_records.any? - records.each { |record| @target.delete(record) } + _records.each { |record| @target.delete(record) } end end @@ -231,7 +231,7 @@ module ActiveRecord # ignoring the +:dependent+ option. def destroy(*records) records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)} - remove_records(records) do |records, old_records| + remove_records(records) do |_records, old_records| old_records.each { |record| record.destroy } end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 34d24a2948..fa5c2e49df 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -412,7 +412,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase i = interests(:trainspotting) m = i.man assert_not_nil m.interests - iz = m.interests.detect {|iz| iz.id == i.id} + iz = m.interests.detect { |_iz| _iz.id == i.id} assert_not_nil iz assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child" i.topic = 'Eating cheese with a spoon' @@ -516,7 +516,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase i = interests(:llama_wrangling) m = i.polymorphic_man assert_not_nil m.polymorphic_interests - iz = m.polymorphic_interests.detect {|iz| iz.id == i.id} + iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id} assert_not_nil iz assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child" i.topic = 'Eating cheese with a spoon' diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 831dd446ad..709e22a648 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1816,8 +1816,8 @@ class BasicsTest < ActiveRecord::TestCase def test_to_xml_with_block value = "Rockin' the block" - xml = Company.new.to_xml(:skip_instruct => true) do |xml| - xml.tag! "arbitrary-element", value + xml = Company.new.to_xml(:skip_instruct => true) do |_xml| + _xml.tag! "arbitrary-element", value end assert_equal "", xml.first(9) assert xml.include?(%(#{value})) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 8008b86f81..93f8749255 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase fixtures = nil assert_nothing_raised { fixtures = create_fixtures(name) } assert_kind_of(Fixtures, fixtures) - fixtures.each { |name, fixture| + fixtures.each { |_name, fixture| fixture.each { |key, value| assert_match(MATCH_ATTRIBUTE_NAME, key) } @@ -229,9 +229,9 @@ if Account.connection.respond_to?(:reset_pk_sequence!) def test_create_fixtures_resets_sequences_when_not_cached @instances.each do |instance| - max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)| + max_id = create_fixtures(instance.class.table_name).inject(0) do |_max_id, (name, fixture)| fixture_id = fixture['id'].to_i - fixture_id > max_id ? fixture_id : max_id + fixture_id > _max_id ? fixture_id : _max_id end # Clone the last fixture to check that it gets the next greatest id. -- cgit v1.2.3 From fc71d592195b6e04e04cdbb5e640716b0d909e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Sat, 17 Jul 2010 17:28:07 +0800 Subject: Introduced redefine_method --- .../core_ext/module/remove_method.rb | 5 +++++ .../source/active_support_core_extensions.textile | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index 2714a46b28..b8c01aca0e 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -3,4 +3,9 @@ class Module remove_method(method) rescue NameError end + + def redefine_method(method, &block) + remove_possible_method(method) + define_method(method, &block) + end end \ No newline at end of file diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index a0ed8d6a90..a895dbded2 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -781,6 +781,28 @@ end This may come in handy if you need to define a method that may already exist, since redefining a method issues a warning "method redefined; discarding old redefined_method_name". +h5. +redefine_method(method_name, &block)+ + +The method first removes method with given name (using +remove_possible_method+) and then defines new one. + + +class A; end + +A.class_eval do + redefine_method(:foobar) do |foo| + #do something here + end + + #Code above does the same as this: + + method_name = :foobar + remove_possible_method(method_name) + define_method(method_name) do |foo| + #do something here + end +end + + NOTE: Defined in +active_support/core_ext/module/remove_method.rb+. h4. Parents -- cgit v1.2.3 From 010eda2eb516d237a7b5cf022b7dd2213ff77bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Sat, 17 Jul 2010 03:21:14 +0800 Subject: Removed warnings about method redefining --- activerecord/lib/active_record/associations.rb | 33 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d67df64f59..a9d256a771 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1354,6 +1354,7 @@ module ActiveRecord end def association_accessor_methods(reflection, association_proxy_class) + remove_possible_method(reflection.name) define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = association_instance_get(reflection.name) @@ -1371,12 +1372,16 @@ module ActiveRecord association.target.nil? ? nil : association end - define_method("loaded_#{reflection.name}?") do + method = "loaded_#{reflection.name}?" + remove_possible_method(method) + define_method(method) do association = association_instance_get(reflection.name) association && association.loaded? end - define_method("#{reflection.name}=") do |new_value| + method = "#{reflection.name}=" + remove_possible_method(method) + define_method(method) do |new_value| association = association_instance_get(reflection.name) if association.nil? || association.target != new_value @@ -1386,8 +1391,10 @@ module ActiveRecord association.replace(new_value) association_instance_set(reflection.name, new_value.nil? ? nil : association) end - - define_method("set_#{reflection.name}_target") do |target| + + method = "set_#{reflection.name}_target" + remove_possible_method(method) + define_method(method) do |target| return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) association.target = target @@ -1396,6 +1403,7 @@ module ActiveRecord end def collection_reader_method(reflection, association_proxy_class) + remove_possible_method(reflection.name) define_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = association_instance_get(reflection.name) @@ -1409,8 +1417,10 @@ module ActiveRecord association end - - define_method("#{reflection.name.to_s.singularize}_ids") do + + method = "#{reflection.name.to_s.singularize}_ids" + remove_possible_method(method) + define_method(method) do if send(reflection.name).loaded? || reflection.options[:finder_sql] send(reflection.name).map(&:id) else @@ -1436,8 +1446,10 @@ module ActiveRecord association.replace(new_value) association end - - define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| + + method = "#{reflection.name.to_s.singularize}_ids=" + remove_possible_method(method) + define_method(method) do |new_value| ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i) send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end @@ -1445,7 +1457,9 @@ module ActiveRecord end def association_constructor_method(constructor, reflection, association_proxy_class) - define_method("#{constructor}_#{reflection.name}") do |*params| + method = "#{constructor}_#{reflection.name}" + remove_possible_method(method) + define_method(method) do |*params| attributees = params.first unless params.empty? replace_existing = params[1].nil? ? true : params[1] association = association_instance_get(reflection.name) @@ -1487,6 +1501,7 @@ module ActiveRecord def add_touch_callbacks(reflection, touch_attribute) method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym + remove_possible_method(method_name) define_method(method_name) do association = send(reflection.name) -- cgit v1.2.3 From 7637b7184a990ae51f3ab10e2af99ff88cb633ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Thu, 15 Jul 2010 04:40:17 +0800 Subject: Test for behaviour of befeore_type_cast when operating on datetime colmun --- activerecord/test/cases/base_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 709e22a648..df6895f0d0 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -123,6 +123,14 @@ class BasicsTest < ActiveRecord::TestCase assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] else assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + + developer.created_at = "345643456" + assert_equal developer.created_at_before_type_cast, "345643456" + assert_equal developer.created_at, nil + + developer.created_at = "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") end end -- cgit v1.2.3 From 661fd98aad027b6171253828ed89115f30fac46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Sat, 17 Jul 2010 17:42:17 +0800 Subject: Make use of redefine_method, removed some more redefining warnings --- activerecord/lib/active_record/associations.rb | 39 +++++++--------------- activerecord/lib/active_record/fixtures.rb | 2 +- activerecord/lib/active_record/named_scope.rb | 2 +- .../cases/adapters/mysql/active_schema_test.rb | 1 + 4 files changed, 15 insertions(+), 29 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a9d256a771..7cddb9c601 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1354,8 +1354,7 @@ module ActiveRecord end def association_accessor_methods(reflection, association_proxy_class) - remove_possible_method(reflection.name) - define_method(reflection.name) do |*params| + redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = association_instance_get(reflection.name) @@ -1372,16 +1371,12 @@ module ActiveRecord association.target.nil? ? nil : association end - method = "loaded_#{reflection.name}?" - remove_possible_method(method) - define_method(method) do + redefine_method("loaded_#{reflection.name}?") do association = association_instance_get(reflection.name) association && association.loaded? end - - method = "#{reflection.name}=" - remove_possible_method(method) - define_method(method) do |new_value| + + redefine_method("#{reflection.name}=") do |new_value| association = association_instance_get(reflection.name) if association.nil? || association.target != new_value @@ -1392,9 +1387,7 @@ module ActiveRecord association_instance_set(reflection.name, new_value.nil? ? nil : association) end - method = "set_#{reflection.name}_target" - remove_possible_method(method) - define_method(method) do |target| + redefine_method("set_#{reflection.name}_target") do |target| return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) association.target = target @@ -1403,8 +1396,7 @@ module ActiveRecord end def collection_reader_method(reflection, association_proxy_class) - remove_possible_method(reflection.name) - define_method(reflection.name) do |*params| + redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? association = association_instance_get(reflection.name) @@ -1418,9 +1410,7 @@ module ActiveRecord association end - method = "#{reflection.name.to_s.singularize}_ids" - remove_possible_method(method) - define_method(method) do + redefine_method("#{reflection.name.to_s.singularize}_ids") do if send(reflection.name).loaded? || reflection.options[:finder_sql] send(reflection.name).map(&:id) else @@ -1440,16 +1430,14 @@ module ActiveRecord collection_reader_method(reflection, association_proxy_class) if writer - define_method("#{reflection.name}=") do |new_value| + redefine_method("#{reflection.name}=") do |new_value| # Loads proxy class instance (defined in collection_reader_method) if not already loaded association = send(reflection.name) association.replace(new_value) association end - method = "#{reflection.name.to_s.singularize}_ids=" - remove_possible_method(method) - define_method(method) do |new_value| + redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i) send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end @@ -1457,9 +1445,7 @@ module ActiveRecord end def association_constructor_method(constructor, reflection, association_proxy_class) - method = "#{constructor}_#{reflection.name}" - remove_possible_method(method) - define_method(method) do |*params| + redefine_method("#{constructor}_#{reflection.name}") do |*params| attributees = params.first unless params.empty? replace_existing = params[1].nil? ? true : params[1] association = association_instance_get(reflection.name) @@ -1500,9 +1486,8 @@ module ActiveRecord end def add_touch_callbacks(reflection, touch_attribute) - method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym - remove_possible_method(method_name) - define_method(method_name) do + method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}" + redefine_method(method_name) do association = send(reflection.name) if touch_attribute == true diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 5bf43b3a72..657303fd14 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -871,7 +871,7 @@ module ActiveRecord table_names.each do |table_name| table_name = table_name.to_s.tr('./', '_') - define_method(table_name) do |*fixtures| + redefine_method(table_name) do |*fixtures| force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload @fixture_cache[table_name] ||= {} diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 849ec9c884..6596c695e2 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -105,7 +105,7 @@ module ActiveRecord extension ? relation.extending(extension) : relation end - singleton_class.send :define_method, name, &scopes[name] + singleton_class.send(:redefine_method, name, &scopes[name]) end def named_scope(*args, &block) diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 6e6645511c..ed4efdc1c0 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -101,6 +101,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase #we need to actually modify some data, so we make execute point to the original method ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do alias_method :execute_with_stub, :execute + remove_method :execute alias_method :execute, :execute_without_stub end yield -- cgit v1.2.3 From bd4b3d8b2fc6b657ca2981582f8736842ac500d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Sat, 17 Jul 2010 23:53:05 +0800 Subject: Added missing require of remove_method --- activerecord/lib/active_record/associations.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 7cddb9c601..1b9b725dd4 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/conversions' +require 'active_support/core_ext/module/remove_method' module ActiveRecord class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: -- cgit v1.2.3 From dd4e81df86a119a32d52396c4ef856c856b7965a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 19 Jul 2010 10:21:45 -0700 Subject: avoid call to Array#first --- activesupport/lib/active_support/notifications/fanout.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 3bbb9cca44..526ca26764 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -38,9 +38,9 @@ module ActiveSupport @delegate = delegate end - def publish(*args) - return unless subscribed_to?(args.first) - @delegate.call(*args) + def publish(message, *args) + return unless subscribed_to?(message) + @delegate.call(message, *args) true end -- cgit v1.2.3 From ad4ef4226fd5d47252d30effa41a3ab2f55dbc8d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 19 Jul 2010 10:31:24 -0700 Subject: avoid const lookup. we know what these constants are in advance --- activerecord/lib/active_record/log_subscriber.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 71065f9908..89c79055fe 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -10,10 +10,10 @@ module ActiveRecord sql = event.payload[:sql].squeeze(' ') if odd? - name = color(name, :cyan, true) + name = color(name, CYAN, true) sql = color(sql, nil, true) else - name = color(name, :magenta, true) + name = color(name, MAGENTA, true) end debug " #{name} #{sql}" @@ -29,4 +29,4 @@ module ActiveRecord end end -ActiveRecord::LogSubscriber.attach_to :active_record \ No newline at end of file +ActiveRecord::LogSubscriber.attach_to :active_record -- cgit v1.2.3 From 38f0161aabb302550e1522cb62d19e54d448be9b Mon Sep 17 00:00:00 2001 From: Daniel Guettler Date: Sun, 18 Jul 2010 07:30:48 -0400 Subject: Minor performance improvment in notifications/fanout and active_record/log_subscriber [#5098 state:open] --- activerecord/lib/active_record/log_subscriber.rb | 2 ++ activesupport/lib/active_support/notifications/fanout.rb | 12 +++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 89c79055fe..278e192e59 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -6,6 +6,8 @@ module ActiveRecord end def sql(event) + return unless logger.debug? + name = '%s (%.1fms)' % [event.payload[:name], event.duration] sql = event.payload[:sql].squeeze(' ') diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 526ca26764..64f315cb6a 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -21,11 +21,11 @@ module ActiveSupport end def publish(name, *args) - if listeners = @listeners_for[name] - listeners.each { |s| s.publish(name, *args) } - else - @listeners_for[name] = @subscribers.select { |s| s.publish(name, *args) } - end + listeners_for(name).each { |s| s.publish(name, *args) } + end + + def listeners_for(name) + @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } end # This is a sync queue, so there is not waiting. @@ -39,9 +39,7 @@ module ActiveSupport end def publish(message, *args) - return unless subscribed_to?(message) @delegate.call(message, *args) - true end def subscribed_to?(name) -- cgit v1.2.3 From 1b26c66ce470ce68674bbdce738c6f68467cff7d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 19 Jul 2010 13:19:28 -0700 Subject: mocking out debing? call in the MockLogger --- activerecord/test/cases/log_subscriber_test.rb | 2 ++ activesupport/lib/active_support/log_subscriber/test_helper.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index cde383783b..fa53fc7477 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -22,6 +22,7 @@ class LogSubscriberTest < ActiveRecord::TestCase end def test_basic_query_logging + @logger.debugging = true Developer.all wait assert_equal 1, @logger.logged(:debug).size @@ -30,6 +31,7 @@ class LogSubscriberTest < ActiveRecord::TestCase end def test_cached_queries + @logger.debugging = true ActiveRecord::Base.cache do Developer.all Developer.all diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index a3fb92778b..0f5fc3554b 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -48,9 +48,12 @@ module ActiveSupport class MockLogger attr_reader :flush_count + attr_accessor :debugging + alias :debug? :debugging def initialize @flush_count = 0 + @debugging = false @logged = Hash.new { |h,k| h[k] = [] } end -- cgit v1.2.3 From d39c3b179c2c0d31099033b3de4a866e19ce144b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 20 Jul 2010 04:28:51 +0800 Subject: Test added, we shouldn't log sql calls when logger is not on debug? mode --- activerecord/test/cases/log_subscriber_test.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index fa53fc7477..2a207bed8a 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -41,4 +41,21 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/CACHE/, @logger.logged(:debug).last) assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end + + def test_basic_query_doesnt_log_when_level_is_not_debug + @logger.debugging = false + Developer.all + wait + assert_equal 0, @logger.logged(:debug).size + end + + def test_cached_queries_doesnt_log_when_level_is_not_debug + @logger.debugging = false + ActiveRecord::Base.cache do + Developer.all + Developer.all + end + wait + assert_equal 0, @logger.logged(:debug).size + end end -- cgit v1.2.3 From 17600eb435693f333af46912fd796fcdff5657b2 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 20 Jul 2010 00:03:20 +0800 Subject: Remove unused orig_handler and unneeded parentheses --- actionpack/lib/action_controller/metal/rescue.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb index cc2b020e03..eb037aa1b0 100644 --- a/actionpack/lib/action_controller/metal/rescue.rb +++ b/actionpack/lib/action_controller/metal/rescue.rb @@ -4,9 +4,9 @@ module ActionController #:nodoc: include ActiveSupport::Rescuable def rescue_with_handler(exception) - if ((exception.respond_to?(:original_exception)) && - (orig_exception = exception.original_exception) && - (orig_handler = handler_for_rescue(orig_exception))) + if (exception.respond_to?(:original_exception) && + (orig_exception = exception.original_exception) && + handler_for_rescue(orig_exception)) exception = orig_exception end super(exception) -- cgit v1.2.3 From c3c349ec3e9a3990cac4d256c308b18fd35d9606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 19 Jul 2010 22:33:51 +0200 Subject: Remove assert_valid. It was already deprecated on Rails 2.3. --- actionpack/lib/action_dispatch/testing/assertions.rb | 2 -- .../lib/action_dispatch/testing/assertions/model.rb | 19 ------------------- .../test/controller/action_pack_assertions_test.rb | 15 --------------- 3 files changed, 36 deletions(-) delete mode 100644 actionpack/lib/action_dispatch/testing/assertions/model.rb diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 0e4a92048f..822150b768 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,7 +1,6 @@ module ActionDispatch module Assertions autoload :DomAssertions, 'action_dispatch/testing/assertions/dom' - autoload :ModelAssertions, 'action_dispatch/testing/assertions/model' autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response' autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing' autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector' @@ -11,7 +10,6 @@ module ActionDispatch included do include DomAssertions - include ModelAssertions include ResponseAssertions include RoutingAssertions include SelectorAssertions diff --git a/actionpack/lib/action_dispatch/testing/assertions/model.rb b/actionpack/lib/action_dispatch/testing/assertions/model.rb deleted file mode 100644 index 46714418c6..0000000000 --- a/actionpack/lib/action_dispatch/testing/assertions/model.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActionDispatch - module Assertions - module ModelAssertions - # Ensures that the passed record is valid by Active Record standards and - # returns any error messages if it is not. - # - # ==== Examples - # - # # assert that a newly created record is valid - # model = Model.new - # assert_valid(model) - # - def assert_valid(record) - ::ActiveSupport::Deprecation.warn("assert_valid is deprecated. Use assert record.valid? instead", caller) - assert record.valid?, record.errors.full_messages.join("\n") - end - end - end -end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 765e111226..53cdd358b4 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -482,21 +482,6 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase assert_redirected_to :controller => 'admin/user' end - def test_assert_valid - get :get_valid_record - assert_deprecated { assert_valid assigns('record') } - end - - def test_assert_valid_failing - get :get_invalid_record - - begin - assert_deprecated { assert_valid assigns('record') } - assert false - rescue ActiveSupport::TestCase::Assertion => e - end - end - def test_assert_response_uses_exception_message @controller = AssertResponseWithUnexpectedErrorController.new get :index -- cgit v1.2.3 From 202fb79e8686ee127fe49497c979cfc9c9d985d5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 19 Jul 2010 13:44:11 -0700 Subject: reusing the time instrumentation from the instrumenter rather than Benchmark. [#5098 state:open] --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 11 +++++++---- .../lib/active_support/notifications/instrumenter.rb | 11 +++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index be8d1bd76b..6072481411 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -199,11 +199,14 @@ module ActiveRecord def log(sql, name) name ||= "SQL" - result = nil - ActiveSupport::Notifications.instrument("sql.active_record", - :sql => sql, :name => name, :connection_id => self.object_id) do - @runtime += Benchmark.ms { result = yield } + instrumenter = ActiveSupport::Notifications.instrumenter + + result = instrumenter.instrument("sql.active_record", + :sql => sql, :name => name, :connection_id => object_id) do + yield end + @runtime += instrumenter.elapsed + result rescue Exception => e message = "#{e.class.name}: #{e.message}: #{sql}" diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 34bccb83d0..ff2b19bc65 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -9,23 +9,30 @@ module ActiveSupport def initialize(notifier) @id = unique_id @notifier = notifier + @started = nil + @finished = nil end # Instrument the given block by measuring the time taken to execute it # and publish it. Notice that events get sent even if an error occurs # in the passed-in block def instrument(name, payload={}) - time = Time.now + @started = Time.now begin yield(payload) if block_given? rescue Exception => e payload[:exception] = [e.class.name, e.message] raise e ensure - @notifier.publish(name, time, Time.now, @id, payload) + @finished = Time.now + @notifier.publish(name, @started, @finished, @id, payload) end end + def elapsed + 1000.0 * @finished.to_f - @started.to_f + end + private def unique_id SecureRandom.hex(10) -- cgit v1.2.3 From e466354edb31f243899051e2119f4ce72bafd5f3 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 19 Jul 2010 13:33:26 -0700 Subject: Extract ParameterFilter class from FilterParameters mixin Signed-off-by: wycats --- actionpack/lib/action_dispatch.rb | 1 + .../lib/action_dispatch/http/filter_parameters.rb | 73 +++------------------- .../lib/action_dispatch/http/parameter_filter.rb | 72 +++++++++++++++++++++ actionpack/test/dispatch/request_test.rb | 8 +-- 4 files changed, 86 insertions(+), 68 deletions(-) create mode 100644 actionpack/lib/action_dispatch/http/parameter_filter.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index cdf81c6648..1da4a0d4be 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -63,6 +63,7 @@ module ActionDispatch autoload :Headers autoload :MimeNegotiation autoload :Parameters + autoload :ParameterFilter autoload :FilterParameters autoload :Upload autoload :UploadedFile, 'action_dispatch/http/upload' diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 47643ce130..1ab48ae04d 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -26,85 +26,30 @@ module ActionDispatch module FilterParameters extend ActiveSupport::Concern - @@compiled_parameter_filter_for = {} + @@parameter_filter_for = {} # Return a hash of parameters with all sensitive data replaced. def filtered_parameters - @filtered_parameters ||= if filtering_parameters? - process_parameter_filter(parameters) - else - parameters.dup - end + @filtered_parameters ||= parameter_filter.filter(parameters) end # Return a hash of request.env with all sensitive data replaced. def filtered_env - filtered_env = @env.dup - filtered_env.each do |key, value| - if (key =~ /RAW_POST_DATA/i) - filtered_env[key] = '[FILTERED]' - elsif value.is_a?(Hash) - filtered_env[key] = process_parameter_filter(value) - end - end - filtered_env + @filtered_env ||= env_filter.filter(@env) end protected - def filtering_parameters? #:nodoc: - @env["action_dispatch.parameter_filter"].present? + def parameter_filter + parameter_filter_for(@env["action_dispatch.parameter_filter"]) end - def process_parameter_filter(params) #:nodoc: - compiled_parameter_filter_for(@env["action_dispatch.parameter_filter"]).call(params) + def env_filter + parameter_filter_for(Array.wrap(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/) end - def compile_parameter_filter(filters) #:nodoc: - strings, regexps, blocks = [], [], [] - - filters.each do |item| - case item - when NilClass - when Proc - blocks << item - when Regexp - regexps << item - else - strings << item.to_s - end - end - - regexps << Regexp.new(strings.join('|'), true) unless strings.empty? - [regexps, blocks] - end - - def compiled_parameter_filter_for(filters) #:nodoc: - @@compiled_parameter_filter_for[filters] ||= begin - regexps, blocks = compile_parameter_filter(filters) - - lambda do |original_params| - filtered_params = {} - - original_params.each do |key, value| - if regexps.find { |r| key =~ r } - value = '[FILTERED]' - elsif value.is_a?(Hash) - value = process_parameter_filter(value) - elsif value.is_a?(Array) - value = value.map { |v| v.is_a?(Hash) ? process_parameter_filter(v) : v } - elsif blocks.present? - key = key.dup - value = value.dup if value.duplicable? - blocks.each { |b| b.call(key, value) } - end - - filtered_params[key] = value - end - - filtered_params - end - end + def parameter_filter_for(filters) + @@parameter_filter_for[filters] ||= ParameterFilter.new(filters) end end diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb new file mode 100644 index 0000000000..1480e8f77c --- /dev/null +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -0,0 +1,72 @@ +module ActionDispatch + module Http + class ParameterFilter + + def initialize(filters) + @filters = filters + end + + def filter(params) + if enabled? + compiled_filter.call(params) + else + params.dup + end + end + + private + + def enabled? + @filters.present? + end + + def compiled_filter + @compiled_filter ||= begin + regexps, blocks = compile_filter + + lambda do |original_params| + filtered_params = {} + + original_params.each do |key, value| + if regexps.find { |r| key =~ r } + value = '[FILTERED]' + elsif value.is_a?(Hash) + value = filter(value) + elsif value.is_a?(Array) + value = value.map { |v| v.is_a?(Hash) ? filter(v) : v } + elsif blocks.present? + key = key.dup + value = value.dup if value.duplicable? + blocks.each { |b| b.call(key, value) } + end + + filtered_params[key] = value + end + + filtered_params + end + end + end + + def compile_filter + strings, regexps, blocks = [], [], [] + + @filters.each do |item| + case item + when NilClass + when Proc + blocks << item + when Regexp + regexps << item + else + strings << item.to_s + end + end + + regexps << Regexp.new(strings.join('|'), true) unless strings.empty? + [regexps, blocks] + end + + end + end +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index e5ee412021..c8947aac80 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -392,19 +392,19 @@ class RequestTest < ActiveSupport::TestCase [{'baz'=>[{'foo'=>'baz'}, "1"]}, {'baz'=>[{'foo'=>'[FILTERED]'}, "1"]}, [/foo/]]] test_hashes.each do |before_filter, after_filter, filter_words| - request = stub_request('action_dispatch.parameter_filter' => filter_words) - assert_equal after_filter, request.send(:process_parameter_filter, before_filter) + parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) + assert_equal after_filter, parameter_filter.filter(before_filter) filter_words << 'blah' filter_words << lambda { |key, value| value.reverse! if key =~ /bargain/ } - request = stub_request('action_dispatch.parameter_filter' => filter_words) + parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} - assert_equal after_filter, request.send(:process_parameter_filter, before_filter) + assert_equal after_filter, parameter_filter.filter(before_filter) end end -- cgit v1.2.3 From a6e95ba55401ddcaf9ef867a080b30c2d07c56ac Mon Sep 17 00:00:00 2001 From: Kouhei Sutou Date: Sat, 17 Jul 2010 15:36:40 +0900 Subject: fix mixed encoding logs can't be logged. [#4807 state:committed] Signed-off-by: Kouhei Sutou Signed-off-by: Jeremy Kemper --- activesupport/lib/active_support/buffered_logger.rb | 6 +++++- activesupport/test/buffered_logger_test.rb | 15 +++++++++++++++ activesupport/test/multibyte_test_helpers.rb | 5 ++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 29c3843d16..b861a6f62a 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -101,7 +101,11 @@ module ActiveSupport @guard.synchronize do unless buffer.empty? old_buffer = buffer - @log.write(old_buffer.join) + all_content = StringIO.new + old_buffer.each do |content| + all_content << content + end + @log.write(all_content.string) end # Important to do this even if buffer was empty or else @buffer will diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 850febb959..97c0ef14db 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -1,9 +1,12 @@ require 'abstract_unit' +require 'multibyte_test_helpers' require 'stringio' require 'fileutils' require 'active_support/buffered_logger' class BufferedLoggerTest < Test::Unit::TestCase + include MultibyteTestHelpers + Logger = ActiveSupport::BufferedLogger def setup @@ -146,4 +149,16 @@ class BufferedLoggerTest < Test::Unit::TestCase @logger.expects :clear_buffer @logger.flush end + + def test_buffer_multibyte + @logger.auto_flushing = 2 + @logger.info(UNICODE_STRING) + @logger.info(BYTE_STRING) + assert @output.string.include?(UNICODE_STRING) + byte_string = @output.string.dup + if byte_string.respond_to?(:force_encoding) + byte_string.force_encoding("ASCII-8BIT") + end + assert byte_string.include?(BYTE_STRING) + end end diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 597f949059..8839b75601 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -4,6 +4,9 @@ module MultibyteTestHelpers UNICODE_STRING = 'こにちわ' ASCII_STRING = 'ohayo' BYTE_STRING = "\270\236\010\210\245" + if BYTE_STRING.respond_to?(:force_encoding) + BYTE_STRING.force_encoding("ASCII-8BIT") + end def chars(str) ActiveSupport::Multibyte::Chars.new(str) @@ -16,4 +19,4 @@ module MultibyteTestHelpers def assert_equal_codepoints(expected, actual, message=nil) assert_equal(inspect_codepoints(expected), inspect_codepoints(actual), message) end -end \ No newline at end of file +end -- cgit v1.2.3 From 325592038ebf16eb0feb526191155d439e6e1a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 20 Jul 2010 00:22:34 -0700 Subject: Fix typo on CHANGELOG. --- railties/CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/CHANGELOG b/railties/CHANGELOG index bdfaa0decf..6a8db7c4a6 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -15,7 +15,7 @@ *Rails 3.0.0 [beta 4] (June 8th, 2010)* -* Removed Rails Metal [Yehuda Kayz, José Valim]. +* Removed Rails Metal [Yehuda Katz, José Valim]. *Rails 3.0.0 [beta 3] (April 13th, 2010)* -- cgit v1.2.3 From 51d2db0a63529cfe6e7d7d0c620f10235c63ffe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 20 Jul 2010 09:55:24 +0200 Subject: Add missing require to metal/streaming.rb --- actionpack/lib/action_controller/metal/streaming.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 753af3dc58..d75b46dace 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/file/path' + module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. -- cgit v1.2.3 From 767de13ff4a19a53d3fe99edb5554040cb6e5e60 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 20 Jul 2010 06:56:20 -0400 Subject: expanded comment for update_attribute method --- activerecord/lib/active_record/persistence.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 7ec443ccc7..68f5be63d6 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -102,8 +102,15 @@ module ActiveRecord became end - # Updates a single attribute and saves the record without going through the normal validation procedure - # or callbacks. This is especially useful for boolean flags on existing records. + # Updates a single attribute and saves the record. + # This is especially useful for boolean flags on existing records. Also note that + # + # * validation is skipped + # * No callbacks are invoked + # * updated_at/updated_on column is updated if that column is available + # * does not work on associations + # * does not work on attr_accessor attributes. The attribute that is being updated must be column name. + # def update_attribute(name, value) changes = record_update_timestamps || {} -- cgit v1.2.3 From 2e4b741111b93d13b2b19537cf68645aff14c57e Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Tue, 20 Jul 2010 14:22:19 +0200 Subject: Active Record Validations and Callbacks guide: Fixed typos and rephrased some paragraphs for clarity --- .../active_record_validations_callbacks.textile | 23 +++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 84c33e34f9..be9917868f 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -1,22 +1,22 @@ h2. Active Record Validations and Callbacks -This guide teaches you how to hook into the lifecycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object lifecycle. +This guide teaches you how to hook into the life cycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle. After reading this guide and trying out the presented concepts, we hope that you'll be able to: -* Understand the lifecycle of Active Record objects +* Understand the life cycle of Active Record objects * Use the built-in Active Record validation helpers * Create your own custom validation methods * Work with the error messages generated by the validation process -* Create callback methods that respond to events in the object lifecycle +* Create callback methods that respond to events in the object life cycle * Create special classes that encapsulate common behavior for your callbacks -* Create Observers that respond to lifecycle events outside of the original class +* Create Observers that respond to life cycle events outside of the original class endprologue. -h3. The Object Lifecycle +h3. The Object Life Cycle -During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object lifecycle so that you can control your application and its data. +During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object life cycle so that you can control your application and its data. Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state. @@ -57,7 +57,7 @@ We can see how it works by looking at some +rails console+ output: => false -Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated. +Creating and saving a new record will send a SQL +INSERT+ operation to the database. Updating an existing record will send a SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated. CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful. @@ -838,7 +838,7 @@ This will result in something like the following: h3. Callbacks Overview -Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database. +Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database. h4. Callback Registration @@ -984,7 +984,7 @@ h3. Halting Execution As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed. -The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception. +The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception. WARNING. Raising an arbitrary exception may break code that expects +save+ and friends not to fail like that. The +ActiveRecord::Rollback+ exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised. @@ -1020,7 +1020,7 @@ Like in validations, we can also make our callbacks conditional, calling them on h4. Using +:if+ and +:unless+ with a Symbol -You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed. +You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns +false+; when using the +:unless+ option, the callback won't be executed if the method returns +true+. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed. class Order < ActiveRecord::Base @@ -1135,7 +1135,7 @@ Observers are conventionally placed inside of your +app/models+ directory and re config.active_record.observers = :user_observer -As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead. +As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead. h4. Sharing Observers @@ -1162,6 +1162,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks +* July 20, 2010: Fixed typos and rephrased some paragraphs for clarity. "Jaime Iniesta":http://jaimeiniesta.com * May 24, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * May 15, 2010: Validation Errors section updated by "Emili Parreño":http://www.eparreno.com * March 7, 2009: Callbacks revision by Trevor Turk -- cgit v1.2.3 From 9df9c4bac008ba94c37faff411368c33408faff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 20 Jul 2010 17:07:18 +0200 Subject: Add a test for elapsed and require missing benchmark file. --- .../lib/action_controller/metal/instrumentation.rb | 1 + .../active_support/notifications/instrumenter.rb | 4 +-- activesupport/test/notifications_test.rb | 30 ++++++++++------------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index b2c119d7e4..b08d9a8434 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -1,3 +1,4 @@ +require 'benchmark' require 'abstract_controller/logger' module ActionController diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index ff2b19bc65..e98189f899 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -17,8 +17,8 @@ module ActiveSupport # and publish it. Notice that events get sent even if an error occurs # in the passed-in block def instrument(name, payload={}) - @started = Time.now begin + @started = Time.now yield(payload) if block_given? rescue Exception => e payload[:exception] = [e.class.name, e.message] @@ -30,7 +30,7 @@ module ActiveSupport end def elapsed - 1000.0 * @finished.to_f - @started.to_f + 1000.0 * (@finished.to_f - @started.to_f) end private diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 73c85be87c..41e8ca4ae7 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -11,14 +11,11 @@ module Notifications @named_subscription = @notifier.subscribe("named.subscription") { |*args| @named_events << event(*args) } end - private - def event(*args) - ActiveSupport::Notifications::Event.new(*args) - end + private - def drain - @notifier.wait - end + def event(*args) + ActiveSupport::Notifications::Event.new(*args) + end end class UnsubscribeTest < TestCase @@ -132,13 +129,10 @@ module Notifications def test_instrument_returns_block_result assert_equal 2, instrument(:awesome) { 1 + 1 } - drain end def test_instrument_yields_the_paylod_for_further_modification assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 } - drain - assert_equal 1, @events.size assert_equal :awesome, @events.first.name assert_equal Hash[:result => 2], @events.first.payload @@ -154,15 +148,11 @@ module Notifications 1 + 1 end - drain - assert_equal 1, @events.size assert_equal :wot, @events.first.name assert_equal Hash[:payload => "child"], @events.first.payload end - drain - assert_equal 2, @events.size assert_equal :awesome, @events.last.name assert_equal Hash[:payload => "notifications"], @events.last.payload @@ -177,16 +167,22 @@ module Notifications assert_equal "FAIL", e.message end - drain assert_equal 1, @events.size assert_equal Hash[:payload => "notifications", :exception => ["RuntimeError", "FAIL"]], @events.last.payload end + def test_elapsed + instrument(:something) do + sleep(0.001) + end + + # Elapsed returns duration in ms + assert_in_delta 1, ActiveSupport::Notifications.instrumenter.elapsed, 100 + end + def test_event_is_pushed_even_without_block instrument(:awesome, :payload => "notifications") - drain - assert_equal 1, @events.size assert_equal :awesome, @events.last.name assert_equal Hash[:payload => "notifications"], @events.last.payload -- cgit v1.2.3 From e1df4b956882f0c10a310088c1c13dcaa655a3b1 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 19 Jul 2010 21:50:11 -0700 Subject: adding a reader for loaded, initializing @loaded to false --- activerecord/lib/active_record/named_scope.rb | 2 +- activerecord/lib/active_record/relation.rb | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 6596c695e2..417ff4b5eb 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -26,7 +26,7 @@ module ActiveRecord # You can define a \scope that applies to all finders using # ActiveRecord::Base.default_scope. def scoped(options = nil) - if options.present? + if options scoped.apply_finder_options(options) else current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7499100f55..86a210d2be 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -13,14 +13,15 @@ module ActiveRecord delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a delegate :insert, :to => :arel - attr_reader :table, :klass + attr_reader :table, :klass, :loaded attr_accessor :extensions + alias :loaded? :loaded def initialize(klass, table) @klass, @table = klass, table @implicit_readonly = nil - @loaded = nil + @loaded = false SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} @@ -292,10 +293,6 @@ module ActiveRecord where(@klass.primary_key => id_or_array).delete_all end - def loaded? - @loaded - end - def reload reset to_a # force reload -- cgit v1.2.3 From 6914d67ed15d41a239407e3c724b670d95a740a6 Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Tue, 20 Jul 2010 18:17:29 +0200 Subject: non-singleton true and false should go on regular font --- railties/guides/source/active_record_validations_callbacks.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index be9917868f..c7ba130a90 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -1020,7 +1020,7 @@ Like in validations, we can also make our callbacks conditional, calling them on h4. Using +:if+ and +:unless+ with a Symbol -You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns +false+; when using the +:unless+ option, the callback won't be executed if the method returns +true+. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed. +You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns false; when using the +:unless+ option, the callback won't be executed if the method returns true. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed. class Order < ActiveRecord::Base -- cgit v1.2.3 From d4151d7f0ac4a0823e788c0beed9ec2476e72386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 20 Jul 2010 21:20:19 +0200 Subject: Fix a failing test in Railtie and properly define all severity levels in MockLogger for LogSubscriber. --- activerecord/test/cases/log_subscriber_test.rb | 6 ++---- .../lib/active_support/log_subscriber/test_helper.rb | 18 ++++++++++++++---- .../application/initializers/notifications_test.rb | 19 +++++-------------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 2a207bed8a..91ba852ecd 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -22,7 +22,6 @@ class LogSubscriberTest < ActiveRecord::TestCase end def test_basic_query_logging - @logger.debugging = true Developer.all wait assert_equal 1, @logger.logged(:debug).size @@ -31,7 +30,6 @@ class LogSubscriberTest < ActiveRecord::TestCase end def test_cached_queries - @logger.debugging = true ActiveRecord::Base.cache do Developer.all Developer.all @@ -43,14 +41,14 @@ class LogSubscriberTest < ActiveRecord::TestCase end def test_basic_query_doesnt_log_when_level_is_not_debug - @logger.debugging = false + @logger.level = 1 Developer.all wait assert_equal 0, @logger.logged(:debug).size end def test_cached_queries_doesnt_log_when_level_is_not_debug - @logger.debugging = false + @logger.level = 1 ActiveRecord::Base.cache do Developer.all Developer.all diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 0f5fc3554b..9e52cb97a9 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -1,4 +1,5 @@ require 'active_support/log_subscriber' +require 'active_support/buffered_logger' module ActiveSupport class LogSubscriber @@ -47,13 +48,14 @@ module ActiveSupport end class MockLogger + include ActiveSupport::BufferedLogger::Severity + attr_reader :flush_count - attr_accessor :debugging - alias :debug? :debugging + attr_accessor :level - def initialize + def initialize(level = DEBUG) @flush_count = 0 - @debugging = false + @level = level @logged = Hash.new { |h,k| h[k] = [] } end @@ -68,6 +70,14 @@ module ActiveSupport def flush @flush_count += 1 end + + ActiveSupport::BufferedLogger::Severity.constants.each do |severity| + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{severity.downcase}? + #{severity} >= @level + end + EOT + end end # Wait notifications to be published. diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb index fc8548af1f..7e035be764 100644 --- a/railties/test/application/initializers/notifications_test.rb +++ b/railties/test/application/initializers/notifications_test.rb @@ -1,17 +1,6 @@ require "isolation/abstract_unit" module ApplicationTests - class MockLogger - def method_missing(*args) - @logged ||= [] - @logged << args.last - end - - def logged - @logged.compact.map { |l| l.to_s.strip } - end - end - class NotificationsTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation @@ -34,15 +23,17 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" + require "active_support/log_subscriber/test_helper" - ActiveRecord::Base.logger = logger = MockLogger.new + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActiveRecord::Base.logger = logger # Mimic Active Record notifications instrument "sql.active_record", :name => "SQL", :sql => "SHOW tables" wait - assert_equal 1, logger.logged.size - assert_match /SHOW tables/, logger.logged.last + assert_equal 1, logger.logged(:debug).size + assert_match /SHOW tables/, logger.logged(:debug).last end end end -- cgit v1.2.3 From 6ce761c8d98dafca0baeb298c10dd1fef5e4224f Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 20 Jul 2010 17:02:37 -0300 Subject: This example is better for guides and gem/plugins docs --- .../lib/rails/generators/rails/app/templates/config/application.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 7a94d6e05c..190ab04cf5 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -46,13 +46,6 @@ module <%= app_const_base %> # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) <% end -%> - # Configure generators values. Many other options are available, be sure to check the documentation. - # config.generators do |g| - # g.orm :active_record - # g.template_engine :erb - # g.test_framework :test_unit, :fixture => true - # end - # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" -- cgit v1.2.3 From 978c49ea6a969fe040ad15dbda78e43ca5afc069 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 20 Jul 2010 17:00:12 -0300 Subject: Make use of severity levels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/log_subscriber_test.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 91ba852ecd..342daa19df 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -4,6 +4,7 @@ require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper + include ActiveSupport::BufferedLogger::Severity def setup @old_logger = ActiveRecord::Base.logger @@ -41,14 +42,14 @@ class LogSubscriberTest < ActiveRecord::TestCase end def test_basic_query_doesnt_log_when_level_is_not_debug - @logger.level = 1 + @logger.level = INFO Developer.all wait assert_equal 0, @logger.logged(:debug).size end def test_cached_queries_doesnt_log_when_level_is_not_debug - @logger.level = 1 + @logger.level = INFO ActiveRecord::Base.cache do Developer.all Developer.all -- cgit v1.2.3 From a63566dda8246bd57e80032a1213532d0dc2ae0b Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 20 Jul 2010 21:07:59 +0200 Subject: Moved PolymorphicRoutes to ActionDispatch::Routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_controller.rb | 1 - actionpack/lib/action_controller/base.rb | 1 - .../lib/action_controller/polymorphic_routes.rb | 183 -------------------- actionpack/lib/action_dispatch/routing.rb | 4 +- .../action_dispatch/routing/polymorphic_routes.rb | 186 +++++++++++++++++++++ actionpack/lib/action_dispatch/routing/url_for.rb | 1 + .../helpers/record_identification_helper.rb | 2 +- actionpack/lib/action_view/test_case.rb | 2 +- 8 files changed, 191 insertions(+), 189 deletions(-) delete mode 100644 actionpack/lib/action_controller/polymorphic_routes.rb create mode 100644 actionpack/lib/action_dispatch/routing/polymorphic_routes.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 1bd4572a47..ca0e5d6ff6 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -6,7 +6,6 @@ module ActionController autoload :Base autoload :Caching - autoload :PolymorphicRoutes autoload :Metal autoload :Middleware diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index cfe6b30add..1c6896d362 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -28,7 +28,6 @@ module ActionController SessionManagement, Caching, MimeResponds, - PolymorphicRoutes, ImplicitRender, Cookies, diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb deleted file mode 100644 index b4837c610a..0000000000 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ /dev/null @@ -1,183 +0,0 @@ -module ActionController - # Polymorphic URL helpers are methods for smart resolution to a named route call when - # given an Active Record model instance. They are to be used in combination with - # ActionController::Resources. - # - # These methods are useful when you want to generate correct URL or path to a RESTful - # resource without having to know the exact type of the record in question. - # - # Nested resources and/or namespaces are also supported, as illustrated in the example: - # - # polymorphic_url([:admin, @article, @comment]) - # - # results in: - # - # admin_article_comment_url(@article, @comment) - # - # == Usage within the framework - # - # Polymorphic URL helpers are used in a number of places throughout the Rails framework: - # - # * url_for, so you can use it with a record as the argument, e.g. - # url_for(@article); - # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write - # form_for(@article) without having to specify :url parameter for the form - # action; - # * redirect_to (which, in fact, uses url_for) so you can write - # redirect_to(post) in your controllers; - # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs - # for feed entries. - # - # == Prefixed polymorphic helpers - # - # In addition to polymorphic_url and polymorphic_path methods, a - # number of prefixed helpers are available as a shorthand to :action => "..." - # in options. Those are: - # - # * edit_polymorphic_url, edit_polymorphic_path - # * new_polymorphic_url, new_polymorphic_path - # - # Example usage: - # - # edit_polymorphic_path(@post) # => "/posts/1/edit" - # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf" - module PolymorphicRoutes - # Constructs a call to a named RESTful route for the given record and returns the - # resulting URL string. For example: - # - # # calls post_url(post) - # polymorphic_url(post) # => "http://example.com/posts/1" - # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" - # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" - # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" - # polymorphic_url(Comment) # => "http://example.com/comments" - # - # ==== Options - # - # * :action - Specifies the action prefix for the named route: - # :new or :edit. Default is no prefix. - # * :routing_type - Allowed values are :path or :url. - # Default is :url. - # - # ==== Examples - # - # # an Article record - # polymorphic_url(record) # same as article_url(record) - # - # # a Comment record - # polymorphic_url(record) # same as comment_url(record) - # - # # it recognizes new records and maps to the collection - # record = Comment.new - # polymorphic_url(record) # same as comments_url() - # - # # the class of a record will also map to the collection - # polymorphic_url(Comment) # same as comments_url() - # - def polymorphic_url(record_or_hash_or_array, options = {}) - if record_or_hash_or_array.kind_of?(Array) - record_or_hash_or_array = record_or_hash_or_array.compact - record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1 - end - - record = extract_record(record_or_hash_or_array) - record = record.to_model if record.respond_to?(:to_model) - - args = case record_or_hash_or_array - when Hash; [ record_or_hash_or_array ] - when Array; record_or_hash_or_array.dup - else [ record_or_hash_or_array ] - end - - inflection = if options[:action].to_s == "new" - args.pop - :singular - elsif (record.respond_to?(:persisted?) && !record.persisted?) - args.pop - :plural - elsif record.is_a?(Class) - args.pop - :plural - else - :singular - end - - args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} - named_route = build_named_route_call(record_or_hash_or_array, inflection, options) - - url_options = options.except(:action, :routing_type) - unless url_options.empty? - args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options - end - - send(named_route, *args) - end - - # Returns the path component of a URL for the given record. It uses - # polymorphic_url with :routing_type => :path. - def polymorphic_path(record_or_hash_or_array, options = {}) - polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path)) - end - - %w(edit new).each do |action| - module_eval <<-EOT, __FILE__, __LINE__ + 1 - def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {}) - polymorphic_url( # polymorphic_url( - record_or_hash, # record_or_hash, - options.merge(:action => "#{action}")) # options.merge(:action => "edit")) - end # end - # - def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {}) - polymorphic_url( # polymorphic_url( - record_or_hash, # record_or_hash, - options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path)) - end # end - EOT - end - - private - def action_prefix(options) - options[:action] ? "#{options[:action]}_" : '' - end - - def routing_type(options) - options[:routing_type] || :url - end - - def build_named_route_call(records, inflection, options = {}) - unless records.is_a?(Array) - record = extract_record(records) - route = '' - else - record = records.pop - route = records.inject("") do |string, parent| - if parent.is_a?(Symbol) || parent.is_a?(String) - string << "#{parent}_" - else - string << RecordIdentifier.plural_class_name(parent).singularize - string << "_" - end - end - end - - if record.is_a?(Symbol) || record.is_a?(String) - route << "#{record}_" - else - route << RecordIdentifier.plural_class_name(record) - route = route.singularize if inflection == :singular - route << "_" - route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural - end - - action_prefix(options) + route + routing_type(options).to_s - end - - def extract_record(record_or_hash_or_array) - case record_or_hash_or_array - when Array; record_or_hash_or_array.last - when Hash; record_or_hash_or_array[:id] - else record_or_hash_or_array - end - end - end -end diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index da62b14f9b..b7e09b99d1 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/regexp' -require 'action_controller/polymorphic_routes' module ActionDispatch # = Routing @@ -217,13 +216,14 @@ module ActionDispatch autoload :Route, 'action_dispatch/routing/route' autoload :RouteSet, 'action_dispatch/routing/route_set' autoload :UrlFor, 'action_dispatch/routing/url_for' + autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes' SEPARATORS = %w( / . ? ) #:nodoc: HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc: # A helper module to hold URL related helpers. module Helpers #:nodoc: - include ActionController::PolymorphicRoutes + include PolymorphicRoutes end end end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb new file mode 100644 index 0000000000..18ea82c478 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -0,0 +1,186 @@ +module ActionDispatch + module Routing + # Polymorphic URL helpers are methods for smart resolution to a named route call when + # given an Active Record model instance. They are to be used in combination with + # ActionController::Resources. + # + # These methods are useful when you want to generate correct URL or path to a RESTful + # resource without having to know the exact type of the record in question. + # + # Nested resources and/or namespaces are also supported, as illustrated in the example: + # + # polymorphic_url([:admin, @article, @comment]) + # + # results in: + # + # admin_article_comment_url(@article, @comment) + # + # == Usage within the framework + # + # Polymorphic URL helpers are used in a number of places throughout the Rails framework: + # + # * url_for, so you can use it with a record as the argument, e.g. + # url_for(@article); + # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write + # form_for(@article) without having to specify :url parameter for the form + # action; + # * redirect_to (which, in fact, uses url_for) so you can write + # redirect_to(post) in your controllers; + # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs + # for feed entries. + # + # == Prefixed polymorphic helpers + # + # In addition to polymorphic_url and polymorphic_path methods, a + # number of prefixed helpers are available as a shorthand to :action => "..." + # in options. Those are: + # + # * edit_polymorphic_url, edit_polymorphic_path + # * new_polymorphic_url, new_polymorphic_path + # + # Example usage: + # + # edit_polymorphic_path(@post) # => "/posts/1/edit" + # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf" + module PolymorphicRoutes + # Constructs a call to a named RESTful route for the given record and returns the + # resulting URL string. For example: + # + # # calls post_url(post) + # polymorphic_url(post) # => "http://example.com/posts/1" + # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" + # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" + # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" + # polymorphic_url(Comment) # => "http://example.com/comments" + # + # ==== Options + # + # * :action - Specifies the action prefix for the named route: + # :new or :edit. Default is no prefix. + # * :routing_type - Allowed values are :path or :url. + # Default is :url. + # + # ==== Examples + # + # # an Article record + # polymorphic_url(record) # same as article_url(record) + # + # # a Comment record + # polymorphic_url(record) # same as comment_url(record) + # + # # it recognizes new records and maps to the collection + # record = Comment.new + # polymorphic_url(record) # same as comments_url() + # + # # the class of a record will also map to the collection + # polymorphic_url(Comment) # same as comments_url() + # + def polymorphic_url(record_or_hash_or_array, options = {}) + if record_or_hash_or_array.kind_of?(Array) + record_or_hash_or_array = record_or_hash_or_array.compact + record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1 + end + + record = extract_record(record_or_hash_or_array) + record = record.to_model if record.respond_to?(:to_model) + + args = case record_or_hash_or_array + when Hash; [ record_or_hash_or_array ] + when Array; record_or_hash_or_array.dup + else [ record_or_hash_or_array ] + end + + inflection = if options[:action].to_s == "new" + args.pop + :singular + elsif (record.respond_to?(:persisted?) && !record.persisted?) + args.pop + :plural + elsif record.is_a?(Class) + args.pop + :plural + else + :singular + end + + args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} + named_route = build_named_route_call(record_or_hash_or_array, inflection, options) + + url_options = options.except(:action, :routing_type) + unless url_options.empty? + args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options + end + + send(named_route, *args) + end + + # Returns the path component of a URL for the given record. It uses + # polymorphic_url with :routing_type => :path. + def polymorphic_path(record_or_hash_or_array, options = {}) + polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path)) + end + + %w(edit new).each do |action| + module_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {}) + polymorphic_url( # polymorphic_url( + record_or_hash, # record_or_hash, + options.merge(:action => "#{action}")) # options.merge(:action => "edit")) + end # end + # + def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {}) + polymorphic_url( # polymorphic_url( + record_or_hash, # record_or_hash, + options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path)) + end # end + EOT + end + + private + def action_prefix(options) + options[:action] ? "#{options[:action]}_" : '' + end + + def routing_type(options) + options[:routing_type] || :url + end + + def build_named_route_call(records, inflection, options = {}) + unless records.is_a?(Array) + record = extract_record(records) + route = '' + else + record = records.pop + route = records.inject("") do |string, parent| + if parent.is_a?(Symbol) || parent.is_a?(String) + string << "#{parent}_" + else + string << ActionController::RecordIdentifier.plural_class_name(parent).singularize + string << "_" + end + end + end + + if record.is_a?(Symbol) || record.is_a?(String) + route << "#{record}_" + else + route << ActionController::RecordIdentifier.plural_class_name(record) + route = route.singularize if inflection == :singular + route << "_" + route << "index_" if ActionController::RecordIdentifier.uncountable?(record) && inflection == :plural + end + + action_prefix(options) + route + routing_type(options).to_s + end + + def extract_record(record_or_hash_or_array) + case record_or_hash_or_array + when Array; record_or_hash_or_array.last + when Hash; record_or_hash_or_array[:id] + else record_or_hash_or_array + end + end + end + end +end + diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 980abd44df..662eb05c26 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -82,6 +82,7 @@ module ActionDispatch # module UrlFor extend ActiveSupport::Concern + include PolymorphicRoutes included do # TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options diff --git a/actionpack/lib/action_view/helpers/record_identification_helper.rb b/actionpack/lib/action_view/helpers/record_identification_helper.rb index 372f1cb8aa..eea5de9bc6 100644 --- a/actionpack/lib/action_view/helpers/record_identification_helper.rb +++ b/actionpack/lib/action_view/helpers/record_identification_helper.rb @@ -20,4 +20,4 @@ module ActionView end end end -end \ No newline at end of file +end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 56aebca957..137281e5e9 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -37,7 +37,7 @@ module ActionView include ActionController::TemplateAssertions include ActionView::Context - include ActionController::PolymorphicRoutes + include ActionDispatch::Routing::PolymorphicRoutes include ActionController::RecordIdentifier include AbstractController::Helpers -- cgit v1.2.3 From f576d7cf848717384799a9e9669b253ccc94deb5 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 19 Jul 2010 15:32:00 -0400 Subject: Ensure that primary_keys of HABTM records is not double quoted [#5152 state:reslved] --- .../has_and_belongs_to_many_association.rb | 4 ++-- .../has_and_belongs_to_many_associations_test.rb | 18 ++++++++++++++++++ activerecord/test/models/country.rb | 7 +++++++ activerecord/test/models/treaty.rb | 7 +++++++ activerecord/test/schema/schema.rb | 13 +++++++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 activerecord/test/models/country.rb create mode 100644 activerecord/test/models/treaty.rb diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index c989c3536d..aba66d5a96 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -49,9 +49,9 @@ module ActiveRecord attributes = columns.inject({}) do |attrs, column| case column.name.to_s when @reflection.primary_key_name.to_s - attrs[relation[column.name]] = owner_quoted_id + attrs[relation[column.name]] = @owner.send(:id) when @reflection.association_foreign_key.to_s - attrs[relation[column.name]] = record.quoted_id + attrs[relation[column.name]] = record.send(:id) else if record.has_attribute?(column.name) value = @owner.send(:quote_value, record[column.name], column) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index b11969a841..d4d3d8e43e 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -24,6 +24,8 @@ require 'models/club' require 'models/member' require 'models/membership' require 'models/sponsor' +require 'models/country' +require 'models/treaty' require 'active_support/core_ext/string/conversions' class ProjectWithAfterCreateHook < ActiveRecord::Base @@ -83,6 +85,22 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings + def test_should_property_quote_string_primary_keys + country = Country.new(:name => 'India') + country.country_id = 'c1' + country.save! + + treaty = Treaty.new(:name => 'peace') + treaty.treaty_id = 't1' + country.treaties << treaty + + con = ActiveRecord::Base.connection + sql = 'select * from countries_treaties' + record = con.select_rows(sql).last + assert_equal 'c1', record[0] + assert_equal 't1', record[1] + end + def test_has_and_belongs_to_many david = Developer.find(1) diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb new file mode 100644 index 0000000000..15e3a1de0b --- /dev/null +++ b/activerecord/test/models/country.rb @@ -0,0 +1,7 @@ +class Country < ActiveRecord::Base + + set_primary_key :country_id + + has_and_belongs_to_many :treaties + +end diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb new file mode 100644 index 0000000000..b46537f0d2 --- /dev/null +++ b/activerecord/test/models/treaty.rb @@ -0,0 +1,7 @@ +class Treaty < ActiveRecord::Base + + set_primary_key :treaty_id + + has_and_belongs_to_many :countries + +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 641726b43f..c4eed256bf 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -600,6 +600,19 @@ ActiveRecord::Schema.define do t.string :title end + create_table :countries, :force => true, :id => false, :primary_key => 'country_id' do |t| + t.string :country_id + t.string :name + end + create_table :treaties, :force => true, :id => false, :primary_key => 'treaty_id' do |t| + t.string :treaty_id + t.string :name + end + create_table :countries_treaties, :force => true, :id => false do |t| + t.string :country_id, :null => false + t.string :treaty_id, :null => false + end + except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, :force => true do |t| -- cgit v1.2.3 From fa8b290496789eb037d4fad89acea1cb0a534f35 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 21 Jul 2010 08:13:31 +0800 Subject: id is a public method --- .../active_record/associations/has_and_belongs_to_many_association.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index aba66d5a96..9ec63e3fca 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -49,9 +49,9 @@ module ActiveRecord attributes = columns.inject({}) do |attrs, column| case column.name.to_s when @reflection.primary_key_name.to_s - attrs[relation[column.name]] = @owner.send(:id) + attrs[relation[column.name]] = @owner.id when @reflection.association_foreign_key.to_s - attrs[relation[column.name]] = record.send(:id) + attrs[relation[column.name]] = record.id else if record.has_attribute?(column.name) value = @owner.send(:quote_value, record[column.name], column) -- cgit v1.2.3 From 6807b080996ee4bd6b80abb4f5e9964632c421c8 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 21 Jul 2010 10:37:09 +0200 Subject: Moved a few methods from RecordIdentifier to ActiveModel::Naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../lib/action_controller/record_identifier.rb | 31 +---------- .../action_dispatch/routing/polymorphic_routes.rb | 6 +-- actionpack/lib/action_view/helpers/form_helper.rb | 10 ++-- .../test/controller/record_identifier_test.rb | 40 -------------- activemodel/lib/active_model/naming.rb | 29 ++++++++++ activemodel/test/cases/naming_helpers_test.rb | 63 ++++++++++++++++++++++ 6 files changed, 101 insertions(+), 78 deletions(-) create mode 100644 activemodel/test/cases/naming_helpers_test.rb diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index d20c3b64c5..975b03c0c9 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -46,7 +46,7 @@ module ActionController # dom_class(post, :edit) # => "edit_post" # dom_class(Person, :edit) # => "edit_person" def dom_class(record_or_class, prefix = nil) - singular = singular_class_name(record_or_class) + singular = ActiveModel::Naming.singular(record_or_class) prefix ? "#{prefix}#{JOIN}#{singular}" : singular end @@ -85,34 +85,5 @@ module ActionController def sanitize_dom_id(candidate_id) candidate_id # TODO implement conversion to valid DOM id values end - - # Returns the plural class name of a record or class. Examples: - # - # plural_class_name(post) # => "posts" - # plural_class_name(Highrise::Person) # => "highrise_people" - def plural_class_name(record_or_class) - model_name_from_record_or_class(record_or_class).plural - end - - # Returns the singular class name of a record or class. Examples: - # - # singular_class_name(post) # => "post" - # singular_class_name(Highrise::Person) # => "highrise_person" - def singular_class_name(record_or_class) - model_name_from_record_or_class(record_or_class).singular - end - - # Identifies whether the class name of a record or class is uncountable. Examples: - # - # uncountable?(Sheep) # => true - # uncountable?(Post) => false - def uncountable?(record_or_class) - plural_class_name(record_or_class) == singular_class_name(record_or_class) - end - - private - def model_name_from_record_or_class(record_or_class) - (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name - end end end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 18ea82c478..31dba835ac 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -155,7 +155,7 @@ module ActionDispatch if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << ActionController::RecordIdentifier.plural_class_name(parent).singularize + string << ActiveModel::Naming.plural(parent).singularize string << "_" end end @@ -164,10 +164,10 @@ module ActionDispatch if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << ActionController::RecordIdentifier.plural_class_name(record) + route << ActiveModel::Naming.plural(record) route = route.singularize if inflection == :singular route << "_" - route << "index_" if ActionController::RecordIdentifier.uncountable?(record) && inflection == :plural + route << "index_" if ActiveModel::Naming.uncountable?(record) && inflection == :plural end action_prefix(options) + route + routing_type(options).to_s diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 711c455ded..524913ae29 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -302,12 +302,12 @@ module ActionView object_name = record_or_name_or_array when Array object = record_or_name_or_array.last - object_name = options[:as] || ActionController::RecordIdentifier.singular_class_name(object) + object_name = options[:as] || ActiveModel::Naming.singular(object) apply_form_for_options!(record_or_name_or_array, options) args.unshift object else object = record_or_name_or_array - object_name = options[:as] || ActionController::RecordIdentifier.singular_class_name(object) + object_name = options[:as] || ActiveModel::Naming.singular(object) apply_form_for_options!([object], options) args.unshift object end @@ -533,7 +533,7 @@ module ActionView object = args.first else object = record_or_name_or_array - object_name = ActionController::RecordIdentifier.singular_class_name(object) + object_name = ActiveModel::Naming.singular(object) end builder = options[:builder] || ActionView::Base.default_form_builder @@ -1156,11 +1156,11 @@ module ActionView end when Array object = record_or_name_or_array.last - name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]" args.unshift(object) else object = record_or_name_or_array - name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]" args.unshift(object) end diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb index 6a84475758..835a0e970b 100644 --- a/actionpack/test/controller/record_identifier_test.rb +++ b/actionpack/test/controller/record_identifier_test.rb @@ -26,20 +26,6 @@ class Sheep end end -class Comment::Nested < Comment; end - -class Test::Unit::TestCase - protected - def comments_url - 'http://www.example.com/comments' - end - - def comment_url(comment) - "http://www.example.com/comments/#{comment.id}" - end -end - - class RecordIdentifierTest < Test::Unit::TestCase include ActionController::RecordIdentifier @@ -76,30 +62,4 @@ class RecordIdentifierTest < Test::Unit::TestCase def test_dom_class_with_prefix assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix) end - - def test_singular_class_name - assert_equal @singular, singular_class_name(@record) - end - - def test_singular_class_name_for_class - assert_equal @singular, singular_class_name(@klass) - end - - def test_plural_class_name - assert_equal @plural, plural_class_name(@record) - end - - def test_plural_class_name_for_class - assert_equal @plural, plural_class_name(@klass) - end - - def test_uncountable - assert_equal true, uncountable?(@uncountable) - assert_equal false, uncountable?(@klass) - end - - private - def method_missing(method, *args) - RecordIdentifier.send(method, *args) - end end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index ca1e9f0ee8..dc83932dde 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -57,6 +57,35 @@ module ActiveModel def model_name @_model_name ||= ActiveModel::Name.new(self) end + + # Returns the plural class name of a record or class. Examples: + # + # ActiveModel::Naming.plural(post) # => "posts" + # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people" + def self.plural(record_or_class) + model_name_from_record_or_class(record_or_class).plural + end + + # Returns the singular class name of a record or class. Examples: + # + # ActiveModel::Naming.singular(post) # => "post" + # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person" + def self.singular(record_or_class) + model_name_from_record_or_class(record_or_class).singular + end + + # Identifies whether the class name of a record or class is uncountable. Examples: + # + # ActiveModel::Naming.uncountable?(Sheep) # => true + # ActiveModel::Naming.uncountable?(Post) => false + def self.uncountable?(record_or_class) + plural(record_or_class) == singular(record_or_class) + end + + private + def self.model_name_from_record_or_class(record_or_class) + (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name + end end end diff --git a/activemodel/test/cases/naming_helpers_test.rb b/activemodel/test/cases/naming_helpers_test.rb new file mode 100644 index 0000000000..e7234e009e --- /dev/null +++ b/activemodel/test/cases/naming_helpers_test.rb @@ -0,0 +1,63 @@ +require 'cases/helper' + +class Comment + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + def to_key; id ? [id] : nil end + def save; @id = 1 end + def new_record?; @id.nil? end + def name + @id.nil? ? 'new comment' : "comment ##{@id}" + end +end + +class Sheep + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + def to_key; id ? [id] : nil end + def save; @id = 1 end + def new_record?; @id.nil? end + def name + @id.nil? ? 'new sheep' : "sheep ##{@id}" + end +end + +class NamingHelpersTest < Test::Unit::TestCase + def setup + @klass = Comment + @record = @klass.new + @singular = 'comment' + @plural = 'comments' + @uncountable = Sheep + end + + def test_singular + assert_equal @singular, singular(@record) + end + + def test_singular_for_class + assert_equal @singular, singular(@klass) + end + + def test_plural + assert_equal @plural, plural(@record) + end + + def test_plural_for_class + assert_equal @plural, plural(@klass) + end + + def test_uncountable + assert_equal true, uncountable?(@uncountable) + assert_equal false, uncountable?(@klass) + end + + private + def method_missing(method, *args) + ActiveModel::Naming.send(method, *args) + end +end -- cgit v1.2.3 From c565f0060aab85db60390220679c5df419998f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 11:46:38 +0200 Subject: No need to delegate. Simply include the whole RecordIdentifier module. --- actionpack/lib/abstract_controller.rb | 1 + .../lib/action_controller/record_identifier.rb | 2 ++ actionpack/lib/action_dispatch.rb | 2 ++ actionpack/lib/action_view.rb | 1 + actionpack/lib/action_view/helpers.rb | 2 -- .../helpers/record_identification_helper.rb | 23 ---------------------- .../lib/action_view/helpers/record_tag_helper.rb | 4 ++++ 7 files changed, 10 insertions(+), 25 deletions(-) delete mode 100644 actionpack/lib/action_view/helpers/record_identification_helper.rb diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 5990a1bbd0..c565c940a1 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,6 +1,7 @@ activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +require 'action_pack' require 'active_support/ruby/shim' require 'active_support/dependencies/autoload' require 'active_support/core_ext/class/attribute' diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 975b03c0c9..3de40b0de3 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -67,6 +67,8 @@ module ActionController end end + protected + # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. # This can be overwritten to customize the default generated string representation if desired. # If you need to read back a key from a dom_id in order to query for the underlying database record, diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 1da4a0d4be..9bb0471ffc 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -27,6 +27,8 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.inc require 'active_support' require 'active_support/dependencies/autoload' +require 'action_pack' +require 'active_model' require 'rack' module Rack diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 9f56cca869..c0d7423682 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -23,6 +23,7 @@ activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + require 'active_support/ruby/shim' require 'active_support/core_ext/class/attribute_accessors' diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index ba3bdd0d18..b7ffa345cc 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -20,7 +20,6 @@ module ActionView #:nodoc: autoload :NumberHelper autoload :PrototypeHelper autoload :RawOutputHelper - autoload :RecordIdentificationHelper autoload :RecordTagHelper autoload :SanitizeHelper autoload :ScriptaculousHelper @@ -51,7 +50,6 @@ module ActionView #:nodoc: include NumberHelper include PrototypeHelper include RawOutputHelper - include RecordIdentificationHelper include RecordTagHelper include SanitizeHelper include ScriptaculousHelper diff --git a/actionpack/lib/action_view/helpers/record_identification_helper.rb b/actionpack/lib/action_view/helpers/record_identification_helper.rb deleted file mode 100644 index eea5de9bc6..0000000000 --- a/actionpack/lib/action_view/helpers/record_identification_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActionView - # = Action View Record Identification Helpers - # - # See ActionController::RecordIdentifier for documentation on these methods. - module Helpers - module RecordIdentificationHelper - # See ActionController::RecordIdentifier.partial_path -- this is just a delegate to that for convenient access in the view. - def partial_path(*args, &block) - ActionController::RecordIdentifier.partial_path(*args, &block) - end - - # See ActionController::RecordIdentifier.dom_class -- this is just a delegate to that for convenient access in the view. - def dom_class(*args, &block) - ActionController::RecordIdentifier.dom_class(*args, &block) - end - - # See ActionController::RecordIdentifier.dom_id -- this is just a delegate to that for convenient access in the view. - def dom_id(*args, &block) - ActionController::RecordIdentifier.dom_id(*args, &block) - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 7433f08777..e4a9210cde 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -1,7 +1,11 @@ +require 'action_controller/record_identifier' + module ActionView # = Action View Record Tag Helpers module Helpers module RecordTagHelper + include ActionController::RecordIdentifier + # Produces a wrapper DIV element with id and class parameters that # relate to the specified Active Record object. Usage example: # -- cgit v1.2.3 From f1082bd51eb575f22f86b78f347ab75a4b2bff1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 11:56:28 +0200 Subject: Remove old install.rb files. --- actionmailer/README | 22 +++------------------- actionmailer/install.rb | 30 ------------------------------ actionpack/README | 17 ++++------------- actionpack/install.rb | 30 ------------------------------ activerecord/README | 21 +++------------------ activerecord/install.rb | 30 ------------------------------ activesupport/README | 15 +++------------ activesupport/install.rb | 30 ------------------------------ 8 files changed, 13 insertions(+), 182 deletions(-) delete mode 100644 actionmailer/install.rb delete mode 100644 actionpack/install.rb delete mode 100644 activerecord/install.rb delete mode 100644 activesupport/install.rb diff --git a/actionmailer/README b/actionmailer/README index 2a4d507d8a..3dd56a6fd8 100644 --- a/actionmailer/README +++ b/actionmailer/README @@ -126,36 +126,20 @@ or is accessible as a GEM. Additionally, Action Mailer requires the Mail gem, http://github.com/mikel/mail -== Bundled software - -* Text::Format 0.63 by Austin Ziegler released under OpenSource - Read more on http://www.halostatue.ca/ruby/Text__Format.html - == Download -The latest version of Action Mailer can be found at +The latest version of Action Mailer can be installed with Rubygems: -* http://rubyforge.org/project/showfiles.php?group_id=361 +* gem install actionmailer Documentation can be found at -* http://actionmailer.rubyonrails.org - - -== Installation - -You can install Action Mailer with the following command. - - % [sudo] ruby install.rb - -from its distribution directory. - +* http://api.rubyonrails.org == License Action Mailer is released under the MIT license. - == Support The Action Mailer homepage is http://www.rubyonrails.org. You can find diff --git a/actionmailer/install.rb b/actionmailer/install.rb deleted file mode 100644 index 8d7c140c3b..0000000000 --- a/actionmailer/install.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -# this was adapted from rdoc's install.rb by way of Log4r - -$sitedir = CONFIG["sitelibdir"] -unless $sitedir - version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] - $libdir = File.join(CONFIG["libdir"], "ruby", version) - $sitedir = $:.find {|x| x =~ /site_ruby/ } - if !$sitedir - $sitedir = File.join($libdir, "site_ruby") - elsif $sitedir !~ Regexp.quote(version) - $sitedir = File.join($sitedir, version) - end -end - -# the actual gruntwork -Dir.chdir("lib") - -Find.find("action_mailer", "action_mailer.rb") { |f| - if f[-3..-1] == ".rb" - File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) - else - File::makedirs(File.join($sitedir, *f.split(/\//))) - end -} diff --git a/actionpack/README b/actionpack/README index 1a59f728cc..20cc09c26f 100644 --- a/actionpack/README +++ b/actionpack/README @@ -366,22 +366,13 @@ an URL such as /weblog/5 (where 5 is the id of the post). == Download -The latest version of Action Pack can be found at +The latest version of Action Pack can be installed with Rubygems: -* http://rubyforge.org/project/showfiles.php?group_id=249 +* gem install actionpack -Documentation can be found at +Documentation can be found at -* http://api.rubyonrails.com - - -== Installation - -You can install Action Pack with the following command. - - % [sudo] ruby install.rb - -from its distribution directory. +* http://api.rubyonrails.org == License diff --git a/actionpack/install.rb b/actionpack/install.rb deleted file mode 100644 index d3b83c3b00..0000000000 --- a/actionpack/install.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -# this was adapted from rdoc's install.rb by way of Log4r - -$sitedir = CONFIG["sitelibdir"] -unless $sitedir - version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] - $libdir = File.join(CONFIG["libdir"], "ruby", version) - $sitedir = $:.find {|x| x =~ /site_ruby/ } - if !$sitedir - $sitedir = File.join($libdir, "site_ruby") - elsif $sitedir !~ Regexp.quote(version) - $sitedir = File.join($sitedir, version) - end -end - -# the actual gruntwork -Dir.chdir("lib") - -Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f| - if f[-3..-1] == ".rb" - File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) - else - File::makedirs(File.join($sitedir, *f.split(/\//))) - end -} \ No newline at end of file diff --git a/activerecord/README b/activerecord/README index d68eb28a64..0446180207 100644 --- a/activerecord/README +++ b/activerecord/README @@ -309,28 +309,13 @@ Admit the Database: == Download -The latest version of Active Record can be found at +The latest version of Active Record can be installed with Rubygems: -* http://rubyforge.org/project/showfiles.php?group_id=182 +* gem install activerecord Documentation can be found at -* http://ar.rubyonrails.com - - -== Installation - -The prefered method of installing Active Record is through its GEM file. You'll need to have -RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have, -then use: - - % [sudo] gem install activerecord-1.10.0.gem - -You can also install Active Record the old-fashioned way with the following command: - - % [sudo] ruby install.rb - -from its distribution directory. +* http://api.rubyonrails.org == License diff --git a/activerecord/install.rb b/activerecord/install.rb deleted file mode 100644 index c87398b1f4..0000000000 --- a/activerecord/install.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -# this was adapted from rdoc's install.rb by ways of Log4r - -$sitedir = CONFIG["sitelibdir"] -unless $sitedir - version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] - $libdir = File.join(CONFIG["libdir"], "ruby", version) - $sitedir = $:.find {|x| x =~ /site_ruby/ } - if !$sitedir - $sitedir = File.join($libdir, "site_ruby") - elsif $sitedir !~ Regexp.quote(version) - $sitedir = File.join($sitedir, version) - end -end - -# the actual gruntwork -Dir.chdir("lib") - -Find.find("active_record", "active_record.rb") { |f| - if f[-3..-1] == ".rb" - File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) - else - File::makedirs(File.join($sitedir, *f.split(/\//))) - end -} diff --git a/activesupport/README b/activesupport/README index 9fb9a80cbe..aa86f1fd65 100644 --- a/activesupport/README +++ b/activesupport/README @@ -7,22 +7,13 @@ Ruby sweeter. == Download -The latest version of Active Support can be found at +The latest version of Active Support can be installed with Rubygems: -* http://rubyforge.org/project/showfiles.php?group_id=182 +* gem install activesupport Documentation can be found at -* http://as.rubyonrails.com - - -== Installation - -The preferred method of installing Active Support is through its GEM file. You'll need to have -RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have it, -then use: - - % [sudo] gem install activesupport-1.0.0.gem +* http://api.rubyonrails.org == License diff --git a/activesupport/install.rb b/activesupport/install.rb deleted file mode 100644 index 9c54d8c1a9..0000000000 --- a/activesupport/install.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -# this was adapted from rdoc's install.rb by ways of Log4r - -$sitedir = CONFIG["sitelibdir"] -unless $sitedir - version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] - $libdir = File.join(CONFIG["libdir"], "ruby", version) - $sitedir = $:.find {|x| x =~ /site_ruby/ } - if !$sitedir - $sitedir = File.join($libdir, "site_ruby") - elsif $sitedir !~ Regexp.quote(version) - $sitedir = File.join($sitedir, version) - end -end - -# the actual gruntwork -Dir.chdir("lib") - -Find.find("active_support", "active_support.rb") { |f| - if f[-3..-1] == ".rb" - File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) - else - File::makedirs(File.join($sitedir, *f.split(/\//))) - end -} -- cgit v1.2.3 From cd1536887bd144dc934dde7ff47b608b490d7766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 12:30:38 +0200 Subject: Improve contribuition guide. --- .../guides/source/contributing_to_rails.textile | 57 ++++++++++++++++------ 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile index 5590895508..f0e9a4b5ec 100644 --- a/railties/guides/source/contributing_to_rails.textile +++ b/railties/guides/source/contributing_to_rails.textile @@ -62,26 +62,39 @@ git clone git://github.com/rails/rails.git cd rails -h4. Pick a Branch +h4. Set up and Run the Tests -Currently, there is active work being done on both the 2-3-stable branch of Rails and on the master branch (which will become Rails 3.0). If you want to work with the master branch, you're all set. To work with 2.3, you'll need to set up and switch to your own local tracking branch: +All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. First, you need to install all Rails dependencies with bundler: -git branch --track 2-3-stable origin/2-3-stable -git checkout 2-3-stable +gem install bundler +bundle install --without db -TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with. +The second command will install all dependencies, except MySQL and PostgreSQL. We will come back at these soon. With dependencies installed, you can run the whole Rails test suite with: -h4. Set up and Run the Tests + +rake test + -All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. Rails needs the +mocha+ gem for running some tests, so install it with: +You can also run tests for an specific framework, like Action Pack, by going into its directory and executing the same command: -gem install mocha +cd actionpack +rake test -For the tests that touch the database, this means creating test databases. If you're using MySQL, create a user named +rails+ with privileges on the test databases. +h4. Testing Active Record + +By default, when you run Active Record tests, it will execute the test suite three times, one for each of the main databases: SQLite3, MySQL and PostgreSQL. If you are adding a feature that is not specific to the database, you can run the test suite (or just one file) for just one of them. Here is an example for SQLite3: + + +cd activerecord +rake test_sqlite3 +rake test_sqlite3 TEST=test/cases/validations_test.rb + + +If you want to use another database, as MySQL, you need to create a user named +rails+ with privileges on the test databases. mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* @@ -90,7 +103,13 @@ mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; -Enter this from the +activerecord+ directory to create the test databases: +Then ensure you run bundle install without the +--without db+ option: + + +bundle install + + +Finally, enter this from the +activerecord+ directory to create the test databases: rake mysql:build_databases @@ -100,18 +119,26 @@ NOTE: Using the rake task to create the test databases ensures they have the cor If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails. -Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory: +You can now run tests as you did for +sqlite3+: -rake test_sqlite3 -rake test_sqlite3 TEST=test/cases/validations_test.rb +rake test_mysql -You can replace +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs. +You can also +myqsl+ with +postgresql+, +jdbcmysql+, +jdbcsqlite3+ or +jdbcpostgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs. +NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. +h4. Older versions of Rails -NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. +If you want to work add a fix to older versions of Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to Rails 2.3 branch: + + +git branch --track 2-3-stable origin/2-3-stable +git checkout 2-3-stable + + +TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with. h3. Helping to Resolve Existing Issues -- cgit v1.2.3 From b70062f1e71dc8bda8e9b8159a1f202389a80a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 12:37:05 +0200 Subject: Rework a bit README files. --- README | 58 +++++++++++ actionpack/README | 9 -- railties/README | 280 +++--------------------------------------------------- railties/Rakefile | 7 -- 4 files changed, 70 insertions(+), 284 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000000..6198000279 --- /dev/null +++ b/README @@ -0,0 +1,58 @@ +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. Install Rails at the command prompt if you haven't yet: + gem install rails + +2. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) + +3. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) + +4. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" + +5. Follow the guidelines to start developing your application. You can find +the following resources handy: + +* The README file created within your application +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Contributing + +Check out the contributing guide at http://edgeguides.rubyonrails.org/contributing_to_rails.html + + +== License + +Ruby on Rails is released under the MIT license. diff --git a/actionpack/README b/actionpack/README index 20cc09c26f..272feb63d0 100644 --- a/actionpack/README +++ b/actionpack/README @@ -19,15 +19,6 @@ the HTML. To avoid cluttering the templates with code, a bunch of helper classes provide common behavior for forms, dates, and strings. And it's easy to add specific helpers to keep the separation as the application evolves. -Note: Some of the features, such as scaffolding and form building, are tied to -ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational -mapping package), but that doesn't mean that Action Pack depends on Active -Record. Action Pack is an independent package that can be used with any sort -of backend (Instiki[http://www.instiki.org], which is based on an older version -of Action Pack, used Madeleine for example). Read more about the role Action -Pack can play when used together with Active Record on -http://www.rubyonrails.org. - A short rundown of the major features: * Actions grouped in controller as methods instead of separate command objects diff --git a/railties/README b/railties/README index d8be15e346..a1718a7d96 100644 --- a/railties/README +++ b/railties/README @@ -1,281 +1,25 @@ -== Welcome to Rails += Railties -- Gluing the Engine to the Rails -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. +Railties is responsible to glue all frameworks together. Overall, it: -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. +* handles all the bootstrapping process for a Rails application; -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. +* manager rails command line interface; -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. +* provides Rails generators core; -== Getting Started +== Download -1. At the command prompt, create a new Rails application: - rails new myapp (where myapp is the application name) +The latest version of Railties can be installed with Rubygems: -2. Change directory to myapp and start the web server: - cd myapp; rails server (run with --help for options) +* gem install railties -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" +Documentation can be found at -4. Follow the guidelines to start developing your application. You can find -the following resources handy: +* http://api.rubyonrails.org -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ +== License -== Web Servers - -By default, Rails will try to use Mongrel if it's installed when started with -rails server, otherwise Rails will use WEBrick, the web server that -ships with Ruby. - -Mongrel is a Ruby-based web server with a C component (which requires -compilation) that is suitable for development. If you have Ruby Gems installed, -getting up and running with mongrel is as easy as: - sudo gem install mongrel. - -You can find more info at: http://mongrel.rubyforge.org - -You can alternatively run Rails applications with other Ruby web servers, e.g., -{Thin}[http://code.macournoyer.com/thin/], {Ebb}[http://ebb.rubyforge.org/], and -Apache with {mod_rails}[http://www.modrails.com/]. However, rails server -doesn't search for or start them. - -For production use, often a web/proxy server, e.g., {Apache}[http://apache.org], -{Nginx}[http://nginx.net/], {LiteSpeed}[http://litespeedtech.com/], -{Lighttpd}[http://www.lighttpd.net/], or {IIS}[http://www.iis.net/], is deployed -as the front end server with the chosen Ruby web server running in the back end -and receiving the proxied requests via one of several protocols (HTTP, CGI, FCGI). - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install ruby-debug to run the server in debugging -mode. With gems, use sudo gem install ruby-debug. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.find(:all) - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#nil, "body"=>nil, "id"=>"1"}>, - #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run rails console from the application -directory. - -Options: - -* Passing the -s, --sandbox argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: rails console production. - -To reload your controllers and models after launching the console run -reload! - -More information about irb can be found at: -link:http://www.rubycentral.com/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through rails -dbconsole. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like rails dbconsole production. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- controllers - | |-- helpers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | `-- tasks - |-- log - |-- public - | |-- images - | |-- javascripts - | `-- stylesheets - |-- script - | `-- performance - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | |-- cache - | |-- pids - | |-- sessions - | `-- sockets - `-- vendor - `-- plugins - -app - Holds all the code that's specific to this particular application. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the layout :default and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Contains subdirectories for - images, stylesheets, and javascripts. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. Also includes the plugins - subdirectory. If the app has frozen rails, those gems also go here, under - vendor/rails/. This directory is in the load path. +Railties is released under the MIT license. diff --git a/railties/Rakefile b/railties/Rakefile index ddc872e18b..ae9db3c022 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -35,13 +35,6 @@ end # Update spinoffs ------------------------------------------------------------------- -desc "Updates application README to the latest version Railties README" -task :update_readme do - readme = "lib/rails/generators/rails/app/templates/README" - rm readme - cp "./README", readme -end - desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"' task :generate_guides do ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this -- cgit v1.2.3 From 508fba9e070e09f0a321f2dd7acf7938967468f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 12:51:14 +0200 Subject: Add .rdoc extension to README files. --- README | 58 ------- README.rdoc | 58 +++++++ Rakefile | 16 +- actionmailer/README | 151 ------------------ actionmailer/README.rdoc | 151 ++++++++++++++++++ actionmailer/Rakefile | 2 +- actionpack/README | 382 --------------------------------------------- actionpack/README.rdoc | 382 +++++++++++++++++++++++++++++++++++++++++++++ actionpack/Rakefile | 2 +- activemodel/README | 206 ------------------------ activemodel/README.rdoc | 206 ++++++++++++++++++++++++ activemodel/Rakefile | 2 +- activerecord/README | 336 --------------------------------------- activerecord/README.rdoc | 336 +++++++++++++++++++++++++++++++++++++++ activerecord/Rakefile | 2 +- activeresource/README | 165 -------------------- activeresource/README.rdoc | 165 ++++++++++++++++++++ activeresource/Rakefile | 2 +- activesupport/README | 34 ---- activesupport/README.rdoc | 34 ++++ activesupport/Rakefile | 2 +- railties/README | 25 --- railties/README.rdoc | 25 +++ railties/Rakefile | 2 +- 24 files changed, 1372 insertions(+), 1372 deletions(-) delete mode 100644 README create mode 100644 README.rdoc delete mode 100644 actionmailer/README create mode 100644 actionmailer/README.rdoc delete mode 100644 actionpack/README create mode 100644 actionpack/README.rdoc delete mode 100644 activemodel/README create mode 100644 activemodel/README.rdoc delete mode 100644 activerecord/README create mode 100644 activerecord/README.rdoc delete mode 100644 activeresource/README create mode 100644 activeresource/README.rdoc delete mode 100644 activesupport/README create mode 100644 activesupport/README.rdoc delete mode 100644 railties/README create mode 100644 railties/README.rdoc diff --git a/README b/README deleted file mode 100644 index 6198000279..0000000000 --- a/README +++ /dev/null @@ -1,58 +0,0 @@ -== Welcome to Rails - -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. - -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting Started - -1. Install Rails at the command prompt if you haven't yet: - gem install rails - -2. At the command prompt, create a new Rails application: - rails new myapp (where myapp is the application name) - -3. Change directory to myapp and start the web server: - cd myapp; rails server (run with --help for options) - -4. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" - -5. Follow the guidelines to start developing your application. You can find -the following resources handy: - -* The README file created within your application -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ - - -== Contributing - -Check out the contributing guide at http://edgeguides.rubyonrails.org/contributing_to_rails.html - - -== License - -Ruby on Rails is released under the MIT license. diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000000..6198000279 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,58 @@ +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. Install Rails at the command prompt if you haven't yet: + gem install rails + +2. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) + +3. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) + +4. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" + +5. Follow the guidelines to start developing your application. You can find +the following resources handy: + +* The README file created within your application +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Contributing + +Check out the contributing guide at http://edgeguides.rubyonrails.org/contributing_to_rails.html + + +== License + +Ruby on Rails is released under the MIT license. diff --git a/Rakefile b/Rakefile index e608af0319..c49942c0bb 100644 --- a/Rakefile +++ b/Rakefile @@ -69,7 +69,7 @@ Rake::RDocTask.new do |rdoc| rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' - rdoc.options << '--main' << 'railties/README' + rdoc.options << '--main' << 'README.rdoc' # Workaround: RDoc assumes that rdoc.template can be required, and that # rdoc.template.upcase is a constant living in RDoc::Generator::HTML @@ -83,38 +83,38 @@ Rake::RDocTask.new do |rdoc| rdoc.rdoc_files.include('railties/CHANGELOG') rdoc.rdoc_files.include('railties/MIT-LICENSE') - rdoc.rdoc_files.include('railties/README') + rdoc.rdoc_files.include('railties/README.rdoc') rdoc.rdoc_files.include('railties/lib/**/*.rb') rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/*') - rdoc.rdoc_files.include('activerecord/README') + rdoc.rdoc_files.include('activerecord/README.rdoc') rdoc.rdoc_files.include('activerecord/CHANGELOG') rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb') rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*') - rdoc.rdoc_files.include('activeresource/README') + rdoc.rdoc_files.include('activeresource/README.rdoc') rdoc.rdoc_files.include('activeresource/CHANGELOG') rdoc.rdoc_files.include('activeresource/lib/active_resource.rb') rdoc.rdoc_files.include('activeresource/lib/active_resource/*') - rdoc.rdoc_files.include('actionpack/README') + rdoc.rdoc_files.include('actionpack/README.rdoc') rdoc.rdoc_files.include('actionpack/CHANGELOG') rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb') rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*') - rdoc.rdoc_files.include('actionmailer/README') + rdoc.rdoc_files.include('actionmailer/README.rdoc') rdoc.rdoc_files.include('actionmailer/CHANGELOG') rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb') rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*') - rdoc.rdoc_files.include('activesupport/README') + rdoc.rdoc_files.include('activesupport/README.rdoc') rdoc.rdoc_files.include('activesupport/CHANGELOG') rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb') rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*') - rdoc.rdoc_files.include('activemodel/README') + rdoc.rdoc_files.include('activemodel/README.rdoc') rdoc.rdoc_files.include('activemodel/CHANGELOG') rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb') end diff --git a/actionmailer/README b/actionmailer/README deleted file mode 100644 index 3dd56a6fd8..0000000000 --- a/actionmailer/README +++ /dev/null @@ -1,151 +0,0 @@ -= Action Mailer -- Easy email delivery and testing - -Action Mailer is a framework for designing email-service layers. These layers -are used to consolidate code for sending out forgotten passwords, welcome -wishes on signup, invoices for billing, and any other use case that requires -a written notification to either a person or another system. - -Action Mailer is in essence a wrapper around Action Controller and the -Mail gem. It provides a way to make emails using templates in the same -way that Action Controller renders views using templates. - -Additionally, an Action Mailer class can be used to process incoming email, -such as allowing a weblog to accept new posts from an email (which could even -have been sent from a phone). - -== Sending emails - -The framework works by initializing any instance variables you want to be -available in the email template, followed by a call to +mail+ to deliver -the email. - -This can be as simple as: - - class Notifier < ActionMailer::Base - delivers_from 'system@loudthinking.com' - - def welcome(recipient) - @recipient = recipient - mail(:to => recipient, - :subject => "[Signed up] Welcome #{recipient}") - end - end - -The body of the email is created by using an Action View template (regular -ERb) that has the instance variables that are declared in the mailer action. - -So the corresponding body template for the method above could look like this: - - Hello there, - - Mr. <%= @recipient %> - - Thank you for signing up! - -And if the recipient was given as "david@loudthinking.com", the email -generated would look like this: - - Date: Mon, 25 Jan 2010 22:48:09 +1100 - From: system@loudthinking.com - To: david@loudthinking.com - Message-ID: <4b5d84f9dd6a5_7380800b81ac29578@void.loudthinking.com.mail> - Subject: [Signed up] Welcome david@loudthinking.com - Mime-Version: 1.0 - Content-Type: text/plain; - charset="US-ASCII"; - Content-Transfer-Encoding: 7bit - - Hello there, - - Mr. david@loudthinking.com - -In previous version of rails you would call create_method_name and -deliver_method_name. Rails 3.0 has a much simpler interface, you -simply call the method and optionally call +deliver+ on the return value. - -Calling the method returns a Mail Message object: - - message = Notifier.welcome #=> Returns a Mail::Message object - message.deliver #=> delivers the email - -Or you can just chain the methods together like: - - Notifier.welcome.deliver # Creates the email and sends it immediately - -== Receiving emails - -To receive emails, you need to implement a public instance method called receive that takes a -tmail object as its single parameter. The Action Mailer framework has a corresponding class method, -which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns -into the tmail object and calls the receive instance method. - -Example: - - class Mailman < ActionMailer::Base - def receive(email) - page = Page.find_by_address(email.to.first) - page.emails.create( - :subject => email.subject, :body => email.body - ) - - if email.has_attachments? - for attachment in email.attachments - page.attachments.create({ - :file => attachment, :description => email.subject - }) - end - end - end - end - -This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the -trivial case like this: - - rails runner 'Mailman.receive(STDIN.read)' - -However, invoking Rails in the runner for each mail to be received is very resource intensive. A single -instance of Rails should be run within a daemon if it is going to be utilized to process more than just -a limited number of email. - -== Configuration - -The Base class has the full list of configuration options. Here's an example: - - ActionMailer::Base.smtp_settings = { - :address => 'smtp.yourserver.com', # default: localhost - :port => '25', # default: 25 - :user_name => 'user', - :password => 'pass', - :authentication => :plain # :plain, :login or :cram_md5 - } - -== Dependencies - -Action Mailer requires that the Action Pack is either available to be required immediately -or is accessible as a GEM. - -Additionally, Action Mailer requires the Mail gem, http://github.com/mikel/mail - -== Download - -The latest version of Action Mailer can be installed with Rubygems: - -* gem install actionmailer - -Documentation can be found at - -* http://api.rubyonrails.org - -== License - -Action Mailer is released under the MIT license. - -== Support - -The Action Mailer homepage is http://www.rubyonrails.org. You can find -the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer. -And as Jim from Rake says: - - Feel free to submit commits or feature requests. If you send a patch, - remember to update the corresponding unit tests. If fact, I prefer - new feature to be submitted in the form of new unit tests. diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc new file mode 100644 index 0000000000..3dd56a6fd8 --- /dev/null +++ b/actionmailer/README.rdoc @@ -0,0 +1,151 @@ += Action Mailer -- Easy email delivery and testing + +Action Mailer is a framework for designing email-service layers. These layers +are used to consolidate code for sending out forgotten passwords, welcome +wishes on signup, invoices for billing, and any other use case that requires +a written notification to either a person or another system. + +Action Mailer is in essence a wrapper around Action Controller and the +Mail gem. It provides a way to make emails using templates in the same +way that Action Controller renders views using templates. + +Additionally, an Action Mailer class can be used to process incoming email, +such as allowing a weblog to accept new posts from an email (which could even +have been sent from a phone). + +== Sending emails + +The framework works by initializing any instance variables you want to be +available in the email template, followed by a call to +mail+ to deliver +the email. + +This can be as simple as: + + class Notifier < ActionMailer::Base + delivers_from 'system@loudthinking.com' + + def welcome(recipient) + @recipient = recipient + mail(:to => recipient, + :subject => "[Signed up] Welcome #{recipient}") + end + end + +The body of the email is created by using an Action View template (regular +ERb) that has the instance variables that are declared in the mailer action. + +So the corresponding body template for the method above could look like this: + + Hello there, + + Mr. <%= @recipient %> + + Thank you for signing up! + +And if the recipient was given as "david@loudthinking.com", the email +generated would look like this: + + Date: Mon, 25 Jan 2010 22:48:09 +1100 + From: system@loudthinking.com + To: david@loudthinking.com + Message-ID: <4b5d84f9dd6a5_7380800b81ac29578@void.loudthinking.com.mail> + Subject: [Signed up] Welcome david@loudthinking.com + Mime-Version: 1.0 + Content-Type: text/plain; + charset="US-ASCII"; + Content-Transfer-Encoding: 7bit + + Hello there, + + Mr. david@loudthinking.com + +In previous version of rails you would call create_method_name and +deliver_method_name. Rails 3.0 has a much simpler interface, you +simply call the method and optionally call +deliver+ on the return value. + +Calling the method returns a Mail Message object: + + message = Notifier.welcome #=> Returns a Mail::Message object + message.deliver #=> delivers the email + +Or you can just chain the methods together like: + + Notifier.welcome.deliver # Creates the email and sends it immediately + +== Receiving emails + +To receive emails, you need to implement a public instance method called receive that takes a +tmail object as its single parameter. The Action Mailer framework has a corresponding class method, +which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns +into the tmail object and calls the receive instance method. + +Example: + + class Mailman < ActionMailer::Base + def receive(email) + page = Page.find_by_address(email.to.first) + page.emails.create( + :subject => email.subject, :body => email.body + ) + + if email.has_attachments? + for attachment in email.attachments + page.attachments.create({ + :file => attachment, :description => email.subject + }) + end + end + end + end + +This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the +trivial case like this: + + rails runner 'Mailman.receive(STDIN.read)' + +However, invoking Rails in the runner for each mail to be received is very resource intensive. A single +instance of Rails should be run within a daemon if it is going to be utilized to process more than just +a limited number of email. + +== Configuration + +The Base class has the full list of configuration options. Here's an example: + + ActionMailer::Base.smtp_settings = { + :address => 'smtp.yourserver.com', # default: localhost + :port => '25', # default: 25 + :user_name => 'user', + :password => 'pass', + :authentication => :plain # :plain, :login or :cram_md5 + } + +== Dependencies + +Action Mailer requires that the Action Pack is either available to be required immediately +or is accessible as a GEM. + +Additionally, Action Mailer requires the Mail gem, http://github.com/mikel/mail + +== Download + +The latest version of Action Mailer can be installed with Rubygems: + +* gem install actionmailer + +Documentation can be found at + +* http://api.rubyonrails.org + +== License + +Action Mailer is released under the MIT license. + +== Support + +The Action Mailer homepage is http://www.rubyonrails.org. You can find +the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer. +And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index f20e7ea928..98aeae9818 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -32,7 +32,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG') rdoc.rdoc_files.include('lib/action_mailer.rb') rdoc.rdoc_files.include('lib/action_mailer/*.rb') rdoc.rdoc_files.include('lib/action_mailer/delivery_method/*.rb') diff --git a/actionpack/README b/actionpack/README deleted file mode 100644 index 272feb63d0..0000000000 --- a/actionpack/README +++ /dev/null @@ -1,382 +0,0 @@ -= Action Pack -- On rails from request to response - -Action Pack splits the response to a web request into a controller part -(performing the logic) and a view part (rendering a template). This two-step -approach is known as an action, which will normally create, read, update, or -delete (CRUD for short) some sort of model part (often backed by a database) -before choosing either to render a template or redirecting to another action. - -Action Pack implements these actions as public methods on Action Controllers -and uses Action Views to implement the template rendering. Action Controllers -are then responsible for handling all the actions relating to a certain part -of an application. This grouping usually consists of actions for lists and for -CRUDs revolving around a single (or a few) model objects. So ContactsController -would be responsible for listing contacts, creating, deleting, and updating -contacts. A WeblogController could be responsible for both posts and comments. - -Action View templates are written using embedded Ruby in tags mingled in with -the HTML. To avoid cluttering the templates with code, a bunch of helper -classes provide common behavior for forms, dates, and strings. And it's easy -to add specific helpers to keep the separation as the application evolves. - -A short rundown of the major features: - -* Actions grouped in controller as methods instead of separate command objects - and can therefore share helper methods - - CustomersController < ActionController::Base - def show - @customer = find_customer - end - - def update - @customer = find_customer - @customer.attributes = params[:customer] - @customer.save ? - redirect_to(:action => "show") : - render(:action => "edit") - end - - private - def find_customer() Customer.find(params[:id]) end - end - - {Learn more}[link:classes/ActionController/Base.html] - - -* Embedded Ruby for templates (no new "easy" template language) - - <% for post in @posts %> - Title: <%= post.title %> - <% end %> - - All post titles: <%= @posts.collect{ |p| p.title }.join ", " %> - - <% unless @person.is_client? %> - Not for clients to see... - <% end %> - - {Learn more}[link:classes/ActionView.html] - - -* Builder-based templates (great for XML content, like RSS) - - xml.rss("version" => "2.0") do - xml.channel do - xml.title(@feed_title) - xml.link(@url) - xml.description "Basecamp: Recent items" - xml.language "en-us" - xml.ttl "40" - - for item in @recent_items - xml.item do - xml.title(item_title(item)) - xml.description(item_description(item)) - xml.pubDate(item_pubDate(item)) - xml.guid(@recent_items.url(item)) - xml.link(@recent_items.url(item)) - end - end - end - end - - {Learn more}[link:classes/ActionView/Base.html] - - -* Filters for pre and post processing of the response (as methods, procs, and classes) - - class WeblogController < ActionController::Base - before_filter :authenticate, :cache, :audit - after_filter { |c| c.response.body = Gzip::compress(c.response.body) } - after_filter LocalizeFilter - - def index - # Before this action is run, the user will be authenticated, the cache - # will be examined to see if a valid copy of the results already - # exists, and the action will be logged for auditing. - - # After this action has run, the output will first be localized then - # compressed to minimize bandwidth usage - end - - private - def authenticate - # Implement the filter with full access to both request and response - end - end - - {Learn more}[link:classes/ActionController/Filters/ClassMethods.html] - - -* Helpers for forms, dates, action links, and text - - <%= text_field "post", "title", "size" => 30 %> - <%= html_date_select(Date.today) %> - <%= link_to "New post", :controller => "post", :action => "new" %> - <%= truncate(post.title, :length => 25) %> - - {Learn more}[link:classes/ActionView/Helpers.html] - - -* Layout sharing for template reuse (think simple version of Struts - Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html]) - - class WeblogController < ActionController::Base - layout "weblog_layout" - - def hello_world - end - end - - Layout file (called weblog_layout): - <%= yield %> - - Template for hello_world action: -

Hello world

- - Result of running hello_world action: -

Hello world

- - {Learn more}[link:classes/ActionController/Layout/ClassMethods.html] - - -* Routing makes pretty urls incredibly easy - - map.connect 'clients/:client_name/:project_name/:controller/:action' - - Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with - { "client_name" => "37signals", "project_name" => "basecamp" } in params[:params] - - From that URL, you can rewrite the redirect in a number of ways: - - redirect_to(:action => "edit") => - /clients/37signals/basecamp/project/dash - - redirect_to(:client_name => "nextangle", :project_name => "rails") => - /clients/nextangle/rails/project/dash - - {Learn more}[link:classes/ActionController/Base.html] - - -* Easy testing of both controller and rendered template through ActionController::TestCase - - class LoginControllerTest < ActionController::TestCase - def test_failing_authenticate - process :authenticate, :user_name => "nop", :password => "" - assert flash.has_key?(:alert) - assert_redirected_to :action => "index" - end - end - - {Learn more}[link:classes/ActionController/TestCase.html] - - -* Automated benchmarking and integrated logging - - Started GET "/weblog" for 127.0.0.1 at Fri May 28 00:41:55 - Processing by WeblogController#index as HTML - Rendered weblog/index.html.erb within layouts/application (25.7ms) - Completed 200 OK in 29.3ms - - If Active Record is used as the model, you'll have the database debugging - as well: - - Started POST "/posts" for 127.0.0.1 at Sat Jun 19 14:04:23 - Processing by PostsController#create as HTML - Parameters: {"post"=>{"title"=>"this is good"}} - SQL (0.6ms) INSERT INTO posts (title) VALUES('this is good') - Redirected to http://example.com/posts/5 - Completed 302 Found in 221ms (Views: 215ms | ActiveRecord: 0.6ms) - - You specify a logger through a class method, such as: - - ActionController::Base.logger = Logger.new("Application Log") - ActionController::Base.logger = Log4r::Logger.new("Application Log") - - -* Caching at three levels of granularity (page, action, fragment) - - class WeblogController < ActionController::Base - caches_page :show - caches_action :account - - def show - # the output of the method will be cached as - # ActionController::Base.page_cache_directory + "/weblog/show/n.html" - # and the web server will pick it up without even hitting Rails - end - - def account - # the output of the method will be cached in the fragment store - # but Rails is hit to retrieve it, so filters are run - end - - def update - List.update(params[:list][:id], params[:list]) - expire_page :action => "show", :id => params[:list][:id] - expire_action :action => "account" - redirect_to :action => "show", :id => params[:list][:id] - end - end - - {Learn more}[link:classes/ActionController/Caching.html] - - -* Powerful debugging mechanism for local requests - - All exceptions raised on actions performed on the request of a local user - will be presented with a tailored debugging screen that includes exception - message, stack trace, request parameters, session contents, and the - half-finished response. - - {Learn more}[link:classes/ActionController/Rescue.html] - - -* Scaffolding for Active Record model objects - - class AccountController < ActionController::Base - scaffold :account - end - - The AccountController now has the full CRUD range of actions and default - templates: list, show, destroy, new, create, edit, update - - {Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html] - - -* Form building for Active Record model objects - - The post object has a title (varchar), content (text), and - written_on (date) - - <%= form "post" %> - - ...will generate something like (the selects will have more options, of - course): - -
-

- Title:
- -

-

- Content:
- -

-

- Written on:
- - - -

- - -
- - This form generates a params[:post] array that can be used directly in a save action: - - class WeblogController < ActionController::Base - def create - post = Post.create(params[:post]) - redirect_to :action => "show", :id => post.id - end - end - - {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html] - - -* Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby - - -== Simple example (from outside of Rails) - -This example will implement a simple weblog system using inline templates and -an Active Record model. So let's build that WeblogController with just a few -methods: - - require 'action_controller' - require 'post' - - class WeblogController < ActionController::Base - layout "weblog/layout" - - def index - @posts = Post.find(:all) - end - - def show - @post = Post.find(params[:id]) - end - - def new - @post = Post.new - end - - def create - @post = Post.create(params[:post]) - redirect_to :action => "show", :id => @post.id - end - end - - WeblogController::Base.view_paths = [ File.dirname(__FILE__) ] - WeblogController.process_cgi if $0 == __FILE__ - -The last two lines are responsible for telling ActionController where the -template files are located and actually running the controller on a new -request from the web-server (like to be Apache). - -And the templates look like this: - - weblog/layout.html.erb: - - <%= yield %> - - - weblog/index.html.erb: - <% for post in @posts %> -

<%= link_to(post.title, :action => "show", :id => post.id) %>

- <% end %> - - weblog/show.html.erb: -

- <%= @post.title %>
- <%= @post.content %> -

- - weblog/new.html.erb: - <%= form "post" %> - -This simple setup will list all the posts in the system on the index page, -which is called by accessing /weblog/. It uses the form builder for the Active -Record model to make the new screen, which in turn hands everything over to -the create action (that's the default target for the form builder when given a -new model). After creating the post, it'll redirect to the show page using -an URL such as /weblog/5 (where 5 is the id of the post). - - -== Download - -The latest version of Action Pack can be installed with Rubygems: - -* gem install actionpack - -Documentation can be found at - -* http://api.rubyonrails.org - - -== License - -Action Pack is released under the MIT license. - - -== Support - -The Action Pack homepage is http://www.rubyonrails.org. You can find -the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack. -And as Jim from Rake says: - - Feel free to submit commits or feature requests. If you send a patch, - remember to update the corresponding unit tests. If fact, I prefer - new feature to be submitted in the form of new unit tests. diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc new file mode 100644 index 0000000000..272feb63d0 --- /dev/null +++ b/actionpack/README.rdoc @@ -0,0 +1,382 @@ += Action Pack -- On rails from request to response + +Action Pack splits the response to a web request into a controller part +(performing the logic) and a view part (rendering a template). This two-step +approach is known as an action, which will normally create, read, update, or +delete (CRUD for short) some sort of model part (often backed by a database) +before choosing either to render a template or redirecting to another action. + +Action Pack implements these actions as public methods on Action Controllers +and uses Action Views to implement the template rendering. Action Controllers +are then responsible for handling all the actions relating to a certain part +of an application. This grouping usually consists of actions for lists and for +CRUDs revolving around a single (or a few) model objects. So ContactsController +would be responsible for listing contacts, creating, deleting, and updating +contacts. A WeblogController could be responsible for both posts and comments. + +Action View templates are written using embedded Ruby in tags mingled in with +the HTML. To avoid cluttering the templates with code, a bunch of helper +classes provide common behavior for forms, dates, and strings. And it's easy +to add specific helpers to keep the separation as the application evolves. + +A short rundown of the major features: + +* Actions grouped in controller as methods instead of separate command objects + and can therefore share helper methods + + CustomersController < ActionController::Base + def show + @customer = find_customer + end + + def update + @customer = find_customer + @customer.attributes = params[:customer] + @customer.save ? + redirect_to(:action => "show") : + render(:action => "edit") + end + + private + def find_customer() Customer.find(params[:id]) end + end + + {Learn more}[link:classes/ActionController/Base.html] + + +* Embedded Ruby for templates (no new "easy" template language) + + <% for post in @posts %> + Title: <%= post.title %> + <% end %> + + All post titles: <%= @posts.collect{ |p| p.title }.join ", " %> + + <% unless @person.is_client? %> + Not for clients to see... + <% end %> + + {Learn more}[link:classes/ActionView.html] + + +* Builder-based templates (great for XML content, like RSS) + + xml.rss("version" => "2.0") do + xml.channel do + xml.title(@feed_title) + xml.link(@url) + xml.description "Basecamp: Recent items" + xml.language "en-us" + xml.ttl "40" + + for item in @recent_items + xml.item do + xml.title(item_title(item)) + xml.description(item_description(item)) + xml.pubDate(item_pubDate(item)) + xml.guid(@recent_items.url(item)) + xml.link(@recent_items.url(item)) + end + end + end + end + + {Learn more}[link:classes/ActionView/Base.html] + + +* Filters for pre and post processing of the response (as methods, procs, and classes) + + class WeblogController < ActionController::Base + before_filter :authenticate, :cache, :audit + after_filter { |c| c.response.body = Gzip::compress(c.response.body) } + after_filter LocalizeFilter + + def index + # Before this action is run, the user will be authenticated, the cache + # will be examined to see if a valid copy of the results already + # exists, and the action will be logged for auditing. + + # After this action has run, the output will first be localized then + # compressed to minimize bandwidth usage + end + + private + def authenticate + # Implement the filter with full access to both request and response + end + end + + {Learn more}[link:classes/ActionController/Filters/ClassMethods.html] + + +* Helpers for forms, dates, action links, and text + + <%= text_field "post", "title", "size" => 30 %> + <%= html_date_select(Date.today) %> + <%= link_to "New post", :controller => "post", :action => "new" %> + <%= truncate(post.title, :length => 25) %> + + {Learn more}[link:classes/ActionView/Helpers.html] + + +* Layout sharing for template reuse (think simple version of Struts + Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html]) + + class WeblogController < ActionController::Base + layout "weblog_layout" + + def hello_world + end + end + + Layout file (called weblog_layout): + <%= yield %> + + Template for hello_world action: +

Hello world

+ + Result of running hello_world action: +

Hello world

+ + {Learn more}[link:classes/ActionController/Layout/ClassMethods.html] + + +* Routing makes pretty urls incredibly easy + + map.connect 'clients/:client_name/:project_name/:controller/:action' + + Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with + { "client_name" => "37signals", "project_name" => "basecamp" } in params[:params] + + From that URL, you can rewrite the redirect in a number of ways: + + redirect_to(:action => "edit") => + /clients/37signals/basecamp/project/dash + + redirect_to(:client_name => "nextangle", :project_name => "rails") => + /clients/nextangle/rails/project/dash + + {Learn more}[link:classes/ActionController/Base.html] + + +* Easy testing of both controller and rendered template through ActionController::TestCase + + class LoginControllerTest < ActionController::TestCase + def test_failing_authenticate + process :authenticate, :user_name => "nop", :password => "" + assert flash.has_key?(:alert) + assert_redirected_to :action => "index" + end + end + + {Learn more}[link:classes/ActionController/TestCase.html] + + +* Automated benchmarking and integrated logging + + Started GET "/weblog" for 127.0.0.1 at Fri May 28 00:41:55 + Processing by WeblogController#index as HTML + Rendered weblog/index.html.erb within layouts/application (25.7ms) + Completed 200 OK in 29.3ms + + If Active Record is used as the model, you'll have the database debugging + as well: + + Started POST "/posts" for 127.0.0.1 at Sat Jun 19 14:04:23 + Processing by PostsController#create as HTML + Parameters: {"post"=>{"title"=>"this is good"}} + SQL (0.6ms) INSERT INTO posts (title) VALUES('this is good') + Redirected to http://example.com/posts/5 + Completed 302 Found in 221ms (Views: 215ms | ActiveRecord: 0.6ms) + + You specify a logger through a class method, such as: + + ActionController::Base.logger = Logger.new("Application Log") + ActionController::Base.logger = Log4r::Logger.new("Application Log") + + +* Caching at three levels of granularity (page, action, fragment) + + class WeblogController < ActionController::Base + caches_page :show + caches_action :account + + def show + # the output of the method will be cached as + # ActionController::Base.page_cache_directory + "/weblog/show/n.html" + # and the web server will pick it up without even hitting Rails + end + + def account + # the output of the method will be cached in the fragment store + # but Rails is hit to retrieve it, so filters are run + end + + def update + List.update(params[:list][:id], params[:list]) + expire_page :action => "show", :id => params[:list][:id] + expire_action :action => "account" + redirect_to :action => "show", :id => params[:list][:id] + end + end + + {Learn more}[link:classes/ActionController/Caching.html] + + +* Powerful debugging mechanism for local requests + + All exceptions raised on actions performed on the request of a local user + will be presented with a tailored debugging screen that includes exception + message, stack trace, request parameters, session contents, and the + half-finished response. + + {Learn more}[link:classes/ActionController/Rescue.html] + + +* Scaffolding for Active Record model objects + + class AccountController < ActionController::Base + scaffold :account + end + + The AccountController now has the full CRUD range of actions and default + templates: list, show, destroy, new, create, edit, update + + {Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html] + + +* Form building for Active Record model objects + + The post object has a title (varchar), content (text), and + written_on (date) + + <%= form "post" %> + + ...will generate something like (the selects will have more options, of + course): + +
+

+ Title:
+ +

+

+ Content:
+ +

+

+ Written on:
+ + + +

+ + +
+ + This form generates a params[:post] array that can be used directly in a save action: + + class WeblogController < ActionController::Base + def create + post = Post.create(params[:post]) + redirect_to :action => "show", :id => post.id + end + end + + {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html] + + +* Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby + + +== Simple example (from outside of Rails) + +This example will implement a simple weblog system using inline templates and +an Active Record model. So let's build that WeblogController with just a few +methods: + + require 'action_controller' + require 'post' + + class WeblogController < ActionController::Base + layout "weblog/layout" + + def index + @posts = Post.find(:all) + end + + def show + @post = Post.find(params[:id]) + end + + def new + @post = Post.new + end + + def create + @post = Post.create(params[:post]) + redirect_to :action => "show", :id => @post.id + end + end + + WeblogController::Base.view_paths = [ File.dirname(__FILE__) ] + WeblogController.process_cgi if $0 == __FILE__ + +The last two lines are responsible for telling ActionController where the +template files are located and actually running the controller on a new +request from the web-server (like to be Apache). + +And the templates look like this: + + weblog/layout.html.erb: + + <%= yield %> + + + weblog/index.html.erb: + <% for post in @posts %> +

<%= link_to(post.title, :action => "show", :id => post.id) %>

+ <% end %> + + weblog/show.html.erb: +

+ <%= @post.title %>
+ <%= @post.content %> +

+ + weblog/new.html.erb: + <%= form "post" %> + +This simple setup will list all the posts in the system on the index page, +which is called by accessing /weblog/. It uses the form builder for the Active +Record model to make the new screen, which in turn hands everything over to +the create action (that's the default target for the form builder when given a +new model). After creating the post, it'll redirect to the show page using +an URL such as /weblog/5 (where 5 is the id of the post). + + +== Download + +The latest version of Action Pack can be installed with Rubygems: + +* gem install actionpack + +Documentation can be found at + +* http://api.rubyonrails.org + + +== License + +Action Pack is released under the MIT license. + + +== Support + +The Action Pack homepage is http://www.rubyonrails.org. You can find +the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack. +And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. diff --git a/actionpack/Rakefile b/actionpack/Rakefile index aed5278e38..3a988d832d 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -47,7 +47,7 @@ Rake::RDocTask.new { |rdoc| if ENV['DOC_FILES'] rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/)) else - rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') + rdoc.rdoc_files.include('README.rdoc', 'RUNNING_UNIT_TESTS', 'CHANGELOG') rdoc.rdoc_files.include(Dir['lib/**/*.rb'] - Dir['lib/*/vendor/**/*.rb']) rdoc.rdoc_files.exclude('lib/actionpack.rb') diff --git a/activemodel/README b/activemodel/README deleted file mode 100644 index 6f162ef408..0000000000 --- a/activemodel/README +++ /dev/null @@ -1,206 +0,0 @@ -= Active Model - defined interfaces for Rails - -Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have -an object interact with Action Pack helpers, it was required to either -copy chunks of code from Rails, or monkey patch entire helpers to make them -handle objects that did not look like Active Record. This generated code -duplication and fragile applications that broke on upgrades. - -Active Model is a solution for this problem. - -Active Model provides a known set of interfaces that your objects can implement -to then present a common interface to the Action Pack helpers. You can include -functionality from the following modules: - -* Adding attribute magic to your objects - - Add prefixes and suffixes to defined attribute methods... - - class Person - include ActiveModel::AttributeMethods - - attribute_method_prefix 'clear_' - define_attribute_methods [:name, :age] - - attr_accessor :name, :age - - def clear_attribute(attr) - send("#{attr}=", nil) - end - end - - ...gives you clear_name, clear_age. - - {Learn more}[link:classes/ActiveModel/AttributeMethods.html] - -* Adding callbacks to your objects - - class Person - extend ActiveModel::Callbacks - define_model_callbacks :create - - def create - _run_create_callbacks do - # Your create action methods here - end - end - end - - ...gives you before_create, around_create and after_create class methods that - wrap your create method. - - {Learn more}[link:classes/ActiveModel/CallBacks.html] - -* For classes that already look like an Active Record object - - class Person - include ActiveModel::Conversion - end - - ...returns the class itself when sent :to_model - - {Learn more}[link:classes/ActiveModel/Conversion.html] - -* Tracking changes in your object - - Provides all the value tracking features implemented by ActiveRecord... - - person = Person.new - person.name # => nil - person.changed? # => false - person.name = 'bob' - person.changed? # => true - person.changed # => ['name'] - person.changes # => { 'name' => [nil, 'bob'] } - person.name = 'robert' - person.save - person.previous_changes # => {'name' => ['bob, 'robert']} - - {Learn more}[link:classes/ActiveModel/Dirty.html] - -* Adding +errors+ support to your object - - Provides the error messages to allow your object to interact with Action Pack - helpers seamlessly... - - class Person - - def initialize - @errors = ActiveModel::Errors.new(self) - end - - attr_accessor :name - attr_reader :errors - - def validate! - errors.add(:name, "can not be nil") if name == nil - end - - def ErrorsPerson.human_attribute_name(attr, options = {}) - "Name" - end - - end - - ... gives you... - - person.errors.full_messages - # => ["Name Can not be nil"] - person.errors.full_messages - # => ["Name Can not be nil"] - - {Learn more}[link:classes/ActiveModel/Errors.html] - -* Testing the compliance of your object - - Use ActiveModel::Lint to test the compliance of your object to the - basic ActiveModel API... - - {Learn more}[link:classes/ActiveModel/Lint/Tests.html] - -* Providing a human face to your object - - ActiveModel::Naming provides your model with the model_name convention - and a human_name attribute... - - class NamedPerson - extend ActiveModel::Naming - end - - ...gives you... - - NamedPerson.model_name #=> "NamedPerson" - NamedPerson.model_name.human #=> "Named person" - - {Learn more}[link:classes/ActiveModel/Naming.html] - -* Adding observer support to your objects - - ActiveModel::Observers allows your object to implement the Observer - pattern in a Rails App and take advantage of all the standard observer - functions. - - {Learn more}[link:classes/ActiveModel/Observer.html] - -* Making your object serializable - - ActiveModel::Serialization provides a standard interface for your object - to provide to_json or to_xml serialization... - - s = SerialPerson.new - s.serializable_hash # => {"name"=>nil} - s.to_json # => "{\"name\":null}" - s.to_xml # => "\n false - - {Learn more}[link:classes/ActiveModel/Validations.html] - -* Make custom validators - - class Person - include ActiveModel::Validations - validates_with HasNameValidator - attr_accessor :name - end - - class HasNameValidator < ActiveModel::Validator - def validate(record) - record.errors[:name] = "must exist" if record.name.blank? - end - end - - p = ValidatorPerson.new - p.valid? #=> false - p.errors.full_messages #=> ["Name must exist"] - p.name = "Bob" - p.valid? #=> true - - {Learn more}[link:classes/ActiveModel/Validator.html] diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc new file mode 100644 index 0000000000..6f162ef408 --- /dev/null +++ b/activemodel/README.rdoc @@ -0,0 +1,206 @@ += Active Model - defined interfaces for Rails + +Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have +an object interact with Action Pack helpers, it was required to either +copy chunks of code from Rails, or monkey patch entire helpers to make them +handle objects that did not look like Active Record. This generated code +duplication and fragile applications that broke on upgrades. + +Active Model is a solution for this problem. + +Active Model provides a known set of interfaces that your objects can implement +to then present a common interface to the Action Pack helpers. You can include +functionality from the following modules: + +* Adding attribute magic to your objects + + Add prefixes and suffixes to defined attribute methods... + + class Person + include ActiveModel::AttributeMethods + + attribute_method_prefix 'clear_' + define_attribute_methods [:name, :age] + + attr_accessor :name, :age + + def clear_attribute(attr) + send("#{attr}=", nil) + end + end + + ...gives you clear_name, clear_age. + + {Learn more}[link:classes/ActiveModel/AttributeMethods.html] + +* Adding callbacks to your objects + + class Person + extend ActiveModel::Callbacks + define_model_callbacks :create + + def create + _run_create_callbacks do + # Your create action methods here + end + end + end + + ...gives you before_create, around_create and after_create class methods that + wrap your create method. + + {Learn more}[link:classes/ActiveModel/CallBacks.html] + +* For classes that already look like an Active Record object + + class Person + include ActiveModel::Conversion + end + + ...returns the class itself when sent :to_model + + {Learn more}[link:classes/ActiveModel/Conversion.html] + +* Tracking changes in your object + + Provides all the value tracking features implemented by ActiveRecord... + + person = Person.new + person.name # => nil + person.changed? # => false + person.name = 'bob' + person.changed? # => true + person.changed # => ['name'] + person.changes # => { 'name' => [nil, 'bob'] } + person.name = 'robert' + person.save + person.previous_changes # => {'name' => ['bob, 'robert']} + + {Learn more}[link:classes/ActiveModel/Dirty.html] + +* Adding +errors+ support to your object + + Provides the error messages to allow your object to interact with Action Pack + helpers seamlessly... + + class Person + + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "can not be nil") if name == nil + end + + def ErrorsPerson.human_attribute_name(attr, options = {}) + "Name" + end + + end + + ... gives you... + + person.errors.full_messages + # => ["Name Can not be nil"] + person.errors.full_messages + # => ["Name Can not be nil"] + + {Learn more}[link:classes/ActiveModel/Errors.html] + +* Testing the compliance of your object + + Use ActiveModel::Lint to test the compliance of your object to the + basic ActiveModel API... + + {Learn more}[link:classes/ActiveModel/Lint/Tests.html] + +* Providing a human face to your object + + ActiveModel::Naming provides your model with the model_name convention + and a human_name attribute... + + class NamedPerson + extend ActiveModel::Naming + end + + ...gives you... + + NamedPerson.model_name #=> "NamedPerson" + NamedPerson.model_name.human #=> "Named person" + + {Learn more}[link:classes/ActiveModel/Naming.html] + +* Adding observer support to your objects + + ActiveModel::Observers allows your object to implement the Observer + pattern in a Rails App and take advantage of all the standard observer + functions. + + {Learn more}[link:classes/ActiveModel/Observer.html] + +* Making your object serializable + + ActiveModel::Serialization provides a standard interface for your object + to provide to_json or to_xml serialization... + + s = SerialPerson.new + s.serializable_hash # => {"name"=>nil} + s.to_json # => "{\"name\":null}" + s.to_xml # => "\n false + + {Learn more}[link:classes/ActiveModel/Validations.html] + +* Make custom validators + + class Person + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors[:name] = "must exist" if record.name.blank? + end + end + + p = ValidatorPerson.new + p.valid? #=> false + p.errors.full_messages #=> ["Name must exist"] + p.name = "Bob" + p.valid? #=> true + + {Learn more}[link:classes/ActiveModel/Validator.html] diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 1dba664539..4e4bbcee96 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -32,7 +32,7 @@ Rake::RDocTask.new do |rdoc| rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include("README", "CHANGELOG") + rdoc.rdoc_files.include("README.rdoc", "CHANGELOG") rdoc.rdoc_files.include("lib/**/*.rb") end diff --git a/activerecord/README b/activerecord/README deleted file mode 100644 index 0446180207..0000000000 --- a/activerecord/README +++ /dev/null @@ -1,336 +0,0 @@ -= Active Record -- Object-relation mapping put on rails - -Active Record connects business objects and database tables to create a persistable -domain model where logic and data are presented in one wrapping. It's an implementation -of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] -by the same name as described by Martin Fowler: - - "An object that wraps a row in a database table or view, encapsulates - the database access, and adds domain logic on that data." - -Active Record's main contribution to the pattern is to relieve the original of two stunting problems: -lack of associations and inheritance. By adding a simple domain language-like set of macros to describe -the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the -gap of functionality between the data mapper and active record approach. - -A short rundown of the major features: - -* Automated mapping between classes and tables, attributes and columns. - - class Product < ActiveRecord::Base; end - - ...is automatically mapped to the table named "products", such as: - - CREATE TABLE products ( - id int(11) NOT NULL auto_increment, - name varchar(255), - PRIMARY KEY (id) - ); - - ...which again gives Product#name and Product#name=(new_name) - - {Learn more}[link:classes/ActiveRecord/Base.html] - - -* Associations between objects controlled by simple meta-programming macros. - - class Firm < ActiveRecord::Base - has_many :clients - has_one :account - belongs_to :conglomorate - end - - {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] - - -* Aggregations of value objects controlled by simple meta-programming macros. - - class Account < ActiveRecord::Base - composed_of :balance, :class_name => "Money", - :mapping => %w(balance amount) - composed_of :address, - :mapping => [%w(address_street street), %w(address_city city)] - end - - {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] - - -* Validation rules that can differ for new or existing objects. - - class Account < ActiveRecord::Base - validates_presence_of :subdomain, :name, :email_address, :password - validates_uniqueness_of :subdomain - validates_acceptance_of :terms_of_service, :on => :create - validates_confirmation_of :password, :email_address, :on => :create - end - - {Learn more}[link:classes/ActiveRecord/Validations.html] - -* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc). - - class Person < ActiveRecord::Base - def before_destroy # is called just before Person#destroy - CreditCard.find(credit_card_id).destroy - end - end - - class Account < ActiveRecord::Base - after_find :eager_load, 'self.class.announce(#{id})' - end - - {Learn more}[link:classes/ActiveRecord/Callbacks.html] - - -* Observers for the entire lifecycle - - class CommentObserver < ActiveRecord::Observer - def after_create(comment) # is called just after Comment#save - Notifications.deliver_new_comment("david@loudthinking.com", comment) - end - end - - {Learn more}[link:classes/ActiveRecord/Observer.html] - - -* Inheritance hierarchies - - class Company < ActiveRecord::Base; end - class Firm < Company; end - class Client < Company; end - class PriorityClient < Client; end - - {Learn more}[link:classes/ActiveRecord/Base.html] - - -* Transactions - - # Database transaction - Account.transaction do - david.withdrawal(100) - mary.deposit(100) - end - - {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] - - -* Reflections on columns, associations, and aggregations - - reflection = Firm.reflect_on_association(:clients) - reflection.klass # => Client (class) - Firm.columns # Returns an array of column descriptors for the firms table - - {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] - - -* Direct manipulation (instead of service invocation) - - So instead of (Hibernate[http://www.hibernate.org/] example): - - long pkId = 1234; - DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) ); - // something interesting involving a cat... - sess.save(cat); - sess.flush(); // force the SQL INSERT - - Active Record lets you: - - pkId = 1234 - cat = Cat.find(pkId) - # something even more interesting involving the same cat... - cat.save - - {Learn more}[link:classes/ActiveRecord/Base.html] - - -* Database abstraction through simple adapters (~100 lines) with a shared connector - - ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") - - ActiveRecord::Base.establish_connection( - :adapter => "mysql", - :host => "localhost", - :username => "me", - :password => "secret", - :database => "activerecord" - ) - - {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for - MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html]. - - -* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] - - ActiveRecord::Base.logger = Logger.new(STDOUT) - ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") - - -* Database agnostic schema management with Migrations - - class AddSystemSettings < ActiveRecord::Migration - def self.up - create_table :system_settings do |t| - t.string :name - t.string :label - t.text :value - t.string :type - t.integer :position - end - - SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1 - end - - def self.down - drop_table :system_settings - end - end - - {Learn more}[link:classes/ActiveRecord/Migration.html] - -== Simple example (1/2): Defining tables and classes (using MySQL) - -Data definitions are specified only in the database. Active Record queries the database for -the column names (that then serves to determine which attributes are valid) on regular -object instantiation through the new constructor and relies on the column names in the rows -with the finders. - - # CREATE TABLE companies ( - # id int(11) unsigned NOT NULL auto_increment, - # client_of int(11), - # name varchar(255), - # type varchar(100), - # PRIMARY KEY (id) - # ) - -Active Record automatically links the "Company" object to the "companies" table - - class Company < ActiveRecord::Base - has_many :people, :class_name => "Person" - end - - class Firm < Company - has_many :clients - - def people_with_all_clients - clients.inject([]) { |people, client| people + client.people } - end - end - -The foreign_key is only necessary because we didn't use "firm_id" in the data definition - - class Client < Company - belongs_to :firm, :foreign_key => "client_of" - end - - # CREATE TABLE people ( - # id int(11) unsigned NOT NULL auto_increment, - # name text, - # company_id text, - # PRIMARY KEY (id) - # ) - -Active Record will also automatically link the "Person" object to the "people" table - - class Person < ActiveRecord::Base - belongs_to :company - end - -== Simple example (2/2): Using the domain - -Picking a database connection for all the Active Records - - ActiveRecord::Base.establish_connection( - :adapter => "mysql", - :host => "localhost", - :username => "me", - :password => "secret", - :database => "activerecord" - ) - -Create some fixtures - - firm = Firm.new("name" => "Next Angle") - # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm") - firm.save - - client = Client.new("name" => "37signals", "client_of" => firm.id) - # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm") - client.save - -Lots of different finders - - # SQL: SELECT * FROM companies WHERE id = 1 - next_angle = Company.find(1) - - # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm' - next_angle = Firm.find(1) - - # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle' - next_angle = Company.find(:first, :conditions => "name = 'Next Angle'") - - next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first - -The supertype, Company, will return subtype instances - - Firm === next_angle - -All the dynamic methods added by the has_many macro - - next_angle.clients.empty? # true - next_angle.clients.size # total number of clients - all_clients = next_angle.clients - -Constrained finds makes access security easier when ID comes from a web-app - - # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2 - thirty_seven_signals = next_angle.clients.find(2) - -Bi-directional associations thanks to the "belongs_to" macro - - thirty_seven_signals.firm.nil? # true - - -== Philosophy - -Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is -object-relational mapping. The prime directive for this mapping has been to minimize -the amount of code needed to build a real-world domain model. This is made possible -by relying on a number of conventions that make it easy for Active Record to infer -complex relations and structures from a minimal amount of explicit direction. - -Convention over Configuration: -* No XML-files! -* Lots of reflection and run-time extension -* Magic is not inherently a bad word - -Admit the Database: -* Lets you drop down to SQL for odd cases and performance -* Doesn't attempt to duplicate or replace data definitions - - -== Download - -The latest version of Active Record can be installed with Rubygems: - -* gem install activerecord - -Documentation can be found at - -* http://api.rubyonrails.org - - -== License - -Active Record is released under the MIT license. - - -== Support - -The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record -RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says: - - Feel free to submit commits or feature requests. If you send a patch, - remember to update the corresponding unit tests. If fact, I prefer - new feature to be submitted in the form of new unit tests. - -For other information, feel free to ask on the rubyonrails-talk -(http://groups.google.com/group/rubyonrails-talk) mailing list. diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc new file mode 100644 index 0000000000..0446180207 --- /dev/null +++ b/activerecord/README.rdoc @@ -0,0 +1,336 @@ += Active Record -- Object-relation mapping put on rails + +Active Record connects business objects and database tables to create a persistable +domain model where logic and data are presented in one wrapping. It's an implementation +of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] +by the same name as described by Martin Fowler: + + "An object that wraps a row in a database table or view, encapsulates + the database access, and adds domain logic on that data." + +Active Record's main contribution to the pattern is to relieve the original of two stunting problems: +lack of associations and inheritance. By adding a simple domain language-like set of macros to describe +the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the +gap of functionality between the data mapper and active record approach. + +A short rundown of the major features: + +* Automated mapping between classes and tables, attributes and columns. + + class Product < ActiveRecord::Base; end + + ...is automatically mapped to the table named "products", such as: + + CREATE TABLE products ( + id int(11) NOT NULL auto_increment, + name varchar(255), + PRIMARY KEY (id) + ); + + ...which again gives Product#name and Product#name=(new_name) + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Associations between objects controlled by simple meta-programming macros. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomorate + end + + {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] + + +* Aggregations of value objects controlled by simple meta-programming macros. + + class Account < ActiveRecord::Base + composed_of :balance, :class_name => "Money", + :mapping => %w(balance amount) + composed_of :address, + :mapping => [%w(address_street street), %w(address_city city)] + end + + {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] + + +* Validation rules that can differ for new or existing objects. + + class Account < ActiveRecord::Base + validates_presence_of :subdomain, :name, :email_address, :password + validates_uniqueness_of :subdomain + validates_acceptance_of :terms_of_service, :on => :create + validates_confirmation_of :password, :email_address, :on => :create + end + + {Learn more}[link:classes/ActiveRecord/Validations.html] + +* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc). + + class Person < ActiveRecord::Base + def before_destroy # is called just before Person#destroy + CreditCard.find(credit_card_id).destroy + end + end + + class Account < ActiveRecord::Base + after_find :eager_load, 'self.class.announce(#{id})' + end + + {Learn more}[link:classes/ActiveRecord/Callbacks.html] + + +* Observers for the entire lifecycle + + class CommentObserver < ActiveRecord::Observer + def after_create(comment) # is called just after Comment#save + Notifications.deliver_new_comment("david@loudthinking.com", comment) + end + end + + {Learn more}[link:classes/ActiveRecord/Observer.html] + + +* Inheritance hierarchies + + class Company < ActiveRecord::Base; end + class Firm < Company; end + class Client < Company; end + class PriorityClient < Client; end + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Transactions + + # Database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] + + +* Reflections on columns, associations, and aggregations + + reflection = Firm.reflect_on_association(:clients) + reflection.klass # => Client (class) + Firm.columns # Returns an array of column descriptors for the firms table + + {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] + + +* Direct manipulation (instead of service invocation) + + So instead of (Hibernate[http://www.hibernate.org/] example): + + long pkId = 1234; + DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) ); + // something interesting involving a cat... + sess.save(cat); + sess.flush(); // force the SQL INSERT + + Active Record lets you: + + pkId = 1234 + cat = Cat.find(pkId) + # something even more interesting involving the same cat... + cat.save + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Database abstraction through simple adapters (~100 lines) with a shared connector + + ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") + + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) + + {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html]. + + +* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] + + ActiveRecord::Base.logger = Logger.new(STDOUT) + ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") + + +* Database agnostic schema management with Migrations + + class AddSystemSettings < ActiveRecord::Migration + def self.up + create_table :system_settings do |t| + t.string :name + t.string :label + t.text :value + t.string :type + t.integer :position + end + + SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1 + end + + def self.down + drop_table :system_settings + end + end + + {Learn more}[link:classes/ActiveRecord/Migration.html] + +== Simple example (1/2): Defining tables and classes (using MySQL) + +Data definitions are specified only in the database. Active Record queries the database for +the column names (that then serves to determine which attributes are valid) on regular +object instantiation through the new constructor and relies on the column names in the rows +with the finders. + + # CREATE TABLE companies ( + # id int(11) unsigned NOT NULL auto_increment, + # client_of int(11), + # name varchar(255), + # type varchar(100), + # PRIMARY KEY (id) + # ) + +Active Record automatically links the "Company" object to the "companies" table + + class Company < ActiveRecord::Base + has_many :people, :class_name => "Person" + end + + class Firm < Company + has_many :clients + + def people_with_all_clients + clients.inject([]) { |people, client| people + client.people } + end + end + +The foreign_key is only necessary because we didn't use "firm_id" in the data definition + + class Client < Company + belongs_to :firm, :foreign_key => "client_of" + end + + # CREATE TABLE people ( + # id int(11) unsigned NOT NULL auto_increment, + # name text, + # company_id text, + # PRIMARY KEY (id) + # ) + +Active Record will also automatically link the "Person" object to the "people" table + + class Person < ActiveRecord::Base + belongs_to :company + end + +== Simple example (2/2): Using the domain + +Picking a database connection for all the Active Records + + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) + +Create some fixtures + + firm = Firm.new("name" => "Next Angle") + # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm") + firm.save + + client = Client.new("name" => "37signals", "client_of" => firm.id) + # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm") + client.save + +Lots of different finders + + # SQL: SELECT * FROM companies WHERE id = 1 + next_angle = Company.find(1) + + # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm' + next_angle = Firm.find(1) + + # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle' + next_angle = Company.find(:first, :conditions => "name = 'Next Angle'") + + next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first + +The supertype, Company, will return subtype instances + + Firm === next_angle + +All the dynamic methods added by the has_many macro + + next_angle.clients.empty? # true + next_angle.clients.size # total number of clients + all_clients = next_angle.clients + +Constrained finds makes access security easier when ID comes from a web-app + + # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2 + thirty_seven_signals = next_angle.clients.find(2) + +Bi-directional associations thanks to the "belongs_to" macro + + thirty_seven_signals.firm.nil? # true + + +== Philosophy + +Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is +object-relational mapping. The prime directive for this mapping has been to minimize +the amount of code needed to build a real-world domain model. This is made possible +by relying on a number of conventions that make it easy for Active Record to infer +complex relations and structures from a minimal amount of explicit direction. + +Convention over Configuration: +* No XML-files! +* Lots of reflection and run-time extension +* Magic is not inherently a bad word + +Admit the Database: +* Lets you drop down to SQL for odd cases and performance +* Doesn't attempt to duplicate or replace data definitions + + +== Download + +The latest version of Active Record can be installed with Rubygems: + +* gem install activerecord + +Documentation can be found at + +* http://api.rubyonrails.org + + +== License + +Active Record is released under the MIT license. + + +== Support + +The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record +RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. + +For other information, feel free to ask on the rubyonrails-talk +(http://groups.google.com/group/rubyonrails-talk) mailing list. diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 392b717e0a..d9124c9776 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -172,7 +172,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') + rdoc.rdoc_files.include('README.rdoc', 'RUNNING_UNIT_TESTS', 'CHANGELOG') rdoc.rdoc_files.include('lib/**/*.rb') rdoc.rdoc_files.exclude('lib/active_record/vendor/*') rdoc.rdoc_files.include('dev-utils/*.rb') diff --git a/activeresource/README b/activeresource/README deleted file mode 100644 index 127ac5b4a9..0000000000 --- a/activeresource/README +++ /dev/null @@ -1,165 +0,0 @@ -= Active Resource - -Active Resource (ARes) connects business objects and Representational State Transfer (REST) -web services. It implements object-relational mapping for REST web services to provide transparent -proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing -in ActionController::Resources). - -== Philosophy - -Active Resource attempts to provide a coherent wrapper object-relational mapping for REST -web services. It follows the same philosophy as Active Record, in that one of its prime aims -is to reduce the amount of code needed to map to these resources. This is made possible -by relying on a number of code- and protocol-based conventions that make it easy for Active Resource -to infer complex relations and structures. These conventions are outlined in detail in the documentation -for ActiveResource::Base. - -== Overview - -Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database -tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result -received and serialized into a usable Ruby object. - -=== Configuration and Usage - -Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class -that inherits from ActiveResource::Base and providing a site class variable to it: - - class Person < ActiveResource::Base - self.site = "http://api.people.com:3000/" - end - -Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes -lifecycle methods that operate against a persistent store. - - # Find a person with id = 1 - ryan = Person.find(1) - Person.exists?(1) #=> true - -As you can see, the methods are quite similar to Active Record's methods for dealing with database -records. But rather than dealing directly with a database record, you're dealing with HTTP resources (which may or may not be database records). - -==== Protocol - -Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing -built into Action Controller but will also work with any other REST service that properly implements the protocol. -REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification: - -* GET requests are used for finding and retrieving resources. -* POST requests are used to create new resources. -* PUT requests are used to update existing resources. -* DELETE requests are used to delete resources. - -For more information on how this protocol works with Active Resource, see the ActiveResource::Base documentation; -for more general information on REST web services, see the article here[http://en.wikipedia.org/wiki/Representational_State_Transfer]. - -==== Find - -Find requests use the GET method and expect the XML form of whatever resource/resources is/are being requested. So, -for a request for a single element, the XML of that item is expected in response: - - # Expects a response of - # - # 1value1.. - # - # for GET http://api.people.com:3000/people/1.xml - # - ryan = Person.find(1) - -The XML document that is received is used to build a new object of type Person, with each -XML element becoming an attribute on the object. - - ryan.is_a? Person #=> true - ryan.attribute1 #=> 'value1' - -Any complex element (one that contains other elements) becomes its own object: - - # With this response: - # - # 1value1value2 - # - # for GET http://api.people.com:3000/people/1.xml - # - ryan = Person.find(1) - ryan.complex #=> - ryan.complex.attribute2 #=> 'value2' - -Collections can also be requested in a similar fashion - - # Expects a response of - # - # - # 1Ryan - # 2Jim - # - # - # for GET http://api.people.com:3000/people.xml - # - people = Person.find(:all) - people.first #=> 'Ryan' ...> - people.last #=> 'Jim' ...> - -==== Create - -Creating a new resource submits the XML form of the resource as the body of the request and expects -a 'Location' header in the response with the RESTful URL location of the newly created resource. The -id of the newly created resource is parsed out of the Location response header and automatically set -as the id of the ARes object. - - # Ryan - # - # is submitted as the body on - # - # POST http://api.people.com:3000/people.xml - # - # when save is called on a new Person object. An empty response is - # is expected with a 'Location' header value: - # - # Response (201): Location: http://api.people.com:3000/people/2 - # - ryan = Person.new(:first => 'Ryan') - ryan.new? #=> true - ryan.save #=> true - ryan.new? #=> false - ryan.id #=> 2 - -==== Update - -'save' is also used to update an existing resource - and follows the same protocol as creating a resource -with the exception that no response headers are needed - just an empty response when the update on the -server side was successful. - - # Ryan - # - # is submitted as the body on - # - # PUT http://api.people.com:3000/people/1.xml - # - # when save is called on an existing Person object. An empty response is - # is expected with code (204) - # - ryan = Person.find(1) - ryan.first #=> 'Ryan' - ryan.first = 'Rizzle' - ryan.save #=> true - -==== Delete - -Destruction of a resource can be invoked as a class and instance method of the resource. - - # A request is made to - # - # DELETE http://api.people.com:3000/people/1.xml - # - # for both of these forms. An empty response with - # is expected with response code (200) - # - ryan = Person.find(1) - ryan.destroy #=> true - ryan.exists? #=> false - Person.delete(2) #=> true - Person.exists?(2) #=> false - - -You can find more usage information in the ActiveResource::Base documentation. - diff --git a/activeresource/README.rdoc b/activeresource/README.rdoc new file mode 100644 index 0000000000..127ac5b4a9 --- /dev/null +++ b/activeresource/README.rdoc @@ -0,0 +1,165 @@ += Active Resource + +Active Resource (ARes) connects business objects and Representational State Transfer (REST) +web services. It implements object-relational mapping for REST web services to provide transparent +proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing +in ActionController::Resources). + +== Philosophy + +Active Resource attempts to provide a coherent wrapper object-relational mapping for REST +web services. It follows the same philosophy as Active Record, in that one of its prime aims +is to reduce the amount of code needed to map to these resources. This is made possible +by relying on a number of code- and protocol-based conventions that make it easy for Active Resource +to infer complex relations and structures. These conventions are outlined in detail in the documentation +for ActiveResource::Base. + +== Overview + +Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database +tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result +received and serialized into a usable Ruby object. + +=== Configuration and Usage + +Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class +that inherits from ActiveResource::Base and providing a site class variable to it: + + class Person < ActiveResource::Base + self.site = "http://api.people.com:3000/" + end + +Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes +lifecycle methods that operate against a persistent store. + + # Find a person with id = 1 + ryan = Person.find(1) + Person.exists?(1) #=> true + +As you can see, the methods are quite similar to Active Record's methods for dealing with database +records. But rather than dealing directly with a database record, you're dealing with HTTP resources (which may or may not be database records). + +==== Protocol + +Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing +built into Action Controller but will also work with any other REST service that properly implements the protocol. +REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification: + +* GET requests are used for finding and retrieving resources. +* POST requests are used to create new resources. +* PUT requests are used to update existing resources. +* DELETE requests are used to delete resources. + +For more information on how this protocol works with Active Resource, see the ActiveResource::Base documentation; +for more general information on REST web services, see the article here[http://en.wikipedia.org/wiki/Representational_State_Transfer]. + +==== Find + +Find requests use the GET method and expect the XML form of whatever resource/resources is/are being requested. So, +for a request for a single element, the XML of that item is expected in response: + + # Expects a response of + # + # 1value1.. + # + # for GET http://api.people.com:3000/people/1.xml + # + ryan = Person.find(1) + +The XML document that is received is used to build a new object of type Person, with each +XML element becoming an attribute on the object. + + ryan.is_a? Person #=> true + ryan.attribute1 #=> 'value1' + +Any complex element (one that contains other elements) becomes its own object: + + # With this response: + # + # 1value1value2 + # + # for GET http://api.people.com:3000/people/1.xml + # + ryan = Person.find(1) + ryan.complex #=> + ryan.complex.attribute2 #=> 'value2' + +Collections can also be requested in a similar fashion + + # Expects a response of + # + # + # 1Ryan + # 2Jim + # + # + # for GET http://api.people.com:3000/people.xml + # + people = Person.find(:all) + people.first #=> 'Ryan' ...> + people.last #=> 'Jim' ...> + +==== Create + +Creating a new resource submits the XML form of the resource as the body of the request and expects +a 'Location' header in the response with the RESTful URL location of the newly created resource. The +id of the newly created resource is parsed out of the Location response header and automatically set +as the id of the ARes object. + + # Ryan + # + # is submitted as the body on + # + # POST http://api.people.com:3000/people.xml + # + # when save is called on a new Person object. An empty response is + # is expected with a 'Location' header value: + # + # Response (201): Location: http://api.people.com:3000/people/2 + # + ryan = Person.new(:first => 'Ryan') + ryan.new? #=> true + ryan.save #=> true + ryan.new? #=> false + ryan.id #=> 2 + +==== Update + +'save' is also used to update an existing resource - and follows the same protocol as creating a resource +with the exception that no response headers are needed - just an empty response when the update on the +server side was successful. + + # Ryan + # + # is submitted as the body on + # + # PUT http://api.people.com:3000/people/1.xml + # + # when save is called on an existing Person object. An empty response is + # is expected with code (204) + # + ryan = Person.find(1) + ryan.first #=> 'Ryan' + ryan.first = 'Rizzle' + ryan.save #=> true + +==== Delete + +Destruction of a resource can be invoked as a class and instance method of the resource. + + # A request is made to + # + # DELETE http://api.people.com:3000/people/1.xml + # + # for both of these forms. An empty response with + # is expected with response code (200) + # + ryan = Person.find(1) + ryan.destroy #=> true + ryan.exists? #=> false + Person.delete(2) #=> true + Person.exists?(2) #=> false + + +You can find more usage information in the ActiveResource::Base documentation. + diff --git a/activeresource/Rakefile b/activeresource/Rakefile index 04b08ed8cb..b1e5ca91d3 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -35,7 +35,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG') rdoc.rdoc_files.include('lib/**/*.rb') rdoc.rdoc_files.exclude('lib/activeresource.rb') } diff --git a/activesupport/README b/activesupport/README deleted file mode 100644 index aa86f1fd65..0000000000 --- a/activesupport/README +++ /dev/null @@ -1,34 +0,0 @@ -= Active Support -- Utility classes and standard library extensions from Rails - -Active Support is a collection of various utility classes and standard library extensions that were found useful -for Rails. All these additions have hence been collected in this bundle as way to gather all that sugar that makes -Ruby sweeter. - - -== Download - -The latest version of Active Support can be installed with Rubygems: - -* gem install activesupport - -Documentation can be found at - -* http://api.rubyonrails.org - - -== License - -Active Support is released under the MIT license. - - -== Support - -The Active Support homepage is http://www.rubyonrails.com. You can find the Active Support -RubyForge page at http://rubyforge.org/projects/activesupport. And as Jim from Rake says: - - Feel free to submit commits or feature requests. If you send a patch, - remember to update the corresponding unit tests. If fact, I prefer - new feature to be submitted in the form of new unit tests. - -For other information, feel free to ask on the ruby-talk mailing list -(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc new file mode 100644 index 0000000000..aa86f1fd65 --- /dev/null +++ b/activesupport/README.rdoc @@ -0,0 +1,34 @@ += Active Support -- Utility classes and standard library extensions from Rails + +Active Support is a collection of various utility classes and standard library extensions that were found useful +for Rails. All these additions have hence been collected in this bundle as way to gather all that sugar that makes +Ruby sweeter. + + +== Download + +The latest version of Active Support can be installed with Rubygems: + +* gem install activesupport + +Documentation can be found at + +* http://api.rubyonrails.org + + +== License + +Active Support is released under the MIT license. + + +== Support + +The Active Support homepage is http://www.rubyonrails.com. You can find the Active Support +RubyForge page at http://rubyforge.org/projects/activesupport. And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. + +For other information, feel free to ask on the ruby-talk mailing list +(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 2aebe05de2..77b1a8431d 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -28,7 +28,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG') rdoc.rdoc_files.include('lib/active_support.rb') rdoc.rdoc_files.include('lib/active_support/**/*.rb') } diff --git a/railties/README b/railties/README deleted file mode 100644 index a1718a7d96..0000000000 --- a/railties/README +++ /dev/null @@ -1,25 +0,0 @@ -= Railties -- Gluing the Engine to the Rails - -Railties is responsible to glue all frameworks together. Overall, it: - -* handles all the bootstrapping process for a Rails application; - -* manager rails command line interface; - -* provides Rails generators core; - - -== Download - -The latest version of Railties can be installed with Rubygems: - -* gem install railties - -Documentation can be found at - -* http://api.rubyonrails.org - - -== License - -Railties is released under the MIT license. diff --git a/railties/README.rdoc b/railties/README.rdoc new file mode 100644 index 0000000000..a1718a7d96 --- /dev/null +++ b/railties/README.rdoc @@ -0,0 +1,25 @@ += Railties -- Gluing the Engine to the Rails + +Railties is responsible to glue all frameworks together. Overall, it: + +* handles all the bootstrapping process for a Rails application; + +* manager rails command line interface; + +* provides Rails generators core; + + +== Download + +The latest version of Railties can be installed with Rubygems: + +* gem install railties + +Documentation can be found at + +* http://api.rubyonrails.org + + +== License + +Railties is released under the MIT license. diff --git a/railties/Rakefile b/railties/Rakefile index ae9db3c022..19c860f257 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -59,7 +59,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG') rdoc.rdoc_files.include('lib/**/*.rb') rdoc.rdoc_files.exclude('lib/rails/generators/**/templates/*') } -- cgit v1.2.3 From 53310614d7ef279e3b2d5b02e4468e3e3f25e0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 13:00:56 +0200 Subject: Reuse already existing classes for ActiveModel tests. --- activemodel/test/cases/naming_helpers_test.rb | 63 --------------------------- activemodel/test/cases/naming_test.rb | 39 +++++++++++++++++ activemodel/test/models/contact.rb | 1 + activemodel/test/models/sheep.rb | 4 ++ 4 files changed, 44 insertions(+), 63 deletions(-) delete mode 100644 activemodel/test/cases/naming_helpers_test.rb create mode 100644 activemodel/test/models/sheep.rb diff --git a/activemodel/test/cases/naming_helpers_test.rb b/activemodel/test/cases/naming_helpers_test.rb deleted file mode 100644 index e7234e009e..0000000000 --- a/activemodel/test/cases/naming_helpers_test.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'cases/helper' - -class Comment - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - def to_key; id ? [id] : nil end - def save; @id = 1 end - def new_record?; @id.nil? end - def name - @id.nil? ? 'new comment' : "comment ##{@id}" - end -end - -class Sheep - extend ActiveModel::Naming - include ActiveModel::Conversion - - attr_reader :id - def to_key; id ? [id] : nil end - def save; @id = 1 end - def new_record?; @id.nil? end - def name - @id.nil? ? 'new sheep' : "sheep ##{@id}" - end -end - -class NamingHelpersTest < Test::Unit::TestCase - def setup - @klass = Comment - @record = @klass.new - @singular = 'comment' - @plural = 'comments' - @uncountable = Sheep - end - - def test_singular - assert_equal @singular, singular(@record) - end - - def test_singular_for_class - assert_equal @singular, singular(@klass) - end - - def test_plural - assert_equal @plural, plural(@record) - end - - def test_plural_for_class - assert_equal @plural, plural(@klass) - end - - def test_uncountable - assert_equal true, uncountable?(@uncountable) - assert_equal false, uncountable?(@klass) - end - - private - def method_missing(method, *args) - ActiveModel::Naming.send(method, *args) - end -end diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index dc39b84ed8..5a8bff378a 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -1,4 +1,6 @@ require 'cases/helper' +require 'models/contact' +require 'models/sheep' require 'models/track_back' class NamingTest < ActiveModel::TestCase @@ -26,3 +28,40 @@ class NamingTest < ActiveModel::TestCase assert_equal 'post/track_backs/track_back', @model_name.partial_path end end + +class NamingHelpersTest < Test::Unit::TestCase + def setup + @klass = Contact + @record = @klass.new + @singular = 'contact' + @plural = 'contacts' + @uncountable = Sheep + end + + def test_singular + assert_equal @singular, singular(@record) + end + + def test_singular_for_class + assert_equal @singular, singular(@klass) + end + + def test_plural + assert_equal @plural, plural(@record) + end + + def test_plural_for_class + assert_equal @plural, plural(@klass) + end + + def test_uncountable + assert uncountable?(@uncountable), "Expected 'sheep' to be uncoutable" + assert !uncountable?(@klass), "Expected 'contact' to be countable" + end + + private + def method_missing(method, *args) + ActiveModel::Naming.send(method, *args) + end +end + diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb index 605e435f39..f4f3078473 100644 --- a/activemodel/test/models/contact.rb +++ b/activemodel/test/models/contact.rb @@ -1,4 +1,5 @@ class Contact + extend ActiveModel::Naming include ActiveModel::Conversion attr_accessor :id, :name, :age, :created_at, :awesome, :preferences diff --git a/activemodel/test/models/sheep.rb b/activemodel/test/models/sheep.rb new file mode 100644 index 0000000000..175dbe6477 --- /dev/null +++ b/activemodel/test/models/sheep.rb @@ -0,0 +1,4 @@ +class Sheep + extend ActiveModel::Naming +end + \ No newline at end of file -- cgit v1.2.3 From 0a729b0a9138841676e86e1a33337c573028e9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 14:16:37 +0200 Subject: Fix failing railties tests. --- actionpack/lib/action_dispatch.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 9bb0471ffc..50d8e91c47 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -24,6 +24,9 @@ activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) + require 'active_support' require 'active_support/dependencies/autoload' -- cgit v1.2.3 From 6ba7d5e6544d636a763a40d1543f96d0e0bd77d5 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 9 Jul 2010 06:07:00 -0400 Subject: - without the id test is passing even if I change :allow_destroy from 'false' - adding more tests to strengthen the test suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/nested_attributes_test.rb | 49 ++++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index c9ea0d8c40..84ab61f591 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -59,6 +59,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase pirate.save! assert_equal 1, pirate.birds_with_reject_all_blank.count + assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name end def test_should_raise_an_ArgumentError_for_non_existing_associations @@ -74,7 +75,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase ship = pirate.create_ship(:name => 'Nights Dirty Lightning') assert_no_difference('Ship.count') do - pirate.update_attributes(:ship_attributes => { '_destroy' => true }) + pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id }) end end @@ -100,7 +101,8 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true } assert_no_difference('Ship.count') { pirate.save! } - # pirate.reject_empty_ships_on_create returns false for saved records + # pirate.reject_empty_ships_on_create returns false for saved pirate records + # in the previous step note that pirate gets saved but ship fails pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true } assert_difference('Ship.count') { pirate.save! } end @@ -266,6 +268,28 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end assert_equal 'Mayflower', @ship.reload.name end + + def test_should_update_existing_when_update_only_is_true_and_id_is_given + @ship.delete + @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') + + assert_no_difference('Ship.count') do + @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id }) + end + assert_equal 'Mayflower', @ship.reload.name + end + + def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction + Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true + @ship.delete + @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') + + assert_difference('Ship.count', -1) do + @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true }) + end + Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false + end + end class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @@ -411,6 +435,27 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end assert_equal 'Arr', @pirate.reload.catchphrase end + + def test_should_update_existing_when_update_only_is_true_and_id_is_given + @pirate.delete + @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') + + assert_no_difference('Pirate.count') do + @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id }) + end + assert_equal 'Arr', @pirate.reload.catchphrase + end + + def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction + Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true + @pirate.delete + @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') + + assert_difference('Pirate.count', -1) do + @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true }) + end + Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false + end end module NestedAttributesOnACollectionAssociationTests -- cgit v1.2.3 From c0bfa0bfc17f4aa615cd9d1006509e0d84b5692d Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Sat, 10 Jul 2010 12:29:09 -0400 Subject: In nested_attributes when association is not loaded and association record is saved and then in memory record attributes should be saved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5053 state:resolved] Signed-off-by: José Valim --- .../lib/active_record/associations/association_collection.rb | 9 ++++++++- activerecord/test/cases/nested_attributes_test.rb | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 7abb738a74..7100be0245 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -477,7 +477,14 @@ module ActiveRecord callback(:before_add, record) yield(record) if block_given? @target ||= [] unless loaded? - @target << record unless @reflection.options[:uniq] && @target.include?(record) + index = @target.index(record) + unless @reflection.options[:uniq] && index + if index + @target[index] = record + else + @target << record + end + end callback(:after_add, record) set_inverse_instance(record, @owner) record diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 84ab61f591..20bd4f6a76 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -856,6 +856,12 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR @part = @ship.parts.create!(:name => "Mast") @trinket = @part.trinkets.create!(:name => "Necklace") end + + test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do + @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}] + assert_equal 1, @ship.parts.proxy_target.size + assert_equal 'Deck', @ship.parts[0].name + end test "when grandchild changed in memory, saving parent should save grandchild" do @trinket.name = "changed" -- cgit v1.2.3 From 0057d2df71ec7c2a788acd3b4bade263fc0fe361 Mon Sep 17 00:00:00 2001 From: Szetobo Date: Mon, 12 Jul 2010 09:47:47 +0800 Subject: association load target shouldn't replace records from db if it is already loaded by nested attributes assignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5053 state:resolved] Signed-off-by: José Valim --- .../active_record/associations/association_collection.rb | 7 ++++--- activerecord/test/cases/nested_attributes_test.rb | 14 +++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 7100be0245..692badcc5c 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -396,11 +396,12 @@ module ActiveRecord if @target.is_a?(Array) && @target.any? @target = find_target.map do |f| i = @target.index(f) - t = @target.delete_at(i) if i - if t && t.changed? + if i + t = @target.delete_at(i) + keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names) + t.attributes = f.attributes.except(*keys) t else - f.mark_for_destruction if t && t.marked_for_destruction? f end end + @target diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 20bd4f6a76..df09bbd46a 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -862,7 +862,19 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR assert_equal 1, @ship.parts.proxy_target.size assert_equal 'Deck', @ship.parts[0].name end - + + test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do + @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}] + assert_equal 1, @ship.parts.proxy_target.size + assert_equal 'Mast', @ship.parts[0].name + assert_no_difference("@ship.parts[0].trinkets.proxy_target.size") do + @ship.parts[0].trinkets.proxy_target.size + end + assert_equal 'Ruby', @ship.parts[0].trinkets[0].name + @ship.save + assert_equal 'Ruby', @ship.parts[0].trinkets[0].name + end + test "when grandchild changed in memory, saving parent should save grandchild" do @trinket.name = "changed" @ship.save -- cgit v1.2.3 From 96b2516c3cbcf900f2e84163baba3db7cb0e37d9 Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Wed, 14 Jul 2010 23:13:56 -0400 Subject: Strengthening the test for nested_attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loading the associate target in nested_attributes should load most recent attributes for child records marked for destruction Signed-off-by: José Valim --- activerecord/test/cases/associations_test.rb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 4ae776c35a..ff9e9a472a 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -17,7 +17,8 @@ require 'models/tagging' require 'models/person' require 'models/reader' require 'models/parrot' -require 'models/pirate' +require 'models/ship_part' +require 'models/ship' require 'models/treasure' require 'models/price_estimate' require 'models/club' @@ -29,6 +30,24 @@ class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, :computers, :people, :readers + def test_loading_the_association_target_should_keep_child_records_marked_for_destruction + ship = Ship.create!(:name => "The good ship Dollypop") + part = ship.parts.create!(:name => "Mast") + part.mark_for_destruction + ship.parts.send(:load_target) + assert ship.parts[0].marked_for_destruction? + end + + def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction + ship = Ship.create!(:name => "The good ship Dollypop") + part = ship.parts.create!(:name => "Mast") + part.mark_for_destruction + ShipPart.find(part.id).update_attribute(:name, 'Deck') + ship.parts.send(:load_target) + assert_equal 'Deck', ship.parts[0].name + end + + def test_include_with_order_works assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)} assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)} -- cgit v1.2.3 From 01add55d6a6a67fb487afa040998575111511b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 14:44:46 +0200 Subject: Revert "save on parent should not cascade to child unless child changed [#3353 state:open]" Please check Lighthouse for more information. This reverts commit 4a0d7c1a439c6ad8d35bf514761824e51fa07df2. --- activerecord/lib/active_record/autosave_association.rb | 4 +--- activerecord/test/cases/autosave_association_test.rb | 18 ------------------ 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 6af384367f..7517896235 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -372,9 +372,7 @@ module ActiveRecord if autosave && association.marked_for_destruction? association.destroy elsif autosave != false - if association.new_record? || ( autosave && association.changed? ) - saved = association.save(:validate => !autosave) - end + saved = association.save(:validate => !autosave) if association.new_record? || autosave if association.updated? association_id = association.send(reflection.options[:primary_key] || :id) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 48479bb429..3b89c12a3f 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -632,8 +632,6 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent - #association save method only trigged when association is changed - @ship.pirate.catchphrase = "new catch phrase" # Stub the save method of the @ship.pirate instance to destroy and then raise an exception class << @ship.pirate def save(*args) @@ -882,22 +880,6 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") end - def test_should_not_call_belongs_to_after_save_callbacks_if_no_changes - @ship.attributes = { :name => "Titanic", :pirate_attributes => {:id => @pirate.id} } - #here there are no changes to pirate so if save on ship causes save on pirate - #this callback will fail pirate save.(pirate save shouldn't happen) - @ship.pirate.cancel_save_from_callback = true - @ship.save - assert_equal 'Titanic', @ship.reload.name - end - - def test_should_call_belongs_to_save_if_belongs_to_has_changes - @ship.attributes = { :name => "Titanic", :pirate_attributes => { :catchphrase => 'Jack', :id => @pirate.id} } - @ship.save - assert_equal 'Titanic', @ship.reload.name - assert_equal 'Jack', @pirate.reload.catchphrase - end - def test_should_still_work_without_an_associated_model @pirate.destroy @ship.reload.name = "The Vile Insanity" -- cgit v1.2.3 From 8bb3b634c07a327b9f1c7e21b4d1fcc407737a9f Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 20 Jul 2010 23:52:24 -0400 Subject: Timestamp columns of HABTM join table should record timestamps [#5161 state:resolved] --- .../has_and_belongs_to_many_association.rb | 6 +++- activerecord/lib/active_record/timestamp.rb | 19 ++++++++++-- .../has_and_belongs_to_many_associations_test.rb | 35 +++++++++++++++++++++- activerecord/test/schema/schema.rb | 2 ++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 9ec63e3fca..177d7905c7 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -53,7 +53,11 @@ module ActiveRecord when @reflection.association_foreign_key.to_s attrs[relation[column.name]] = record.id else - if record.has_attribute?(column.name) + if record.send(:all_timestamp_attributes).include?(column.name.to_sym) + if record.record_timestamps + attrs[relation[column.name]] = record.send(:current_time_from_proper_timezone) + end + elsif record.has_attribute?(column.name) value = @owner.send(:quote_value, record[column.name], column) attrs[relation[column.name]] = value unless value.nil? end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 341cc87be5..6c1e376745 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -39,8 +39,9 @@ module ActiveRecord if record_timestamps current_time = current_time_from_proper_timezone - write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil? - write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil? + timestamp_attributes_for_create.each do |column| + write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil? + end timestamp_attributes_for_update_in_model.each do |column| write_attribute(column.to_s, current_time) if self.send(column).nil? @@ -65,7 +66,19 @@ module ActiveRecord end def timestamp_attributes_for_update_in_model #:nodoc: - [:updated_at, :updated_on].select { |elem| respond_to?(elem) } + timestamp_attributes_for_update.select { |elem| respond_to?(elem) } + end + + def timestamp_attributes_for_update #:nodoc: + [:updated_at, :updated_on] + end + + def timestamp_attributes_for_create #:nodoc: + [:created_at, :created_on] + end + + def all_timestamp_attributes #:nodoc: + timestamp_attributes_for_update + timestamp_attributes_for_create end def current_time_from_proper_timezone #:nodoc: diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index d4d3d8e43e..d7e9ca0bcb 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -85,7 +85,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings - def test_should_property_quote_string_primary_keys + def setup_data_for_habtm_case + ActiveRecord::Base.connection.execute('delete from countries_treaties') + country = Country.new(:name => 'India') country.country_id = 'c1' country.save! @@ -93,6 +95,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase treaty = Treaty.new(:name => 'peace') treaty.treaty_id = 't1' country.treaties << treaty + end + + def test_should_property_quote_string_primary_keys + setup_data_for_habtm_case con = ActiveRecord::Base.connection sql = 'select * from countries_treaties' @@ -101,6 +107,33 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 't1', record[1] end + def test_should_record_timestamp_for_join_table + setup_data_for_habtm_case + + con = ActiveRecord::Base.connection + sql = 'select * from countries_treaties' + record = con.select_rows(sql).last + assert_not_nil record[2] + assert_not_nil record[3] + assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2] + assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3] + end + + def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded + begin + Treaty.record_timestamps = false + setup_data_for_habtm_case + + con = ActiveRecord::Base.connection + sql = 'select * from countries_treaties' + record = con.select_rows(sql).last + assert_nil record[2] + assert_nil record[3] + ensure + Treaty.record_timestamps = true + end + end + def test_has_and_belongs_to_many david = Developer.find(1) diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index c4eed256bf..a69e38f414 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -611,6 +611,8 @@ ActiveRecord::Schema.define do create_table :countries_treaties, :force => true, :id => false do |t| t.string :country_id, :null => false t.string :treaty_id, :null => false + t.datetime :created_at + t.datetime :updated_at end except 'SQLite' do -- cgit v1.2.3 From f3e42292a5792f77681a3fbf03bc8cfc75aeb887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 14:50:04 +0200 Subject: Ensure all join table attributes will be in the same timestamp. --- .../has_and_belongs_to_many_association.rb | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 177d7905c7..e61af93d1e 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -45,21 +45,23 @@ module ActiveRecord if @reflection.options[:insert_sql] @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record)) else - relation = Arel::Table.new(@reflection.options[:join_table]) + relation = Arel::Table.new(@reflection.options[:join_table]) + timestamps = record_timestamp_columns(record) + timezone = record.send(:current_time_from_proper_timezone) if timestamps.any? + attributes = columns.inject({}) do |attrs, column| - case column.name.to_s + name = column.name + case name.to_s when @reflection.primary_key_name.to_s - attrs[relation[column.name]] = @owner.id + attrs[relation[name]] = @owner.id when @reflection.association_foreign_key.to_s - attrs[relation[column.name]] = record.id + attrs[relation[name]] = record.id + when *timestamps + attrs[relation[name]] = timezone else - if record.send(:all_timestamp_attributes).include?(column.name.to_sym) - if record.record_timestamps - attrs[relation[column.name]] = record.send(:current_time_from_proper_timezone) - end - elsif record.has_attribute?(column.name) - value = @owner.send(:quote_value, record[column.name], column) - attrs[relation[column.name]] = value unless value.nil? + if record.has_attribute?(name) + value = @owner.send(:quote_value, record[name], column) + attrs[relation[name]] = value unless value.nil? end end attrs @@ -121,6 +123,14 @@ module ActiveRecord build_record(attributes, &block) end end + + def record_timestamp_columns(record) + if record.record_timestamps + record.send(:all_timestamp_attributes).map(&:to_s) + else + [] + end + end end end end -- cgit v1.2.3 From c96a50539121c4a722f354220c7f0e314b24804d Mon Sep 17 00:00:00 2001 From: Thiago Pradi Date: Tue, 20 Jul 2010 14:59:10 -0300 Subject: rake db:seed should check if the database have pending migrations [#5163 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 5024787c3c..2c17c74ab4 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -274,7 +274,7 @@ namespace :db do task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ] desc 'Load the seed data from db/seeds.rb' - task :seed => :environment do + task :seed => 'db:abort_if_pending_migrations' do seed_file = File.join(Rails.root, 'db', 'seeds.rb') load(seed_file) if File.exist?(seed_file) end -- cgit v1.2.3 From 992711a86bc4ddd4460f9067e49eea36b37ca94f Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Sun, 18 Jul 2010 07:54:48 -0400 Subject: update_attribute should not update readonly attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5106 state:resolved] Signed-off-by: José Valim --- activerecord/lib/active_record/persistence.rb | 2 ++ activerecord/test/cases/persistence_test.rb | 8 +++++++- activerecord/test/fixtures/minivans.yml | 1 + activerecord/test/models/minivan.rb | 5 ++++- activerecord/test/schema/schema.rb | 1 + 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 7ec443ccc7..e2d92c860c 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -105,6 +105,8 @@ module ActiveRecord # Updates a single attribute and saves the record without going through the normal validation procedure # or callbacks. This is especially useful for boolean flags on existing records. def update_attribute(name, value) + raise ActiveRecordError, "#{name.to_s} is marked as readonly" if self.class.readonly_attributes.include? name.to_s + changes = record_update_timestamps || {} if name diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 4ea5df0945..54fe991648 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -17,13 +17,14 @@ require 'models/comment' require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' +require 'models/minivan' require 'models/loose_person' require 'rexml/document' require 'active_support/core_ext/exception' class PersistencesTest < ActiveRecord::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans def test_create topic = Topic.new @@ -220,6 +221,11 @@ class PersistencesTest < ActiveRecord::TestCase assert !Topic.find(1).approved? end + def test_update_attribute_for_readonly_attribute + minivan = Minivan.find('m1') + assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } + end + def test_update_attribute_with_one_changed_and_one_updated t = Topic.order('id').limit(1).first title, author_name = t.title, t.author_name diff --git a/activerecord/test/fixtures/minivans.yml b/activerecord/test/fixtures/minivans.yml index e7a2ab77eb..f1224a4c1a 100644 --- a/activerecord/test/fixtures/minivans.yml +++ b/activerecord/test/fixtures/minivans.yml @@ -2,3 +2,4 @@ cool_first: minivan_id: m1 name: my_minivan speedometer_id: s1 + color: blue diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb index c753319a20..602438d16f 100644 --- a/activerecord/test/models/minivan.rb +++ b/activerecord/test/models/minivan.rb @@ -3,4 +3,7 @@ class Minivan < ActiveRecord::Base belongs_to :speedometer has_one :dashboard, :through => :speedometer -end \ No newline at end of file + + attr_readonly :color + +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index a69e38f414..f3fd37cd61 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -300,6 +300,7 @@ ActiveRecord::Schema.define do t.string :minivan_id t.string :name t.string :speedometer_id + t.string :color end create_table :minimalistics, :force => true do |t| -- cgit v1.2.3 From d77c3b669ce052234868b3d8e4f066e1baf9dbd5 Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Mon, 19 Jul 2010 21:26:57 -0400 Subject: eagerly loaded association records should respect default_scope [#2931 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/association_preload.rb | 2 +- activerecord/test/cases/relation_scoping_test.rb | 18 ++++++++++++++++-- activerecord/test/models/person.rb | 1 + activerecord/test/models/reference.rb | 5 +++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 9172ab2a20..08601f8ef9 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -378,7 +378,7 @@ module ActiveRecord :order => preload_options[:order] || options[:order] } - reflection.klass.unscoped.apply_finder_options(find_options).to_a + reflection.klass.scoped.apply_finder_options(find_options).to_a end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index a5a3b3ef38..fdf4536bc0 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -5,6 +5,8 @@ require 'models/developer' require 'models/project' require 'models/comment' require 'models/category' +require 'models/person' +require 'models/reference' class RelationScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects @@ -218,7 +220,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase end class HasManyScopingTest< ActiveRecord::TestCase - fixtures :comments, :posts + fixtures :comments, :posts, :people, :references def setup @welcome = Post.find(1) @@ -250,6 +252,18 @@ class HasManyScopingTest< ActiveRecord::TestCase assert_equal 'a comment...', @welcome.comments.what_are_you end end + + def test_should_maintain_default_scope_on_associations + person = people(:michael) + magician = BadReference.find(1) + assert_equal [magician], people(:michael).bad_references + end + + def test_should_maintain_default_scope_on_eager_loaded_associations + michael = Person.where(:id => people(:michael).id).includes(:bad_references).first + magician = BadReference.find(1) + assert_equal [magician], michael.bad_references + end end class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase @@ -399,4 +413,4 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary end -end \ No newline at end of file +end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 2a73b1ee01..be7463f7c8 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -4,6 +4,7 @@ class Person < ActiveRecord::Base has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null' has_many :references + has_many :bad_references has_many :jobs, :through => :references has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true] has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id' diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index 479e8b72c6..4a17c936f5 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -2,3 +2,8 @@ class Reference < ActiveRecord::Base belongs_to :person belongs_to :job end + +class BadReference < ActiveRecord::Base + self.table_name ='references' + default_scope :conditions => {:favourite => false } +end -- cgit v1.2.3 From 71312443133c6bbcc518d594789f19a213369f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 15:06:23 +0200 Subject: Ensure default_scope can be overwriten by association conditions. --- activerecord/test/cases/relation_scoping_test.rb | 5 +++++ activerecord/test/models/person.rb | 1 + 2 files changed, 6 insertions(+) diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index fdf4536bc0..a50a4d4165 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -259,6 +259,11 @@ class HasManyScopingTest< ActiveRecord::TestCase assert_equal [magician], people(:michael).bad_references end + def test_should_default_scope_on_associations_is_overriden_by_association_conditions + person = people(:michael) + assert_equal [], people(:michael).fixed_bad_references + end + def test_should_maintain_default_scope_on_eager_loaded_associations michael = Person.where(:id => people(:michael).id).includes(:bad_references).first magician = BadReference.find(1) diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index be7463f7c8..951ec93c53 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -5,6 +5,7 @@ class Person < ActiveRecord::Base has_many :references has_many :bad_references + has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference' has_many :jobs, :through => :references has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true] has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id' -- cgit v1.2.3 From 9dfe9fa693c98a828dae6ad96eb9798bbdea7d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 15:17:04 +0200 Subject: Ensure insert_before in middleware stack raises a meaningful error message [#3679 state:resolved] --- actionpack/lib/action_dispatch/middleware/stack.rb | 15 +++++++++++---- actionpack/test/dispatch/middleware_stack_test.rb | 10 ++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 4618f3befc..41078eced7 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -46,7 +46,7 @@ module ActionDispatch end def insert(index, *args, &block) - index = self.index(index) unless index.is_a?(Integer) + index = assert_index(index, :before) middleware = self.class::Middleware.new(*args, &block) super(index, middleware) end @@ -54,9 +54,8 @@ module ActionDispatch alias_method :insert_before, :insert def insert_after(index, *args, &block) - i = index.is_a?(Integer) ? index : self.index(index) - raise "No such middleware to insert after: #{index.inspect}" unless i - insert(i + 1, *args, &block) + index = assert_index(index, :after) + insert(index + 1, *args, &block) end def swap(target, *args, &block) @@ -79,5 +78,13 @@ module ActionDispatch raise "MiddlewareStack#build requires an app" unless app reverse.inject(app) { |a, e| e.build(a) } end + + protected + + def assert_index(index, where) + i = index.is_a?(Integer) ? index : self.index(index) + raise "No such middleware to insert #{where}: #{index.inspect}" unless i + i + end end end diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb index 170c5b8565..6a1a4f556f 100644 --- a/actionpack/test/dispatch/middleware_stack_test.rb +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -66,6 +66,16 @@ class MiddlewareStackTest < ActiveSupport::TestCase assert_equal BazMiddleware, @stack[0].klass end + test "raise an error on invalid index" do + assert_raise RuntimeError do + @stack.insert("HiyaMiddleware", BazMiddleware) + end + + assert_raise RuntimeError do + @stack.insert_after("HiyaMiddleware", BazMiddleware) + end + end + test "lazy evaluates middleware class" do assert_difference "@stack.size" do @stack.use "MiddlewareStackTest::BazMiddleware" -- cgit v1.2.3 From e13e8dcf44fd27332ecd4e5c943d3d09efb58c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 21 Jul 2010 15:23:07 +0200 Subject: Use capture instead of yield in link_to_unless. [#5162 state:resolved] --- actionpack/lib/action_view/helpers/url_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 0e3cc54fd1..b493a0cb0e 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -398,7 +398,7 @@ module ActionView def link_to_unless(condition, name, options = {}, html_options = {}, &block) if condition if block_given? - block.arity <= 1 ? yield(name) : yield(name, options, html_options) + block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) else name end -- cgit v1.2.3 From 659e3b02ab98c8f043dfb02d997758d122034181 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 20 Jul 2010 09:51:06 -0400 Subject: renaming tests by removing proxy_options from names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/named_scope_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index dc85b395d3..7c037b20c5 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -270,27 +270,27 @@ class NamedScopeTest < ActiveRecord::TestCase assert Topic.base.many? end - def test_should_build_with_proxy_options + def test_should_build_on_top_of_named_scope topic = Topic.approved.build({}) assert topic.approved end - def test_should_build_new_with_proxy_options + def test_should_build_new_on_top_of_named_scope topic = Topic.approved.new assert topic.approved end - def test_should_create_with_proxy_options + def test_should_create_on_top_of_named_scope topic = Topic.approved.create({}) assert topic.approved end - def test_should_create_with_bang_with_proxy_options + def test_should_create_with_bang_on_top_of_named_scope topic = Topic.approved.create!({}) assert topic.approved end - def test_should_build_with_proxy_options_chained + def test_should_build_on_top_of_chained_named_scopes topic = Topic.approved.by_lifo.build({}) assert topic.approved assert_equal 'lifo', topic.author_name -- cgit v1.2.3 From 24c0bc52d260db4146525c6720be0f9be2c5fd0f Mon Sep 17 00:00:00 2001 From: Jeroen van Dijk Date: Wed, 21 Jul 2010 17:10:24 +0200 Subject: Mention that ActionMailer::Base.default_url_options is now deprecated --- actionmailer/lib/action_mailer/base.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 7f2ed5ba64..7bbde53306 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -125,14 +125,13 @@ module ActionMailer #:nodoc: # make sense to generate relative URLs in email messages. # # It is also possible to set a default host that will be used in all mailers by setting the :host - # option in the ActionMailer::Base.default_url_options hash as follows: - # - # ActionMailer::Base.default_url_options[:host] = "example.com" - # - # This can also be set as a configuration option in config/application.rb: + # option as a configuration option in config/application.rb: # # config.action_mailer.default_url_options = { :host => "example.com" } # + # Setting ActionMailer::Base.default_url_options directly is now deprecated, use the configuration + # option mentioned above to set the default host. + # # If you do decide to set a default :host for your mailers you will want to use the # :only_path => false option when using url_for. This will ensure that absolute URLs are # generated because the url_for view helper will, by default, generate relative URLs when a -- cgit v1.2.3 From 79d6f314c68a4d833a47d318b392808d530925c4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 21 Jul 2010 11:02:13 -0300 Subject: We are doing the same in this conditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- railties/lib/rails/configuration.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 0becb780de..74b2bcaeb1 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -56,9 +56,7 @@ module Rails return @options[method] if args.empty? - if method == :rails - namespace, configuration = :rails, args.shift - elsif args.first.is_a?(Hash) + if method == :rails || args.first.is_a?(Hash) namespace, configuration = method, args.shift else namespace, configuration = args.shift, args.shift -- cgit v1.2.3 From e107c208f0422b83df473666e30256f837c263eb Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 21 Jul 2010 11:37:03 -0300 Subject: Make config.generators accept string namespaces, you can do now config.generators.test_framework 'rspec' for instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- railties/lib/rails/configuration.rb | 1 + railties/test/application/generators_test.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 74b2bcaeb1..e5af12b901 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -60,6 +60,7 @@ module Rails namespace, configuration = method, args.shift else namespace, configuration = args.shift, args.shift + namespace = namespace.to_sym if namespace.respond_to?(:to_sym) @options[:rails][method] = namespace end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index cbf0decd07..d258625f42 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -103,5 +103,20 @@ module ApplicationTests assert_equal({ :plugin => { :generator => "-g" } }, c.generators.aliases) end end + + test "generators with string and hash for options should generate symbol keys" do + with_bare_config do |c| + c.generators do |g| + g.orm 'datamapper', :migration => false + end + + expected = { + :rails => { :orm => :datamapper }, + :datamapper => { :migration => false } + } + + assert_equal expected, c.generators.options + end + end end end -- cgit v1.2.3 From ef5ae60a07c7d45855a9a2a4b695f153ef9faa79 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 21 Jul 2010 11:04:21 -0300 Subject: Make use of tap to return a previously used var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../lib/active_record/associations/association_collection.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 692badcc5c..f346a19a3a 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -397,10 +397,10 @@ module ActiveRecord @target = find_target.map do |f| i = @target.index(f) if i - t = @target.delete_at(i) - keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names) - t.attributes = f.attributes.except(*keys) - t + @target.delete_at(i).tap do |t| + keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names) + t.attributes = f.attributes.except(*keys) + end else f end -- cgit v1.2.3 From a1a41a3939318e9481469ea194660f3d8a9c1e66 Mon Sep 17 00:00:00 2001 From: Andrew Kaspick Date: Sat, 17 Jul 2010 17:25:08 -0500 Subject: remote_function patch with more detailed test Signed-off-by: wycats --- actionpack/lib/action_view/helpers/prototype_helper.rb | 2 +- actionpack/test/template/prototype_helper_test.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 5c0ff5d59c..28b8a27eef 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -139,7 +139,7 @@ module ActionView function = "if (#{options[:condition]}) { #{function}; }" if options[:condition] function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm] - return function + return function.html_safe end # All the methods were moved to GeneratorMethods so that diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index 0ff37f44c2..036a44730c 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -104,6 +104,12 @@ class PrototypeHelperTest < PrototypeHelperBaseTest assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block) end + def test_remote_function + res = remote_function(:url => authors_path, :with => "'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')") + assert_equal "new Ajax.Request('/authors', {asynchronous:true, evalScripts:true, parameters:'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')})", res + assert res.html_safe? + end + protected def author_path(record) "/authors/#{record.id}" -- cgit v1.2.3 From 680f0459a3a4fe59b215d26c2de03f963c2c29ee Mon Sep 17 00:00:00 2001 From: Wincent Colaiuta Date: Wed, 21 Jul 2010 19:21:05 +0200 Subject: doc: form_for does return output rather than merely evaluate its block --- actionpack/lib/action_view/helpers/form_helper.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 711c455ded..b8c163e1c2 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -150,10 +150,6 @@ module ActionView # here a named route directly as well. Defaults to the current action. # * :html - Optional HTML attributes for the form tag. # - # Worth noting is that the +form_for+ tag is called in a ERb evaluation - # block, not an ERb output block. So that's <% %>, not - # <%= %>. - # # Also note that +form_for+ doesn't create an exclusive scope. It's still # possible to use both the stand-alone FormHelper methods and methods # from FormTagHelper. For example: -- cgit v1.2.3 From d16c5cc99b4ac5a5517b643aabb3b31bf0f0f1b6 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 01:00:01 +0800 Subject: Change some missing README -> README.rdoc --- actionmailer/actionmailer.gemspec | 2 +- actionpack/actionpack.gemspec | 2 +- activemodel/activemodel.gemspec | 2 +- activerecord/activerecord.gemspec | 6 +++--- activeresource/activeresource.gemspec | 6 +++--- activesupport/activesupport.gemspec | 2 +- railties/lib/rails/tasks/documentation.rake | 16 ++++++++-------- railties/railties.gemspec | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index fa0ee778c9..daf30e434a 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'actionmailer' - s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 9ce7ba5f02..99deff234c 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'actionpack' - s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 6bd6fe49ff..c483ecbc3c 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'activemodel' - s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' s.has_rdoc = true diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index ce10404feb..67d521d56b 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -14,12 +14,12 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'activerecord' - s.files = Dir['CHANGELOG', 'README', 'examples/**/*', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*'] s.require_path = 'lib' s.has_rdoc = true - s.extra_rdoc_files = %w( README ) - s.rdoc_options.concat ['--main', 'README'] + s.extra_rdoc_files = %w( README.rdoc ) + s.rdoc_options.concat ['--main', 'README.rdoc'] s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) diff --git a/activeresource/activeresource.gemspec b/activeresource/activeresource.gemspec index ca74c0dd7d..a71168722b 100644 --- a/activeresource/activeresource.gemspec +++ b/activeresource/activeresource.gemspec @@ -14,12 +14,12 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'activeresource' - s.files = Dir['CHANGELOG', 'README', 'examples/**/*', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*'] s.require_path = 'lib' s.has_rdoc = true - s.extra_rdoc_files = %w( README ) - s.rdoc_options.concat ['--main', 'README'] + s.extra_rdoc_files = %w( README.rdoc ) + s.rdoc_options.concat ['--main', 'README.rdoc'] s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 8611a1e5fa..df7f68fecf 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'activesupport' - s.files = Dir['CHANGELOG', 'README', 'lib/**/*'] + s.files = Dir['CHANGELOG', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' s.has_rdoc = true diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 492f05e3cc..843d2b4e82 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -55,46 +55,46 @@ namespace :doc do rdoc.template = "#{ENV['template']}.rb" if ENV['template'] rdoc.title = "Rails Framework Documentation" rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('README.rdoc') gem_path('actionmailer') do |actionmailer| - %w(README CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file| + %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file| rdoc.rdoc_files.include("#{actionmailer}/#{file}") end end gem_path('actionpack') do |actionpack| - %w(README CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file| + %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file| rdoc.rdoc_files.include("#{actionpack}/#{file}") end end gem_path('activemodel') do |activemodel| - %w(README CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file| + %w(README.rdoc CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file| rdoc.rdoc_files.include("#{activemodel}/#{file}") end end gem_path('activerecord') do |activerecord| - %w(README CHANGELOG lib/active_record/**/*.rb).each do |file| + %w(README.rdoc CHANGELOG lib/active_record/**/*.rb).each do |file| rdoc.rdoc_files.include("#{activerecord}/#{file}") end end gem_path('activeresource') do |activeresource| - %w(README CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file| + %w(README.rdoc CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file| rdoc.rdoc_files.include("#{activeresource}/#{file}") end end gem_path('activesupport') do |activesupport| - %w(README CHANGELOG lib/active_support/**/*.rb).each do |file| + %w(README.rdoc CHANGELOG lib/active_support/**/*.rb).each do |file| rdoc.rdoc_files.include("#{activesupport}/#{file}") end end gem_path('railties') do |railties| - %w(README CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file| + %w(README.rdoc CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file| rdoc.rdoc_files.include("#{railties}/#{file}") end end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 247b926af9..38dcb17ff1 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'rails' - s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] + s.files = Dir['CHANGELOG', 'README.rdoc', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] s.require_path = 'lib' s.rdoc_options << '--exclude' << '.' -- cgit v1.2.3 From 622092d33e8d326217ab1ed6138e2c572c95b8ba Mon Sep 17 00:00:00 2001 From: Brian Rose Date: Fri, 16 Jul 2010 15:26:21 -0600 Subject: Fixed a globbed route issue where slashes were being escaped, causing assert_routing to fail. [#5135 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_dispatch/routing/route_set.rb | 3 ++- actionpack/test/controller/test_test.rb | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 36c52eb65a..a9b97a17eb 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -414,7 +414,8 @@ module ActionDispatch elsif value.is_a?(Array) value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') else - Rack::Mount::Utils.escape_uri(value.to_param) + return nil unless param = value.to_param + param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/") end end {:parameterize => parameterize} diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index f9fc7a0976..950ad9266f 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -461,6 +461,13 @@ XML def test_assert_routing_in_module assert_routing 'admin/user', :controller => 'admin/user', :action => 'index' end + + def test_assert_routing_with_glob + with_routing do |set| + set.draw { |map| match('*path' => "pages#show") } + assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' }) + end + end def test_params_passing get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'} -- cgit v1.2.3 From b0c7dee4f2744286069c5ff4e65c3d081a4cb24a Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Mon, 19 Jul 2010 22:53:54 -0400 Subject: removing unused models from tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5153 state:resolved] Signed-off-by: José Valim --- .../test/cases/associations/belongs_to_associations_test.rb | 2 -- activerecord/test/cases/associations/callbacks_test.rb | 2 -- .../test/cases/associations/cascaded_eager_loading_test.rb | 1 - .../associations/has_and_belongs_to_many_associations_test.rb | 6 ------ activerecord/test/cases/associations_test.rb | 11 ----------- activerecord/test/cases/finder_test.rb | 1 - activerecord/test/cases/method_scoping_test.rb | 3 +-- activerecord/test/cases/persistence_test.rb | 7 ------- activerecord/test/cases/query_cache_test.rb | 2 -- activerecord/test/cases/relations_test.rb | 1 - activerecord/test/cases/transaction_callbacks_test.rb | 1 - .../validations/i18n_generate_message_validation_test.rb | 1 - activerecord/test/cases/validations_test.rb | 5 ----- activerecord/test/cases/xml_serialization_test.rb | 1 - 14 files changed, 1 insertion(+), 43 deletions(-) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index fb1e6e7e70..4d5769d173 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -5,8 +5,6 @@ require 'models/company' require 'models/topic' require 'models/reply' require 'models/computer' -require 'models/customer' -require 'models/order' require 'models/post' require 'models/author' require 'models/tag' diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 91b1af125e..15537d6940 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -1,8 +1,6 @@ require "cases/helper" require 'models/post' -require 'models/comment' require 'models/author' -require 'models/category' require 'models/project' require 'models/developer' diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index f5d59c9a43..b93e49613d 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -2,7 +2,6 @@ require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' -require 'models/category' require 'models/categorization' require 'models/company' require 'models/topic' diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index d7e9ca0bcb..6b4a1d9408 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -2,20 +2,14 @@ require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' -require 'models/topic' -require 'models/reply' -require 'models/computer' require 'models/customer' require 'models/order' require 'models/categorization' require 'models/category' require 'models/post' require 'models/author' -require 'models/comment' require 'models/tag' require 'models/tagging' -require 'models/person' -require 'models/reader' require 'models/parrot' require 'models/pirate' require 'models/treasure' diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index ff9e9a472a..a1c794c084 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -2,11 +2,6 @@ require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' -require 'models/topic' -require 'models/reply' -require 'models/computer' -require 'models/customer' -require 'models/order' require 'models/categorization' require 'models/category' require 'models/post' @@ -19,12 +14,6 @@ require 'models/reader' require 'models/parrot' require 'models/ship_part' require 'models/ship' -require 'models/treasure' -require 'models/price_estimate' -require 'models/club' -require 'models/member' -require 'models/membership' -require 'models/sponsor' class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 860d330a7f..a107c1a474 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -10,7 +10,6 @@ require 'models/entrant' require 'models/project' require 'models/developer' require 'models/customer' -require 'models/job' class DynamicFinderMatchTest < ActiveRecord::TestCase def test_find_no_match diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 4e8ce1dac1..774b50e2e4 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -8,7 +8,6 @@ require 'models/author' require 'models/developer' require 'models/project' require 'models/comment' -require 'models/category' class MethodScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects @@ -543,4 +542,4 @@ class NestedScopingTest < ActiveRecord::TestCase assert_equal 1, scoped_authors.size assert_equal authors(:david).attributes, scoped_authors.first.attributes end -end \ No newline at end of file +end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 54fe991648..1cc3a337c3 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -5,15 +5,8 @@ require 'models/topic' require 'models/reply' require 'models/category' require 'models/company' -require 'models/customer' require 'models/developer' require 'models/project' -require 'models/default' -require 'models/auto_id' -require 'models/column_name' -require 'models/subscriber' -require 'models/keyboard' -require 'models/comment' require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 68abca70b3..f0d97a00d0 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -1,8 +1,6 @@ require "cases/helper" require 'models/topic' -require 'models/reply' require 'models/task' -require 'models/course' require 'models/category' require 'models/post' diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index ffde8daa07..cb252d56fe 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'models/tag' require 'models/tagging' require 'models/post' require 'models/topic' diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index df123c9de8..ffc2cd638f 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -1,6 +1,5 @@ require "cases/helper" require 'models/topic' -require 'models/reply' class TransactionCallbacksTest < ActiveRecord::TestCase self.use_transactional_fixtures = false diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 454e42ed37..628029f8df 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -1,6 +1,5 @@ require "cases/helper" require 'models/topic' -require 'models/reply' class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 3f1b0e333f..fd771ef4be 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -4,11 +4,6 @@ require 'models/topic' require 'models/reply' require 'models/person' require 'models/developer' -require 'models/warehouse_thing' -require 'models/guid' -require 'models/owner' -require 'models/pet' -require 'models/event' require 'models/parrot' require 'models/company' diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 751946ffc5..b11b340e94 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -2,7 +2,6 @@ require "cases/helper" require 'models/contact' require 'models/post' require 'models/author' -require 'models/tagging' require 'models/comment' require 'models/company_in_module' -- cgit v1.2.3 From b2818b24725e5bc468af8ef870c21f4098b20822 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 22 Jul 2010 00:16:26 +0200 Subject: routing guide: a "photo" resource has by convention paths under "photos", in plural --- railties/guides/source/routing.textile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 72a76e25bb..ae80ba77e4 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -441,13 +441,13 @@ h4. Segment Constraints You can use the +:constraints+ option to enforce a format for a dynamic segment: -match 'photo/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ } +match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ } -This route would match URLs such as +/photo/A12345+. You can more succinctly express the same route this way: +This route would match URLs such as +/photos/A12345+. You can more succinctly express the same route this way: -match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/ +match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ +:constraints+ takes regular expression. However note that regexp anchors can't be used within constraints. For example following route will not work: @@ -472,7 +472,7 @@ You can also constrain a route based on any method on the {:subdomain => "admin"} +match "photos", :constraints => {:subdomain => "admin"}
You can also specify constrains in a block form: @@ -511,10 +511,10 @@ h4. Route Globbing Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example -match 'photo/*other' => 'photos#unknown' +match 'photos/*other' => 'photos#unknown' -This route would match +photo/12+ or +/photo/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+. +This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+. h4. Redirection -- cgit v1.2.3 From 5c858220085dc4ddc1bec496747059dfbe32f1da Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 05:08:34 +0800 Subject: Hash#to_param is doesn't use sort anymore, some tests added for Hash#to_param --- .../lib/active_support/core_ext/object/to_param.rb | 2 +- activesupport/test/core_ext/hash_ext_test.rb | 25 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 06f077e920..f2e7c2351e 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -44,6 +44,6 @@ class Hash def to_param(namespace = nil) collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort * '&' + end * '&' end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 7b2c10908f..f369f55675 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -2,6 +2,8 @@ require 'abstract_unit' require 'active_support/core_ext/hash' require 'bigdecimal' require 'active_support/core_ext/string/access' +require 'active_support/ordered_hash' +require 'active_support/core_ext/object/conversions' class HashExtTest < Test::Unit::TestCase def setup @@ -449,6 +451,29 @@ class IWriteMyOwnXML end end +class HashExtToParamTests < Test::Unit::TestCase + class ToParam < String + def to_param + "#{self}-1" + end + end + + def test_string_hash + assert_equal '', {}.to_param + assert_equal 'hello=world', { :hello => "world" }.to_param + assert_equal 'hello=10', { "hello" => 10 }.to_param + assert_equal 'hello=world&say_bye=true', ActiveSupport::OrderedHash[:hello, "world", "say_bye", true].to_param + end + + def test_number_hash + assert_equal '10=20&30=40&50=60', ActiveSupport::OrderedHash[10, 20, 30, 40, 50, 60].to_param + end + + def test_to_param_hash + assert_equal 'custom=param-1&custom2=param2-1', ActiveSupport::OrderedHash[ToParam.new('custom'), ToParam.new('param'), ToParam.new('custom2'), ToParam.new('param2')].to_param + end +end + class HashToXmlTest < Test::Unit::TestCase def setup @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 } -- cgit v1.2.3 From 6cbd085f692aae7518ac67380e805ebb65896951 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 06:16:27 +0800 Subject: Test Hash#to_param escapes keys and values [#5175] --- activesupport/test/core_ext/hash_ext_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index f369f55675..5d9846a216 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -472,6 +472,10 @@ class HashExtToParamTests < Test::Unit::TestCase def test_to_param_hash assert_equal 'custom=param-1&custom2=param2-1', ActiveSupport::OrderedHash[ToParam.new('custom'), ToParam.new('param'), ToParam.new('custom2'), ToParam.new('param2')].to_param end + + def test_to_param_hash_escapes_its_keys_and_values + assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param + end end class HashToXmlTest < Test::Unit::TestCase -- cgit v1.2.3 From 5cba6635787ed65a420d48ea0e93ff4af2ba59f2 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 22 Jul 2010 00:32:39 +0200 Subject: routing guide: say "path" when you mean path --- railties/guides/source/routing.textile | 72 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index ae80ba77e4..7e6d6b5b34 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -5,14 +5,14 @@ This guide covers the user-facing features of Rails routing. By referring to thi * Understand the code in +routes.rb+ * Construct your own routes, using either the preferred resourceful style or with the @match@ method * Identify what parameters to expect an action to receive -* Automatically create URLs using route helpers +* Automatically create paths and URLs using route helpers * Use advanced techniques such as constraints and Rack endpoints endprologue. h3. The Purpose of the Rails Router -The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate URLs, avoiding the need to hardcode URL strings in your views. +The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views. h4. Connecting URLs to Code @@ -30,9 +30,9 @@ match "/patients/:id" => "patients#show" the request is dispatched to the +patients+ controller's +show+ action with { :id => "17" } in +params+. -h4. Generating URLs from Code +h4. Generating Paths and URLs from Code -You can also generate URLs. If your application contains this code: +You can also generate paths and URLs. If your application contains this code: @patient = Patient.find(17) @@ -76,7 +76,7 @@ resources :photos creates seven different routes in your application, all mapping to the +Photos+ controller: -|_. Verb |_.URL |_.action |_.used for| +|_. Verb |_.Path |_.action |_.used for| |GET |/photos |index |display a list of all photos| |GET |/photos/new |new |return an HTML form for creating a new photo| |POST |/photos |create |create a new photo| @@ -85,7 +85,7 @@ creates seven different routes in your application, all mapping to the +Photos+ |PUT |/photos/:id |update |update a specific photo| |DELETE |/photos/:id |destroy |delete a specific photo| -h4. URLs and Paths +h4. Paths and URLs Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of +resources :photos+: @@ -130,7 +130,7 @@ resource :geocoder creates six different routes in your application, all mapping to the +Geocoders+ controller: -|_. Verb |_.URL |_.action |_.used for| +|_. Verb |_.Path |_.action |_.used for| |GET |/geocoder/new |new |return an HTML form for creating the geocoder| |POST |/geocoder |create |create the new geocoder| |GET |/geocoder |show |display the one and only geocoder resource| @@ -160,7 +160,7 @@ end This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create: -|_. Verb |_.URL |_.action |_. helper | +|_. Verb |_.Path |_.action |_. helper | |GET |/admin/photos |index | admin_photos_path | |GET |/admin/photos/new |new | new_admin_photos_path | |POST |/admin/photos |create | admin_photos_path | @@ -197,16 +197,16 @@ or, for a single case resources :posts, :path => "/admin" -In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following URLs map to +PostsController+: +In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following paths map to +PostsController+: -|_. Verb |_.URL |_.action |_. helper | -|GET |photos |index | photos_path | -|GET |photos/new |new | photos_path | -|POST |photos |create | photos_path | -|GET |photos/1 |show | photo_path(id) | -|GET |photos/1/edit |edit | edit_photo_path(id) | -|PUT |photos/1 |update | photo_path(id) | -|DELETE |photos/1 |destroy | photo_path(id) | +|_. Verb |_.Path |_.action |_. helper | +|GET |/admin/photos |index | photos_path | +|GET |/admin/photos/new |new | photos_path | +|POST |/admin/photos |create | photos_path | +|GET |/admin/photos/1 |show | photo_path(id) | +|GET |/admin/photos/1/edit |edit | edit_photo_path(id) | +|PUT |/admin/photos/1 |update | photo_path(id) | +|DELETE |/admin/photos/1 |destroy | photo_path(id) | h4. Nested Resources @@ -232,7 +232,7 @@ end In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine: -|_.Verb |_.URL |_.action |_.used for| +|_.Verb |_.Path |_.action |_.used for| |GET |/magazines/1/ads |index |display a list of all ads for a specific magazine| |GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine| |POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine| @@ -256,7 +256,7 @@ resources :publishers do end -Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize URLs such as +Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize paths such as
 /publishers/1/magazines/2/photos/3
@@ -266,9 +266,9 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin
 
 TIP: _Resources should never be nested more than 1 level deep._
 
-h4. Creating URLs From Objects
+h4. Creating Paths and URLs From Objects
 
-In addition to using the routing helpers, Rails can also create URLs from an array of parameters. For example, suppose you have this set of routes:
+In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters. For example, suppose you have this set of routes:
 
 
 resources :magazines do
@@ -340,7 +340,7 @@ resources :photos do
 end
 
 
-This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
+This will enable Rails to recognize paths such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
 
 Just as with member routes, you can pass +:on+ to a route:
 
@@ -380,7 +380,7 @@ You can set up as many dynamic segments within a regular route as you like. Anyt
 match ':controller/:action/:id/:user_id'
 
 
-An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
+An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
 
 NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
 
@@ -396,7 +396,7 @@ You can specify static segments when creating a route:
 match ':controller/:action/:id/with_user/:user_id'
 
 
-This route would respond to URLs such as +/photos/show/1/with_user/2+. In this case, +params+ would be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
+This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
 
 h4. The Query String
 
@@ -406,7 +406,7 @@ The +params+ will also include any parameters from the query string. For example
 match ':controller/:action/:id'
 
 
-An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
+An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
 
 h4. Defining Defaults
 
@@ -416,7 +416,7 @@ You do not need to explicitly use the +:controller+ and +:action+ symbols within
 match 'photos/:id' => 'photos#show'
 
 
-With this route, Rails will match an incoming URL of +/photos/12+ to the +show+ action of  +PhotosController+.
+With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of  +PhotosController+.
 
 You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
 
@@ -444,7 +444,7 @@ You can use the +:constraints+ option to enforce a format for a dynamic segment:
 match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
 
 
-This route would match URLs such as +/photos/A12345+. You can more succinctly express the same route this way:
+This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way:
 
 
 match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
@@ -573,9 +573,9 @@ The +:controller+ option lets you explicitly specify a controller to use for the
 resources :photos, :controller => "images"
 
 
-will recognize incoming URLs beginning with +/photo+ but route to the +Images+ controller:
+will recognize incoming paths beginning with +/photo+ but route to the +Images+ controller:
 
-|_. Verb |_.URL          |_.action |
+|_. Verb |_.Path         |_.action |
 |GET     |/photos        |index    |
 |GET     |/photos/new    |new      |
 |POST    |/photos        |create   |
@@ -584,7 +584,7 @@ will recognize incoming URLs beginning with +/photo+ but route to the +Images+ c
 |PUT     |/photos/1      |update   |
 |DELETE  |/photos/1      |destroy  |
 
-NOTE: Use +photos_path+, +new_photos_path+, etc. to generate URLs for this resource.
+NOTE: Use +photos_path+, +new_photos_path+, etc. to generate paths for this resource.
 
 h4. Specifying Constraints
 
@@ -615,9 +615,9 @@ The +:as+ option lets you override the normal naming for the named route helpers
 resources :photos, :as => "images"
 
 
-will recognize incoming URLs beginning with +/photos+ and route the requests to +PhotosController+:
+will recognize incoming paths beginning with +/photos+ and route the requests to +PhotosController+:
 
-|_.HTTP verb|_.URL           |_.action |_.named helper   |
+|_.HTTP verb|_.Path          |_.action |_.named helper   |
 |GET        |/photos         |index    | images_path     |
 |GET        |/photos/new     |new      | new_image_path  |
 |POST       |/photos         |create   | images_path     |
@@ -628,20 +628,20 @@ will recognize incoming URLs beginning with +/photos+ and route the requests to
 
 h4. Overriding the +new+ and +edit+ Segments
 
-The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
+The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in paths:
 
 
 resources :photos, :path_names => { :new => 'make', :edit => 'change' }
 
 
-This would cause the routing to recognize URLs such as
+This would cause the routing to recognize paths such as
 
 
 /photos/make
 /photos/1/change
 
 
-NOTE: The actual action names aren't changed by this option. The two URLs shown would still route to the new and edit actions.
+NOTE: The actual action names aren't changed by this option. The two paths shown would still route to the +new+ and +edit+ actions.
 
 TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope:
 
@@ -709,7 +709,7 @@ end
 
 Rails now creates routes to the +CategoriesControlleR+.
 
-|_.HTTP verb|_.URL                      |_.action |
+|_.HTTP verb|_.Path                     |_.action |
 |GET        |/kategorien                |index    |
 |GET        |/kategorien/neu            |new      |
 |POST       |/kategorien                |create   |
-- 
cgit v1.2.3


From 56669ec3048de316918ec5ad554fff83d757911b Mon Sep 17 00:00:00 2001
From: Xavier Noria 
Date: Thu, 22 Jul 2010 01:27:02 +0200
Subject: camelize and underscore are sort of inverse of each other, but not in
 a mathematical sense [#5174 state:resolved]

---
 activesupport/lib/active_support/inflector/methods.rb        | 12 +++++++++++-
 .../guides/source/active_support_core_extensions.textile     |  6 +++++-
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index b3dc5b2f3a..de49750083 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -20,6 +20,11 @@ module ActiveSupport
     #   "active_record".camelize(:lower)        # => "activeRecord"
     #   "active_record/errors".camelize         # => "ActiveRecord::Errors"
     #   "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+    #
+    # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
+    # though there are cases where that does not hold:
+    #
+    #   "SSLError".underscore.camelize # => "SslError"
     def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
       if first_letter_in_uppercase
         lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
@@ -28,13 +33,18 @@ module ActiveSupport
       end
     end
 
-    # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
+    # Makes an underscored, lowercase form from the expression in the string.
     #
     # Changes '::' to '/' to convert namespaces to paths.
     #
     # Examples:
     #   "ActiveRecord".underscore         # => "active_record"
     #   "ActiveRecord::Errors".underscore # => active_record/errors
+    #
+    # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
+    # though there are cases where that does not hold:
+    #
+    #   "SSLError".underscore.camelize # => "SslError"
     def underscore(camel_cased_word)
       word = camel_cased_word.to_s.dup
       word.gsub!(/::/, '/')
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index a0ed8d6a90..297dad2ccc 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -1469,13 +1469,15 @@ end
 
 That may be handy to compute method names in a language that follows that convention, for example JavaScript.
 
+INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: "SSLError".underscore.camelize gives back "SslError".
+
 +camelize+ is aliased to +camelcase+.
 
 NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
 
 h5. +underscore+
 
-The method +underscore+ is the inverse of +camelize+, explained above:
+The method +underscore+ goes the other way around, from camel case to paths:
 
 
 "Product".underscore   # => "product"
@@ -1508,6 +1510,8 @@ def load_missing_constant(from_mod, const_name)
 end
 
 
+INFO: As a rule of thumb you can think of +underscore+ as the inverse of +camelize+, though there are cases where that does not hold. For example, "SSLError".underscore.camelize gives back "SslError".
+
 NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
 
 h5. +titleize+
-- 
cgit v1.2.3


From aa2235be7bad43fcd5c314b76811d736b4084c93 Mon Sep 17 00:00:00 2001
From: Neeraj Singh 
Date: Sun, 18 Jul 2010 18:02:40 -0400
Subject: replacing around with for in the comments for callbacks

---
 activemodel/lib/active_model/callbacks.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index e7aad17021..8c10c54b54 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -19,7 +19,7 @@ module ActiveModel
   # 
   #   define_model_callbacks :create, :update
   # 
-  # This will provide all three standard callbacks (before, around and after) around
+  # This will provide all three standard callbacks (before, around and after) for
   # both the :create and :update methods. To implement, you need to wrap the methods 
   # you want callbacks on in a block so that the callbacks get a chance to fire:
   # 
-- 
cgit v1.2.3


From efdfcf13257769cf2d6f9ad85c8538e64007d97b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?= 
Date: Mon, 19 Jul 2010 12:06:19 +0200
Subject: correct typos in Routing examples

---
 actionpack/lib/action_dispatch/routing.rb | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index b7e09b99d1..683dd72555 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -105,7 +105,7 @@ module ActionDispatch
   # You can specify a regular expression to define a format for a parameter.
   #
   #   controller 'geocode' do
-  #     match 'geocode/:postalcode' => :show', :constraints => {
+  #     match 'geocode/:postalcode' => :show, :constraints => {
   #       :postalcode => /\d{5}(-\d{4})?/
   #     }
   #
@@ -113,13 +113,13 @@ module ActionDispatch
   # expression modifiers:
   #
   #   controller 'geocode' do
-  #     match 'geocode/:postalcode' => :show', :constraints => {
+  #     match 'geocode/:postalcode' => :show, :constraints => {
   #       :postalcode => /hx\d\d\s\d[a-z]{2}/i
   #     }
   #   end
   #
   #   controller 'geocode' do
-  #     match 'geocode/:postalcode' => :show', :constraints => {
+  #     match 'geocode/:postalcode' => :show, :constraints => {
   #       :postalcode => /# Postcode format
   #          \d{5} #Prefix
   #          (-\d{4})? #Suffix
-- 
cgit v1.2.3


From 5c137939a7b9d32a46eb07c90d6b535c7c67db64 Mon Sep 17 00:00:00 2001
From: Neeraj Singh 
Date: Tue, 20 Jul 2010 06:56:20 -0400
Subject: expanded comment for update_attribute method

---
 activerecord/lib/active_record/persistence.rb | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index e2d92c860c..b587abd5d0 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -102,8 +102,15 @@ module ActiveRecord
       became
     end
 
-    # Updates a single attribute and saves the record without going through the normal validation procedure
-    # or callbacks. This is especially useful for boolean flags on existing records.
+    # Updates a single attribute and saves the record.  
+    # This is especially useful for boolean flags on existing records. Also note that
+    #
+    # * validation is skipped
+    # * No callbacks are invoked 
+    # * updated_at/updated_on column is updated if that column is available
+    # * does not work on associations
+    # * does not work on attr_accessor attributes. The attribute that is being updated must be column name.
+    #
     def update_attribute(name, value)
       raise ActiveRecordError, "#{name.to_s} is marked as readonly" if self.class.readonly_attributes.include? name.to_s
 
-- 
cgit v1.2.3


From 402aaa56f5cd4470b9b8032d715e6fb0bdb1bf73 Mon Sep 17 00:00:00 2001
From: Jaime Iniesta 
Date: Tue, 20 Jul 2010 14:22:19 +0200
Subject: Active Record Validations and Callbacks guide: Fixed typos and
 rephrased some paragraphs for clarity

---
 .../active_record_validations_callbacks.textile    | 23 +++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 84c33e34f9..be9917868f 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -1,22 +1,22 @@
 h2. Active Record Validations and Callbacks
 
-This guide teaches you how to hook into the lifecycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object lifecycle.
+This guide teaches you how to hook into the life cycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.
 
 After reading this guide and trying out the presented concepts, we hope that you'll be able to:
 
-* Understand the lifecycle of Active Record objects
+* Understand the life cycle of Active Record objects
 * Use the built-in Active Record validation helpers
 * Create your own custom validation methods
 * Work with the error messages generated by the validation process
-* Create callback methods that respond to events in the object lifecycle
+* Create callback methods that respond to events in the object life cycle
 * Create special classes that encapsulate common behavior for your callbacks
-* Create Observers that respond to lifecycle events outside of the original class
+* Create Observers that respond to life cycle events outside of the original class
 
 endprologue.
 
-h3. The Object Lifecycle
+h3. The Object Life Cycle
 
-During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object lifecycle so that you can control your application and its data. 
+During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object life cycle so that you can control your application and its data. 
 
 Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state.
 
@@ -57,7 +57,7 @@ We can see how it works by looking at some +rails console+ output:
 => false
 
 
-Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
+Creating and saving a new record will send a SQL +INSERT+ operation to the database. Updating an existing record will send a SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
 
 CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful.
 
@@ -838,7 +838,7 @@ This will result in something like the following:
 
 h3. Callbacks Overview
 
-Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
+Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
 
 h4. Callback Registration
 
@@ -984,7 +984,7 @@ h3. Halting Execution
 
 As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed. 
 
-The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception.
+The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.
 
 WARNING. Raising an arbitrary exception may break code that expects +save+ and friends not to fail like that. The +ActiveRecord::Rollback+ exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised.
 
@@ -1020,7 +1020,7 @@ Like in validations, we can also make our callbacks conditional, calling them on
 
 h4. Using +:if+ and +:unless+ with a Symbol
 
-You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
+You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns +false+; when using the +:unless+ option, the callback won't be executed if the method returns +true+. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
 
 
 class Order < ActiveRecord::Base
@@ -1135,7 +1135,7 @@ Observers are conventionally placed inside of your +app/models+ directory and re
 config.active_record.observers = :user_observer
 
 
-As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead.
+As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead.
 
 h4. Sharing Observers
 
@@ -1162,6 +1162,7 @@ h3. Changelog
 
 "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks
 
+* July 20, 2010: Fixed typos and rephrased some paragraphs for clarity. "Jaime Iniesta":http://jaimeiniesta.com
 * May 24, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
 * May 15, 2010: Validation Errors section updated by "Emili Parreño":http://www.eparreno.com
 * March 7, 2009: Callbacks revision by Trevor Turk
-- 
cgit v1.2.3


From 6f7402d32b7b867844fc4ffff891474dc348d0d1 Mon Sep 17 00:00:00 2001
From: Jaime Iniesta 
Date: Tue, 20 Jul 2010 18:17:29 +0200
Subject: non-singleton true and false should go on regular font

---
 railties/guides/source/active_record_validations_callbacks.textile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index be9917868f..c7ba130a90 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -1020,7 +1020,7 @@ Like in validations, we can also make our callbacks conditional, calling them on
 
 h4. Using +:if+ and +:unless+ with a Symbol
 
-You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns +false+; when using the +:unless+ option, the callback won't be executed if the method returns +true+. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
+You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns false; when using the +:unless+ option, the callback won't be executed if the method returns true. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
 
 
 class Order < ActiveRecord::Base
-- 
cgit v1.2.3


From ab62aa1c4309b670c5e3bdce2cf35113e1aa874b Mon Sep 17 00:00:00 2001
From: Jeroen van Dijk 
Date: Wed, 21 Jul 2010 17:10:24 +0200
Subject: Mention that ActionMailer::Base.default_url_options is now deprecated

---
 actionmailer/lib/action_mailer/base.rb | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 7f2ed5ba64..7bbde53306 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -125,14 +125,13 @@ module ActionMailer #:nodoc:
   # make sense to generate relative URLs in email messages.
   #
   # It is also possible to set a default host that will be used in all mailers by setting the :host
-  # option in the ActionMailer::Base.default_url_options hash as follows:
-  #
-  #   ActionMailer::Base.default_url_options[:host] = "example.com"
-  #
-  # This can also be set as a configuration option in config/application.rb:
+  # option as a configuration option in config/application.rb:
   #
   #   config.action_mailer.default_url_options = { :host => "example.com" }
   #
+  # Setting ActionMailer::Base.default_url_options directly is now deprecated, use the configuration
+  # option mentioned above to set the default host.
+  #
   # If you do decide to set a default :host for your mailers you will want to use the
   # :only_path => false option when using url_for. This will ensure that absolute URLs are
   # generated because the url_for view helper will, by default, generate relative URLs when a
-- 
cgit v1.2.3


From 198975ecee96632ef23064d2b8e772d042bca4ac Mon Sep 17 00:00:00 2001
From: Wincent Colaiuta 
Date: Wed, 21 Jul 2010 19:21:05 +0200
Subject: doc: form_for does return output rather than merely evaluate its
 block

---
 actionpack/lib/action_view/helpers/form_helper.rb | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 524913ae29..b0af836522 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -150,10 +150,6 @@ module ActionView
       #   here a named route directly as well. Defaults to the current action.
       # * :html - Optional HTML attributes for the form tag.
       #
-      # Worth noting is that the +form_for+ tag is called in a ERb evaluation
-      # block, not an ERb output block. So that's <% %>, not
-      # <%= %>.
-      #
       # Also note that +form_for+ doesn't create an exclusive scope. It's still
       # possible to use both the stand-alone FormHelper methods and methods
       # from FormTagHelper. For example:
-- 
cgit v1.2.3


From e9127ce7e89ccaddb04a5c2724e18eba2491a053 Mon Sep 17 00:00:00 2001
From: Xavier Noria 
Date: Thu, 22 Jul 2010 00:16:26 +0200
Subject: routing guide: a "photo" resource has by convention paths under
 "photos", in plural

---
 railties/guides/source/routing.textile | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 72a76e25bb..ae80ba77e4 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -441,13 +441,13 @@ h4. Segment Constraints
 You can use the +:constraints+ option to enforce a format for a dynamic segment:
 
 
-match 'photo/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
+match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
 
 
-This route would match URLs such as +/photo/A12345+. You can more succinctly express the same route this way:
+This route would match URLs such as +/photos/A12345+. You can more succinctly express the same route this way:
 
 
-match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
 
 
 +:constraints+ takes regular expression. However note that regexp anchors can't be used within constraints. For example following route will not work:
@@ -472,7 +472,7 @@ You can also constrain a route based on any method on the  {:subdomain => "admin"}
+match "photos", :constraints => {:subdomain => "admin"}
 
 
 You can also specify constrains in a block form:
@@ -511,10 +511,10 @@ h4. Route Globbing
 Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
 
 
-match 'photo/*other' => 'photos#unknown'
+match 'photos/*other' => 'photos#unknown'
 
 
-This route would match +photo/12+ or +/photo/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
+This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
 
 h4. Redirection
 
-- 
cgit v1.2.3


From b72cc472f762a6201e744b2def7467afb363b625 Mon Sep 17 00:00:00 2001
From: Xavier Noria 
Date: Thu, 22 Jul 2010 00:32:39 +0200
Subject: routing guide: say "path" when you mean path

---
 railties/guides/source/routing.textile | 72 +++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 36 deletions(-)

diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index ae80ba77e4..7e6d6b5b34 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -5,14 +5,14 @@ This guide covers the user-facing features of Rails routing. By referring to thi
 * Understand the code in +routes.rb+
 * Construct your own routes, using either the preferred resourceful style or with the @match@ method
 * Identify what parameters to expect an action to receive
-* Automatically create URLs using route helpers
+* Automatically create paths and URLs using route helpers
 * Use advanced techniques such as constraints and Rack endpoints
 
 endprologue.
 
 h3. The Purpose of the Rails Router
 
-The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate URLs, avoiding the need to hardcode URL strings in your views.
+The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.
 
 h4. Connecting URLs to Code
 
@@ -30,9 +30,9 @@ match "/patients/:id" => "patients#show"
 
 the request is dispatched to the +patients+ controller's +show+ action with { :id => "17" } in +params+.
 
-h4. Generating URLs from Code
+h4. Generating Paths and URLs from Code
 
-You can also generate URLs. If your application contains this code:
+You can also generate paths and URLs. If your application contains this code:
 
 
 @patient = Patient.find(17)
@@ -76,7 +76,7 @@ resources :photos
 
 creates seven different routes in your application, all mapping to the +Photos+ controller:
 
-|_. Verb |_.URL             |_.action |_.used for|
+|_. Verb |_.Path            |_.action |_.used for|
 |GET     |/photos           |index    |display a list of all photos|
 |GET     |/photos/new       |new      |return an HTML form for creating a new photo|
 |POST    |/photos           |create   |create a new photo|
@@ -85,7 +85,7 @@ creates seven different routes in your application, all mapping to the +Photos+
 |PUT     |/photos/:id       |update   |update a specific photo|
 |DELETE  |/photos/:id       |destroy  |delete a specific photo|
 
-h4. URLs and Paths
+h4. Paths and URLs
 
 Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of +resources :photos+:
 
@@ -130,7 +130,7 @@ resource :geocoder
 
 creates six different routes in your application, all mapping to the +Geocoders+ controller:
 
-|_. Verb    |_.URL          |_.action |_.used for|
+|_. Verb    |_.Path         |_.action |_.used for|
 |GET        |/geocoder/new  |new      |return an HTML form for creating the geocoder|
 |POST       |/geocoder      |create   |create the new geocoder|
 |GET        |/geocoder      |show     |display the one and only geocoder resource|
@@ -160,7 +160,7 @@ end
 
 This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create:
 
-|_. Verb |_.URL                |_.action |_. helper                  |
+|_. Verb |_.Path               |_.action |_. helper                  |
 |GET     |/admin/photos        |index    | admin_photos_path         |
 |GET     |/admin/photos/new    |new      | new_admin_photos_path     |
 |POST    |/admin/photos        |create   | admin_photos_path         |
@@ -197,16 +197,16 @@ or, for a single case
 resources :posts, :path => "/admin"
 
 
-In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following URLs map to +PostsController+:
+In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following paths map to +PostsController+:
 
-|_. Verb |_.URL         |_.action |_. helper            |
-|GET     |photos        |index    | photos_path         |
-|GET     |photos/new    |new      | photos_path         |
-|POST    |photos        |create   | photos_path         |
-|GET     |photos/1      |show     | photo_path(id)      |
-|GET     |photos/1/edit |edit     | edit_photo_path(id) |
-|PUT     |photos/1      |update   | photo_path(id)      |
-|DELETE  |photos/1      |destroy  | photo_path(id)      |
+|_. Verb |_.Path               |_.action |_. helper            |
+|GET     |/admin/photos        |index    | photos_path         |
+|GET     |/admin/photos/new    |new      | photos_path         |
+|POST    |/admin/photos        |create   | photos_path         |
+|GET     |/admin/photos/1      |show     | photo_path(id)      |
+|GET     |/admin/photos/1/edit |edit     | edit_photo_path(id) |
+|PUT     |/admin/photos/1      |update   | photo_path(id)      |
+|DELETE  |/admin/photos/1      |destroy  | photo_path(id)      |
 
 h4. Nested Resources
 
@@ -232,7 +232,7 @@ end
 
 In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
 
-|_.Verb |_.URL                    |_.action |_.used for|
+|_.Verb |_.Path                   |_.action |_.used for|
 |GET    |/magazines/1/ads         |index    |display a list of all ads for a specific magazine|
 |GET    |/magazines/1/ads/new     |new      |return an HTML form for creating a new ad belonging to a specific magazine|
 |POST   |/magazines/1/ads         |create   |create a new ad belonging to a specific magazine|
@@ -256,7 +256,7 @@ resources :publishers do
 end
 
 
-Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize URLs such as
+Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize paths such as
 
 
 /publishers/1/magazines/2/photos/3
@@ -266,9 +266,9 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin
 
 TIP: _Resources should never be nested more than 1 level deep._
 
-h4. Creating URLs From Objects
+h4. Creating Paths and URLs From Objects
 
-In addition to using the routing helpers, Rails can also create URLs from an array of parameters. For example, suppose you have this set of routes:
+In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters. For example, suppose you have this set of routes:
 
 
 resources :magazines do
@@ -340,7 +340,7 @@ resources :photos do
 end
 
 
-This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
+This will enable Rails to recognize paths such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
 
 Just as with member routes, you can pass +:on+ to a route:
 
@@ -380,7 +380,7 @@ You can set up as many dynamic segments within a regular route as you like. Anyt
 match ':controller/:action/:id/:user_id'
 
 
-An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
+An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
 
 NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
 
@@ -396,7 +396,7 @@ You can specify static segments when creating a route:
 match ':controller/:action/:id/with_user/:user_id'
 
 
-This route would respond to URLs such as +/photos/show/1/with_user/2+. In this case, +params+ would be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
+This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
 
 h4. The Query String
 
@@ -406,7 +406,7 @@ The +params+ will also include any parameters from the query string. For example
 match ':controller/:action/:id'
 
 
-An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
+An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" }.
 
 h4. Defining Defaults
 
@@ -416,7 +416,7 @@ You do not need to explicitly use the +:controller+ and +:action+ symbols within
 match 'photos/:id' => 'photos#show'
 
 
-With this route, Rails will match an incoming URL of +/photos/12+ to the +show+ action of  +PhotosController+.
+With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of  +PhotosController+.
 
 You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
 
@@ -444,7 +444,7 @@ You can use the +:constraints+ option to enforce a format for a dynamic segment:
 match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
 
 
-This route would match URLs such as +/photos/A12345+. You can more succinctly express the same route this way:
+This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way:
 
 
 match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
@@ -573,9 +573,9 @@ The +:controller+ option lets you explicitly specify a controller to use for the
 resources :photos, :controller => "images"
 
 
-will recognize incoming URLs beginning with +/photo+ but route to the +Images+ controller:
+will recognize incoming paths beginning with +/photo+ but route to the +Images+ controller:
 
-|_. Verb |_.URL          |_.action |
+|_. Verb |_.Path         |_.action |
 |GET     |/photos        |index    |
 |GET     |/photos/new    |new      |
 |POST    |/photos        |create   |
@@ -584,7 +584,7 @@ will recognize incoming URLs beginning with +/photo+ but route to the +Images+ c
 |PUT     |/photos/1      |update   |
 |DELETE  |/photos/1      |destroy  |
 
-NOTE: Use +photos_path+, +new_photos_path+, etc. to generate URLs for this resource.
+NOTE: Use +photos_path+, +new_photos_path+, etc. to generate paths for this resource.
 
 h4. Specifying Constraints
 
@@ -615,9 +615,9 @@ The +:as+ option lets you override the normal naming for the named route helpers
 resources :photos, :as => "images"
 
 
-will recognize incoming URLs beginning with +/photos+ and route the requests to +PhotosController+:
+will recognize incoming paths beginning with +/photos+ and route the requests to +PhotosController+:
 
-|_.HTTP verb|_.URL           |_.action |_.named helper   |
+|_.HTTP verb|_.Path          |_.action |_.named helper   |
 |GET        |/photos         |index    | images_path     |
 |GET        |/photos/new     |new      | new_image_path  |
 |POST       |/photos         |create   | images_path     |
@@ -628,20 +628,20 @@ will recognize incoming URLs beginning with +/photos+ and route the requests to
 
 h4. Overriding the +new+ and +edit+ Segments
 
-The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
+The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in paths:
 
 
 resources :photos, :path_names => { :new => 'make', :edit => 'change' }
 
 
-This would cause the routing to recognize URLs such as
+This would cause the routing to recognize paths such as
 
 
 /photos/make
 /photos/1/change
 
 
-NOTE: The actual action names aren't changed by this option. The two URLs shown would still route to the new and edit actions.
+NOTE: The actual action names aren't changed by this option. The two paths shown would still route to the +new+ and +edit+ actions.
 
 TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope:
 
@@ -709,7 +709,7 @@ end
 
 Rails now creates routes to the +CategoriesControlleR+.
 
-|_.HTTP verb|_.URL                      |_.action |
+|_.HTTP verb|_.Path                     |_.action |
 |GET        |/kategorien                |index    |
 |GET        |/kategorien/neu            |new      |
 |POST       |/kategorien                |create   |
-- 
cgit v1.2.3


From b456877cfb7e0cb0bab9ffd5674abd23caba0ab4 Mon Sep 17 00:00:00 2001
From: Xavier Noria 
Date: Thu, 22 Jul 2010 01:27:02 +0200
Subject: camelize and underscore are sort of inverse of each other, but not in
 a mathematical sense [#5174 state:resolved]

---
 activesupport/lib/active_support/inflector/methods.rb        | 12 +++++++++++-
 .../guides/source/active_support_core_extensions.textile     |  6 +++++-
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index b3dc5b2f3a..de49750083 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -20,6 +20,11 @@ module ActiveSupport
     #   "active_record".camelize(:lower)        # => "activeRecord"
     #   "active_record/errors".camelize         # => "ActiveRecord::Errors"
     #   "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+    #
+    # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
+    # though there are cases where that does not hold:
+    #
+    #   "SSLError".underscore.camelize # => "SslError"
     def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
       if first_letter_in_uppercase
         lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
@@ -28,13 +33,18 @@ module ActiveSupport
       end
     end
 
-    # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
+    # Makes an underscored, lowercase form from the expression in the string.
     #
     # Changes '::' to '/' to convert namespaces to paths.
     #
     # Examples:
     #   "ActiveRecord".underscore         # => "active_record"
     #   "ActiveRecord::Errors".underscore # => active_record/errors
+    #
+    # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
+    # though there are cases where that does not hold:
+    #
+    #   "SSLError".underscore.camelize # => "SslError"
     def underscore(camel_cased_word)
       word = camel_cased_word.to_s.dup
       word.gsub!(/::/, '/')
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index a895dbded2..e53c7715bb 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -1491,13 +1491,15 @@ end
 
 That may be handy to compute method names in a language that follows that convention, for example JavaScript.
 
+INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: "SSLError".underscore.camelize gives back "SslError".
+
 +camelize+ is aliased to +camelcase+.
 
 NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
 
 h5. +underscore+
 
-The method +underscore+ is the inverse of +camelize+, explained above:
+The method +underscore+ goes the other way around, from camel case to paths:
 
 
 "Product".underscore   # => "product"
@@ -1530,6 +1532,8 @@ def load_missing_constant(from_mod, const_name)
 end
 
 
+INFO: As a rule of thumb you can think of +underscore+ as the inverse of +camelize+, though there are cases where that does not hold. For example, "SSLError".underscore.camelize gives back "SslError".
+
 NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
 
 h5. +titleize+
-- 
cgit v1.2.3


From 1f8edb996b97da0df6ed0f0e0ceffef9d84119ce Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 15:34:43 -0700
Subject: adding tests for sessions and clearing up warnings

---
 activerecord/lib/active_record/session_store.rb | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 7ea7fb5c51..0c2db4156b 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -62,7 +62,7 @@ module ActiveRecord
 
       class << self
         def data_column_size_limit
-          @data_column_size_limit ||= columns_hash[@@data_column_name].limit
+          @data_column_size_limit ||= columns_hash[data_column_name].limit
         end
 
         # Hook to set up sessid compatibility.
@@ -83,8 +83,8 @@ module ActiveRecord
           connection.execute <<-end_sql
             CREATE TABLE #{table_name} (
               id INTEGER PRIMARY KEY,
-              #{connection.quote_column_name('session_id')} TEXT UNIQUE,
-              #{connection.quote_column_name(@@data_column_name)} TEXT(255)
+              #{connection.quote_column_name(session_id_column)} TEXT UNIQUE,
+              #{connection.quote_column_name(data_column_name)} TEXT(255)
             )
           end_sql
         end
@@ -94,6 +94,10 @@ module ActiveRecord
         end
 
         private
+          def session_id_column
+            'session_id'
+          end
+
           # Compatibility with tables using sessid instead of session_id.
           def setup_sessid_compatibility!
             # Reset column info since it may be stale.
@@ -106,6 +110,8 @@ module ActiveRecord
               define_method(:session_id)  { sessid }
               define_method(:session_id=) { |session_id| self.sessid = session_id }
             else
+              class << self; remove_method :find_by_session_id; end
+
               def self.find_by_session_id(session_id)
                 find :first, :conditions => {:session_id=>session_id}
               end
-- 
cgit v1.2.3


From bdbe390a98acbebcfd48dca28c79d3296c8d3981 Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 16:01:41 -0700
Subject: initializing instance variables

---
 activerecord/lib/active_record/session_store.rb    |  7 ++-
 .../test/cases/session_store/session_test.rb       | 63 ++++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)
 create mode 100644 activerecord/test/cases/session_store/session_test.rb

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 0c2db4156b..feb9e2e8f6 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -119,6 +119,11 @@ module ActiveRecord
           end
       end
 
+      def initialize(attributes = nil)
+        @data = nil
+        super
+      end
+
       # Lazy-unmarshal session state.
       def data
         @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
@@ -128,7 +133,7 @@ module ActiveRecord
 
       # Has the session been loaded yet?
       def loaded?
-        !!@data
+        @data
       end
 
       private
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
new file mode 100644
index 0000000000..bb2e1ec97a
--- /dev/null
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -0,0 +1,63 @@
+require 'cases/helper'
+require 'action_dispatch'
+require 'active_record/session_store'
+
+module ActiveRecord
+  class SessionStore
+    class SessionTest < ActiveRecord::TestCase
+      def setup
+        super
+        Session.drop_table! if Session.table_exists?
+      end
+
+      def test_data_column_name
+        # default column name is 'data'
+        assert_equal 'data', Session.data_column_name
+      end
+
+      def test_table_name
+        assert_equal 'sessions', Session.table_name
+      end
+
+      def test_create_table!
+        assert !Session.table_exists?
+        Session.create_table!
+        assert Session.table_exists?
+        Session.drop_table!
+        assert !Session.table_exists?
+      end
+
+      def test_find_by_sess_id_compat
+        klass = Class.new(Session) do
+          def self.session_id_column
+            'sessid'
+          end
+        end
+        klass.create_table!
+
+        assert klass.columns_hash['sessid'], 'sessid column exists'
+        session = klass.new(:data => 'hello')
+        session.sessid = 100
+        session.save!
+
+        found = klass.find_by_session_id(100)
+        assert_equal session, found
+        assert_equal session.sessid, found.session_id.to_i
+      ensure
+        klass.drop_table!
+      end
+
+      def test_find_by_session_id
+        Session.create_table!
+        s = Session.create!(:data => 'world', :session_id => 10)
+        assert_equal s, Session.find_by_session_id(10)
+        Session.drop_table!
+      end
+
+      def test_loaded?
+        s = Session.new
+        assert !s.loaded?, 'session is not loaded'
+      end
+    end
+  end
+end
-- 
cgit v1.2.3


From ba0d2a9ce374df69647a8280459d59000ce43188 Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 16:36:22 -0700
Subject: starting sql bypass test, fixing create_table and drop_table!

---
 activerecord/lib/active_record/session_store.rb    |  8 ++++----
 .../test/cases/session_store/sql_bypass.rb         | 22 ++++++++++++++++++++++
 2 files changed, 26 insertions(+), 4 deletions(-)
 create mode 100644 activerecord/test/cases/session_store/sql_bypass.rb

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index feb9e2e8f6..cc8d697dde 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -217,17 +217,17 @@ module ActiveRecord
         end
 
         def create_table!
-          @@connection.execute <<-end_sql
+          connection.execute <<-end_sql
             CREATE TABLE #{table_name} (
               id INTEGER PRIMARY KEY,
-              #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
-              #{@@connection.quote_column_name(data_column)} TEXT
+              #{connection.quote_column_name(session_id_column)} TEXT UNIQUE,
+              #{connection.quote_column_name(data_column)} TEXT
             )
           end_sql
         end
 
         def drop_table!
-          @@connection.execute "DROP TABLE #{table_name}"
+          connection.execute "DROP TABLE #{table_name}"
         end
       end
 
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb
new file mode 100644
index 0000000000..5484c34f7d
--- /dev/null
+++ b/activerecord/test/cases/session_store/sql_bypass.rb
@@ -0,0 +1,22 @@
+require 'cases/helper'
+require 'action_dispatch'
+require 'active_record/session_store'
+
+module ActiveRecord
+  class SessionStore
+    class SqlBypassTest < ActiveRecord::TestCase
+      def setup
+        super
+        Session.drop_table! if Session.table_exists?
+      end
+
+      def test_create_table
+        assert !Session.table_exists?
+        SqlBypass.create_table!
+        assert Session.table_exists?
+        SqlBypass.drop_table!
+        assert !Session.table_exists?
+      end
+    end
+  end
+end
-- 
cgit v1.2.3


From 4e6cf429a16ce824b18bb8f494dbcbf5931a615d Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 16:45:38 -0700
Subject: readability is hip

---
 activerecord/lib/active_record/session_store.rb | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index cc8d697dde..a0a9368c12 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -238,8 +238,10 @@ module ActiveRecord
       # telling us to postpone unmarshaling until the data is requested.
       # We need to handle a normal data attribute in case of a new record.
       def initialize(attributes)
-        @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
-        @new_record = @marshaled_data.nil?
+        @session_id     = attributes[:session_id]
+        @data           = attributes[:data]
+        @marshaled_data = attributes[:marshaled_data]
+        @new_record     = @marshaled_data.nil?
       end
 
       def new_record?
-- 
cgit v1.2.3


From f5de56f53768bf07a337c9bf32b397a9d3e61cf0 Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 16:52:15 -0700
Subject: reducing function calls in the session store

---
 activerecord/lib/active_record/session_store.rb | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index a0a9368c12..5de812962a 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -138,17 +138,17 @@ module ActiveRecord
 
       private
         def marshal_data!
-          return false if !loaded?
-          write_attribute(@@data_column_name, self.class.marshal(self.data))
+          return false unless loaded?
+          write_attribute(@@data_column_name, self.class.marshal(data))
         end
 
         # Ensures that the data about to be stored in the database is not
         # larger than the data storage column. Raises
         # ActionController::SessionOverflowError.
         def raise_on_session_data_overflow!
-          return false if !loaded?
+          return false unless loaded?
           limit = self.class.data_column_size_limit
-          if loaded? and limit and read_attribute(@@data_column_name).size > limit
+          if limit and read_attribute(@@data_column_name).size > limit
             raise ActionController::SessionOverflowError
           end
         end
@@ -265,7 +265,7 @@ module ActiveRecord
       end
 
       def save
-        return false if !loaded?
+        return false unless loaded?
         marshaled_data = self.class.marshal(data)
 
         if @new_record
-- 
cgit v1.2.3


From 87a319cd0c51cbc8f6bc8e3d11ab9e30a96ac628 Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 16:55:04 -0700
Subject: testing new_record? and converting to an attribute

---
 activerecord/lib/active_record/session_store.rb     | 8 +++-----
 activerecord/test/cases/session_store/sql_bypass.rb | 5 +++++
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 5de812962a..17f09d64b0 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -231,7 +231,9 @@ module ActiveRecord
         end
       end
 
-      attr_reader :session_id
+      attr_reader :session_id, :new_record
+      alias :new_record? :new_record
+
       attr_writer :data
 
       # Look for normal and marshaled data, self.find_by_session_id's way of
@@ -244,10 +246,6 @@ module ActiveRecord
         @new_record     = @marshaled_data.nil?
       end
 
-      def new_record?
-        @new_record
-      end
-
       # Lazy-unmarshal session state.
       def data
         unless @data
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb
index 5484c34f7d..cea5e4eabe 100644
--- a/activerecord/test/cases/session_store/sql_bypass.rb
+++ b/activerecord/test/cases/session_store/sql_bypass.rb
@@ -17,6 +17,11 @@ module ActiveRecord
         SqlBypass.drop_table!
         assert !Session.table_exists?
       end
+
+      def test_new_record?
+        s = SqlBypass.new :data => 'foo', :session_id => 10
+        assert s.new_record?, 'this is a new record!'
+      end
     end
   end
 end
-- 
cgit v1.2.3


From a78547a9c66eeaa3dbf5e2612483ca172fb137e9 Mon Sep 17 00:00:00 2001
From: Aaron Patterson 
Date: Wed, 21 Jul 2010 16:59:23 -0700
Subject: testing loaded? method and reducing funcalls

---
 activerecord/lib/active_record/session_store.rb     |  2 +-
 activerecord/test/cases/session_store/sql_bypass.rb | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 17f09d64b0..b352f9529d 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -259,7 +259,7 @@ module ActiveRecord
       end
 
       def loaded?
-        !!@data
+        @data
       end
 
       def save
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb
index cea5e4eabe..6c4a70b9fe 100644
--- a/activerecord/test/cases/session_store/sql_bypass.rb
+++ b/activerecord/test/cases/session_store/sql_bypass.rb
@@ -22,6 +22,16 @@ module ActiveRecord
         s = SqlBypass.new :data => 'foo', :session_id => 10
         assert s.new_record?, 'this is a new record!'
       end
+
+      def test_not_loaded?
+        s = SqlBypass.new({})
+        assert !s.loaded?, 'it is not loaded'
+      end
+
+      def test_loaded?
+        s = SqlBypass.new :data => 'hello'
+        assert s.loaded?, 'it is loaded'
+      end
     end
   end
 end
-- 
cgit v1.2.3


From a1e795f554e07476f1084a0c76cb8b033d1d0b0c Mon Sep 17 00:00:00 2001
From: Santiago Pastorino 
Date: Wed, 21 Jul 2010 20:59:05 -0300
Subject: options could be of any kind of Hash (Hash, HashWithIndifferentAccess
 or OrderedHash) this way we keep the properties of the options passed as an
 argument

---
 actionpack/lib/action_dispatch/routing/route_set.rb | 2 +-
 actionpack/lib/action_dispatch/routing/url_for.rb   | 2 +-
 actionpack/lib/action_view/helpers/url_helper.rb    | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a9b97a17eb..77688fe397 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -454,7 +454,7 @@ module ActionDispatch
 
       def url_for(options)
         finalize!
-        options = default_url_options.merge(options || {})
+        options = (options || {}).reverse_merge!(default_url_options)
 
         handle_positional_args(options)
 
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 662eb05c26..9b42f26289 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -129,7 +129,7 @@ module ActionDispatch
         when String
           options
         when nil, Hash
-          _routes.url_for(url_options.merge((options || {}).symbolize_keys))
+          _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys)
         else
           polymorphic_url(options)
         end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index b493a0cb0e..a5c6718c58 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -95,7 +95,7 @@ module ActionView
         when String
           options
         when Hash
-          options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
+          options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
           super
         when :back
           controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
-- 
cgit v1.2.3


From 30df88ae06182a72ae7f959dce8d8df4a7adb99a Mon Sep 17 00:00:00 2001
From: Santiago Pastorino 
Date: Wed, 21 Jul 2010 21:00:11 -0300
Subject: These tests are trusting in the order of the elements so use
 OrderedHash instead of Hash

---
 actionpack/test/template/url_helper_test.rb        | 22 +++++++++-------------
 activeresource/test/cases/base_test.rb             |  3 ++-
 .../test/core_ext/object/to_query_test.rb          |  5 +++--
 3 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 048f96c9a9..d59bbec4a9 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -1,6 +1,6 @@
 # encoding: utf-8
 require 'abstract_unit'
-require 'active_support/ordered_options'
+require 'active_support/ordered_hash'
 require 'controller/fake_controllers'
 
 class UrlHelperTest < ActiveSupport::TestCase
@@ -31,17 +31,13 @@ class UrlHelperTest < ActiveSupport::TestCase
     {}
   end
 
-  def abcd(hash = {})
-    hash_for(:a => :b, :c => :d).merge(hash)
-  end
-
-  def hash_for(opts = {})
-    {:controller => "foo", :action => "bar"}.merge(opts)
+  def hash_for(opts = [])
+    ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))]
   end
   alias url_hash hash_for
 
   def test_url_for_does_not_escape_urls
-    assert_equal "/?a=b&c=d", url_for(abcd)
+    assert_equal "/?a=b&c=d", url_for(hash_for([:a, :b, :c, :d]))
   end
 
   def test_url_for_with_back
@@ -128,7 +124,7 @@ class UrlHelperTest < ActiveSupport::TestCase
   end
 
   def test_link_tag_with_host_option
-    hash = hash_for(:host => "www.example.com")
+    hash = hash_for([:host, "www.example.com"])
     expected = %q{Test Link}
     assert_dom_equal(expected, link_to('Test Link', hash))
   end
@@ -294,7 +290,7 @@ class UrlHelperTest < ActiveSupport::TestCase
   def test_current_page_with_params_that_match
     @request = request_for_url("/?order=desc&page=1")
 
-    assert current_page?(hash_for(:order => "desc", :page => "1"))
+    assert current_page?(hash_for([:order, "desc", :page, "1"]))
     assert current_page?("http://www.example.com/?order=desc&page=1")
   end
 
@@ -316,20 +312,20 @@ class UrlHelperTest < ActiveSupport::TestCase
     @request = request_for_url("/?order=desc&page=1")
 
     assert_equal "Showing",
-      link_to_unless_current("Showing", hash_for(:order=>'desc', :page=>'1'))
+      link_to_unless_current("Showing", hash_for([:order, 'desc', :page, '1']))
     assert_equal "Showing",
       link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1")
 
     @request = request_for_url("/?order=desc")
 
     assert_equal %{Showing},
-      link_to_unless_current("Showing", hash_for(:order => :asc))
+      link_to_unless_current("Showing", hash_for([:order, :asc]))
     assert_equal %{Showing},
       link_to_unless_current("Showing", "http://www.example.com/?order=asc")
 
     @request = request_for_url("/?order=desc")
     assert_equal %{Showing},
-      link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2))
+      link_to_unless_current("Showing", hash_for([:order, "desc", :page, 2]))
     assert_equal %{Showing},
       link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2")
 
diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb
index 4d036d73cc..a176c037d5 100644
--- a/activeresource/test/cases/base_test.rb
+++ b/activeresource/test/cases/base_test.rb
@@ -6,6 +6,7 @@ require "fixtures/sound"
 require "fixtures/beast"
 require "fixtures/proxy"
 require 'active_support/json'
+require 'active_support/ordered_hash'
 require 'active_support/core_ext/hash/conversions'
 require 'mocha'
 
@@ -555,7 +556,7 @@ class BaseTest < Test::Unit::TestCase
 
     assert_equal '/people.xml?name[]=bob&name[]=your+uncle%2Bme&name[]=&name[]=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false])
 
-    assert_equal '/people.xml?struct[a][]=2&struct[a][]=1&struct[b]=fred', Person.collection_path(:struct => {:a => [2,1], 'b' => 'fred'})
+    assert_equal '/people.xml?struct[a][]=2&struct[a][]=1&struct[b]=fred', Person.collection_path(:struct => ActiveSupport::OrderedHash[:a, [2,1], 'b', 'fred'])
   end
 
   def test_custom_element_path
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 4d655913cc..e28b4cd493 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -1,4 +1,5 @@
 require 'abstract_unit'
+require 'active_support/ordered_hash'
 require 'active_support/core_ext/object/to_query'
 
 class ToQueryTest < Test::Unit::TestCase
@@ -18,12 +19,12 @@ class ToQueryTest < Test::Unit::TestCase
 
   def test_nested_conversion
     assert_query_equal 'person[login]=seckar&person[name]=Nicholas',
-      :person => {:name => 'Nicholas', :login => 'seckar'}
+      :person => ActiveSupport::OrderedHash[:login, 'seckar', :name, 'Nicholas']
   end
 
   def test_multiple_nested
     assert_query_equal 'account[person][id]=20&person[id]=10',
-      :person => {:id => 10}, :account => {:person => {:id => 20}}
+      ActiveSupport::OrderedHash[:account, {:person => {:id => 20}}, :person, {:id => 10}]
   end
 
   def test_array_values
-- 
cgit v1.2.3


From 4ed9bd6431d1733d83de0b461280f77f78a9c4c6 Mon Sep 17 00:00:00 2001
From: Bobby Wilson 
Date: Wed, 21 Jul 2010 20:34:00 -0700
Subject: Changed code style that was incorrectly rendering block style instead
 of inline.

---
 railties/guides/source/routing.textile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 7e6d6b5b34..7b665d81e7 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -3,7 +3,7 @@ h2. Rails Routing from the Outside In
 This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to:
 
 * Understand the code in +routes.rb+
-* Construct your own routes, using either the preferred resourceful style or with the @match@ method
+* Construct your own routes, using either the preferred resourceful style or with the match method
 * Identify what parameters to expect an action to receive
 * Automatically create paths and URLs using route helpers
 * Use advanced techniques such as constraints and Rack endpoints
-- 
cgit v1.2.3


From 89b5e79632c4f0b18099faa846e45741b7c5e700 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?= 
Date: Sun, 18 Jul 2010 14:58:40 +0200
Subject: revise download/installation/support sections in READMEs

 - don't reference ancient gem versions
 - don't link to old API doc subdomains
 - point to GitHub instead of RubyForge
 - point to Lighthouse account for support
---
 actionmailer/README.rdoc  | 33 ++++++++++++++++++++-------------
 actionpack/README.rdoc    | 20 ++++++++++----------
 activerecord/README.rdoc  | 20 +++++++++-----------
 activesupport/README.rdoc | 29 ++++++++++++++---------------
 4 files changed, 53 insertions(+), 49 deletions(-)

diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 3dd56a6fd8..1b9cb8a570 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -121,31 +121,38 @@ The Base class has the full list of configuration options. Here's an example:
 
 == Dependencies
 
-Action Mailer requires that the Action Pack is either available to be required immediately
-or is accessible as a GEM.
+Action Mailer depends on Action Pack.
 
-Additionally, Action Mailer requires the Mail gem, http://github.com/mikel/mail
+Additionally, Action Mailer requires the {Mail gem}[http://github.com/mikel/mail].
 
-== Download
+== Bundled software
+
+* Text::Format 0.63 by Austin Ziegler released under OpenSource
+  Read more on http://www.halostatue.ca/ruby/Text__Format.html
+
+
+== Download and installation
 
 The latest version of Action Mailer can be installed with Rubygems:
 
-* gem install actionmailer
+  % [sudo] gem install actionmailer
+
+Source code can be downloaded as part of the Rails project on GitHub
 
-Documentation can be found at 
+* http://github.com/rails/rails/tree/master/actionmailer/
 
-* http://api.rubyonrails.org
 
 == License
 
 Action Mailer is released under the MIT license.
 
+
 == Support
 
-The Action Mailer homepage is http://www.rubyonrails.org. You can find
-the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer.
-And as Jim from Rake says:
+API documentation is at
+
+* http://api.rubyonrails.com
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
 
-   Feel free to submit commits or feature requests.  If you send a patch,
-   remember to update the corresponding unit tests.  If fact, I prefer
-   new feature to be submitted in the form of new unit tests.
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 272feb63d0..7294e7f2d4 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -355,15 +355,15 @@ new model). After creating the post, it'll redirect to the show page using
 an URL such as /weblog/5 (where 5 is the id of the post).
 
 
-== Download
+== Download and installation
 
 The latest version of Action Pack can be installed with Rubygems:
 
-* gem install actionpack
+  % [sudo] gem install actionpack
 
-Documentation can be found at 
+Source code can be downloaded as part of the Rails project on GitHub
 
-* http://api.rubyonrails.org
+* http://github.com/rails/rails/tree/master/actionpack/
 
 
 == License
@@ -373,10 +373,10 @@ Action Pack is released under the MIT license.
 
 == Support
 
-The Action Pack homepage is http://www.rubyonrails.org. You can find
-the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
-And as Jim from Rake says:
+API documentation is at
 
-   Feel free to submit commits or feature requests.  If you send a patch,
-   remember to update the corresponding unit tests.  If fact, I prefer
-   new feature to be submitted in the form of new unit tests.
+* http://api.rubyonrails.com
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 0446180207..efa3c7e7ac 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -307,15 +307,15 @@ Admit the Database:
 * Doesn't attempt to duplicate or replace data definitions
 
 
-== Download
+== Download and installation
 
 The latest version of Active Record can be installed with Rubygems:
 
-* gem install activerecord
+  % [sudo] gem install activerecord
 
-Documentation can be found at 
+Source code can be downloaded as part of the Rails project on GitHub
 
-* http://api.rubyonrails.org
+* http://github.com/rails/rails/tree/master/activerecord/
 
 
 == License
@@ -325,12 +325,10 @@ Active Record is released under the MIT license.
 
 == Support
 
-The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record
-RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:
+API documentation is at
 
-   Feel free to submit commits or feature requests.  If you send a patch,
-   remember to update the corresponding unit tests.  If fact, I prefer
-   new feature to be submitted in the form of new unit tests.
+* http://api.rubyonrails.com
 
-For other information, feel free to ask on the rubyonrails-talk 
-(http://groups.google.com/group/rubyonrails-talk) mailing list.
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index aa86f1fd65..77b8a64304 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -1,19 +1,20 @@
-= Active Support -- Utility classes and standard library extensions from Rails
+= Active Support -- Utility classes and Ruby extensions from Rails
 
-Active Support is a collection of various utility classes and standard library extensions that were found useful
-for Rails. All these additions have hence been collected in this bundle as way to gather all that sugar that makes
-Ruby sweeter.
+Active Support is a collection of utility classes and standard library
+extensions that were found useful for the Rails framework. These additions
+reside in this package so they can be loaded as needed in Ruby projects
+outside of Rails.
 
 
-== Download
+== Download and installation
 
 The latest version of Active Support can be installed with Rubygems:
 
-* gem install activesupport
+  % [sudo] gem install activesupport
 
-Documentation can be found at 
+Source code can be downloaded as part of the Rails project on GitHub
 
-* http://api.rubyonrails.org
+* http://github.com/rails/rails/tree/master/activesupport/
 
 
 == License
@@ -23,12 +24,10 @@ Active Support is released under the MIT license.
 
 == Support
 
-The Active Support homepage is http://www.rubyonrails.com. You can find the Active Support
-RubyForge page at http://rubyforge.org/projects/activesupport. And as Jim from Rake says:
+API documentation is at
 
-   Feel free to submit commits or feature requests.  If you send a patch,
-   remember to update the corresponding unit tests.  If fact, I prefer
-   new feature to be submitted in the form of new unit tests.
+* http://api.rubyonrails.com
 
-For other information, feel free to ask on the ruby-talk mailing list
-(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
-- 
cgit v1.2.3


From a1023b0bf0508378e8e6fcb68f83d87a7981faf7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mislav=20Marohni=C4=87?= 
Date: Mon, 19 Jul 2010 13:51:04 +0200
Subject: improve Action Pack README

 - revise introductory text
 - list modules contained in the package
 - improve examples
 - remove obsolete `scaffold` and `form` examples
---
 actionpack/README.rdoc | 150 ++++++++++++++++++-------------------------------
 1 file changed, 55 insertions(+), 95 deletions(-)

diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 7294e7f2d4..0e7d164623 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -1,25 +1,35 @@
-= Action Pack -- On rails from request to response
+= Action Pack -- From request to response
 
-Action Pack splits the response to a web request into a controller part
-(performing the logic) and a view part (rendering a template). This two-step
-approach is known as an action, which will normally create, read, update, or
-delete (CRUD for short) some sort of model part (often backed by a database)
-before choosing either to render a template or redirecting to another action.
+Action Pack is a framework for handling and responding to web requests. It it
+provides mechanisms for *routing* (mapping request URLs to actions), defining
+*controllers* that implement actions, and generating responses by rendering
+*views*, which are templates of various formats. In short, Action Pack
+provides the view and controller layers in the MVC paradigm.
 
-Action Pack implements these actions as public methods on Action Controllers
-and uses Action Views to implement the template rendering. Action Controllers
-are then responsible for handling all the actions relating to a certain part
-of an application. This grouping usually consists of actions for lists and for
-CRUDs revolving around a single (or a few) model objects. So ContactsController
-would be responsible for listing contacts, creating, deleting, and updating
-contacts. A WeblogController could be responsible for both posts and comments.
+It consists of several modules:
 
-Action View templates are written using embedded Ruby in tags mingled in with
-the HTML. To avoid cluttering the templates with code, a bunch of helper
-classes provide common behavior for forms, dates, and strings. And it's easy
-to add specific helpers to keep the separation as the application evolves.
+* Action Dispatch, which parses information about the web request, handles
+  routing as defined by the user, and does advanced processing related to HTTP
+  such as MIME-type negotiation, decoding parameters in POST/PUT bodies,
+  handling HTTP caching logic, cookies and sessions.
 
-A short rundown of the major features:
+* Action Controller, which provides a base controller class that can be
+  subclassed to implement filters and actions to handle requests. The result
+  of an action is typically content generated from views.
+
+* Action View, which handles view template lookup and rendering, and provides
+  view helpers that assist when building HTML forms, Atom feeds and more.
+  Template formats that Action View handles are ERb (embedded Ruby, typically
+  used to inline short Ruby snippets inside HTML), XML Builder and RJS
+  (dynamically generated JavaScript from Ruby code).
+
+With the Ruby on Rails framework, users only directly interface with the
+Action Controller module. Necessary Action Dispatch functionality is activated
+by default and Action View rendering is implicitly triggered by Action
+Controller. However, these modules are designed to function on their own and
+can be used outside of Rails.
+
+A short rundown of some of the major features:
 
 * Actions grouped in controller as methods instead of separate command objects
   and can therefore share helper methods
@@ -31,26 +41,29 @@ A short rundown of the major features:
 
       def update
         @customer = find_customer
-        @customer.attributes = params[:customer]
-        @customer.save ?
-          redirect_to(:action => "show") :
-          render(:action => "edit")
+        if @customer.update_attributes(params[:customer])
+          redirect_to :action => "show"
+        else
+          render :action => "edit"
+        end
       end
 
       private
-        def find_customer() Customer.find(params[:id]) end
+        def find_customer
+          Customer.find params[:id]
+        end
     end
 
   {Learn more}[link:classes/ActionController/Base.html]
 
 
-* Embedded Ruby for templates (no new "easy" template language)
+* ERb templates (static content mixed with dynamic output from ruby)
 
     <% for post in @posts %>
       Title: <%= post.title %>
     <% end %>
 
-    All post titles: <%= @posts.collect{ |p| p.title }.join ", " %>
+    All post titles: <%= @posts.collect{ |p| p.title }.join(", ") %>
 
     <% unless @person.is_client? %>
       Not for clients to see...
@@ -59,7 +72,7 @@ A short rundown of the major features:
   {Learn more}[link:classes/ActionView.html]
 
 
-* Builder-based templates (great for XML content, like RSS)
+* "Builder" templates (great for XML content, like RSS)
 
     xml.rss("version" => "2.0") do
       xml.channel do
@@ -84,11 +97,16 @@ A short rundown of the major features:
   {Learn more}[link:classes/ActionView/Base.html]
 
 
-* Filters for pre and post processing of the response (as methods, procs, and classes)
+* Filters for pre- and post-processing of the response
 
     class WeblogController < ActionController::Base
+      # filters as methods
       before_filter :authenticate, :cache, :audit
+      
+      # filter as a proc
       after_filter { |c| c.response.body = Gzip::compress(c.response.body) }
+      
+      # class filter
       after_filter LocalizeFilter
 
       def index
@@ -111,16 +129,14 @@ A short rundown of the major features:
 
 * Helpers for forms, dates, action links, and text
 
-    <%= text_field "post", "title", "size" => 30 %>
-    <%= html_date_select(Date.today) %>
+    <%= text_field_tag "post", "title", "size" => 30 %>
     <%= link_to "New post", :controller => "post", :action => "new" %>
     <%= truncate(post.title, :length => 25) %>
 
   {Learn more}[link:classes/ActionView/Helpers.html]
 
 
-* Layout sharing for template reuse (think simple version of Struts
-  Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
+* Layout sharing for template reuse
 
     class WeblogController < ActionController::Base
       layout "weblog_layout"
@@ -141,22 +157,22 @@ A short rundown of the major features:
   {Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
 
 
-* Routing makes pretty urls incredibly easy
+* Routing makes pretty URLs incredibly easy
 
-    map.connect 'clients/:client_name/:project_name/:controller/:action'
+    match 'clients/:client_name/:project_name/:controller/:action'
 
-    Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
-    { "client_name" => "37signals", "project_name" => "basecamp" } in params[:params]
+    Accessing "/clients/37signals/basecamp/project/index" calls ProjectController#index with
+    { "client_name" => "37signals", "project_name" => "basecamp" } in `params`
 
-    From that URL, you can rewrite the redirect in a number of ways:
+    From that action, you can write the redirect in a number of ways:
 
     redirect_to(:action => "edit") =>
-      /clients/37signals/basecamp/project/dash
+      /clients/37signals/basecamp/project/edit
 
     redirect_to(:client_name => "nextangle", :project_name => "rails") =>
-      /clients/nextangle/rails/project/dash
+      /clients/nextangle/rails/project/index
 
-  {Learn more}[link:classes/ActionController/Base.html]
+  {Learn more}[link:classes/ActionDispatch/Routing.html]
 
 
 * Easy testing of both controller and rendered template through ActionController::TestCase
@@ -233,62 +249,6 @@ A short rundown of the major features:
   {Learn more}[link:classes/ActionController/Rescue.html]
 
 
-* Scaffolding for Active Record model objects
-
-    class AccountController < ActionController::Base
-      scaffold :account
-    end
-
-    The AccountController now has the full CRUD range of actions and default
-    templates: list, show, destroy, new, create, edit, update
-
-  {Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html]
-
-
-* Form building for Active Record model objects
-
-    The post object has a title (varchar), content (text), and
-    written_on (date)
-
-    <%= form "post" %>
-
-    ...will generate something like (the selects will have more options, of
-    course):
-
-    
-

- Title:
- -

-

- Content:
- -

-

- Written on:
- - - -

- - -
- - This form generates a params[:post] array that can be used directly in a save action: - - class WeblogController < ActionController::Base - def create - post = Post.create(params[:post]) - redirect_to :action => "show", :id => post.id - end - end - - {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html] - - -* Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby - - == Simple example (from outside of Rails) This example will implement a simple weblog system using inline templates and -- cgit v1.2.3 From d5ee17ed200a4cb0481e99c4d40d9b584418a520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Mon, 19 Jul 2010 13:55:18 +0200 Subject: improve Active Record README - revise introductory text - improve examples - don't claim that Oracle, SQL Server, or DB2 are supported - remove lengthy "simple example" --- activerecord/README.rdoc | 222 ++++++++++++----------------------------------- 1 file changed, 55 insertions(+), 167 deletions(-) diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index efa3c7e7ac..8dbd6c82b5 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -1,49 +1,52 @@ -= Active Record -- Object-relation mapping put on rails += Active Record -- Object-relational mapping put on rails -Active Record connects business objects and database tables to create a persistable -domain model where logic and data are presented in one wrapping. It's an implementation -of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] -by the same name as described by Martin Fowler: +Active Record connects classes to relational database tables to establish an +almost zero-configuration persistence layer for applications. The library +provides a base class that, when subclassed, sets up a mapping between the new +class and an existing table in the database. In context of an application, +these classes are commonly referred to as *models*. Models can also be +connected to other models; this is done by defining *associations*. - "An object that wraps a row in a database table or view, encapsulates - the database access, and adds domain logic on that data." +Active Record relies heavily on naming in that it uses class and association +names to establish mappings between respective database tables and foreign key +columns. Although these mappings can be defined explicitly, it's recommended +to follow naming conventions, especially when getting started with the +library. -Active Record's main contribution to the pattern is to relieve the original of two stunting problems: -lack of associations and inheritance. By adding a simple domain language-like set of macros to describe -the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the -gap of functionality between the data mapper and active record approach. - -A short rundown of the major features: +A short rundown of some of the major features: * Automated mapping between classes and tables, attributes and columns. - class Product < ActiveRecord::Base; end + class Product < ActiveRecord::Base + end - ...is automatically mapped to the table named "products", such as: + The Product class is automatically mapped to the table named "products", + which might look like this: CREATE TABLE products ( id int(11) NOT NULL auto_increment, name varchar(255), PRIMARY KEY (id) ); - - ...which again gives Product#name and Product#name=(new_name) + + This would also define the following accessors: `Product#name` and + `Product#name=(new_name)` {Learn more}[link:classes/ActiveRecord/Base.html] -* Associations between objects controlled by simple meta-programming macros. +* Associations between objects defined by simple class methods. class Firm < ActiveRecord::Base has_many :clients has_one :account - belongs_to :conglomorate + belongs_to :conglomerate end {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] -* Aggregations of value objects controlled by simple meta-programming macros. +* Aggregations of value objects. class Account < ActiveRecord::Base composed_of :balance, :class_name => "Money", @@ -65,23 +68,19 @@ A short rundown of the major features: end {Learn more}[link:classes/ActiveRecord/Validations.html] - -* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc). - class Person < ActiveRecord::Base - def before_destroy # is called just before Person#destroy - CreditCard.find(credit_card_id).destroy - end - end - class Account < ActiveRecord::Base - after_find :eager_load, 'self.class.announce(#{id})' +* Callbacks available for the entire lifecycle (instantiation, saving, destroying, validating, etc.) + + class Person < ActiveRecord::Base + before_destroy :invalidate_payment_plan + # the `invalidate_payment_plan` method gets called just before Person#destroy end {Learn more}[link:classes/ActiveRecord/Callbacks.html] -* Observers for the entire lifecycle +* Observers that react to changes in a model class CommentObserver < ActiveRecord::Observer def after_create(comment) # is called just after Comment#save @@ -122,40 +121,24 @@ A short rundown of the major features: {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] -* Direct manipulation (instead of service invocation) +* Database abstraction through simple adapters - So instead of (Hibernate[http://www.hibernate.org/] example): + # connect to SQLite3 + ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "dbfile.sqlite3") - long pkId = 1234; - DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) ); - // something interesting involving a cat... - sess.save(cat); - sess.flush(); // force the SQL INSERT + # connect to MySQL with authentication + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) - Active Record lets you: - - pkId = 1234 - cat = Cat.find(pkId) - # something even more interesting involving the same cat... - cat.save - - {Learn more}[link:classes/ActiveRecord/Base.html] - - -* Database abstraction through simple adapters (~100 lines) with a shared connector - - ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") - - ActiveRecord::Base.establish_connection( - :adapter => "mysql", - :host => "localhost", - :username => "me", - :password => "secret", - :database => "activerecord" - ) - - {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for - MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html]. + {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], + PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and + SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. * Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] @@ -169,11 +152,11 @@ A short rundown of the major features: class AddSystemSettings < ActiveRecord::Migration def self.up create_table :system_settings do |t| - t.string :name - t.string :label - t.text :value - t.string :type - t.integer :position + t.string :name + t.string :label + t.text :value + t.string :type + t.integer :position end SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1 @@ -186,110 +169,15 @@ A short rundown of the major features: {Learn more}[link:classes/ActiveRecord/Migration.html] -== Simple example (1/2): Defining tables and classes (using MySQL) - -Data definitions are specified only in the database. Active Record queries the database for -the column names (that then serves to determine which attributes are valid) on regular -object instantiation through the new constructor and relies on the column names in the rows -with the finders. - - # CREATE TABLE companies ( - # id int(11) unsigned NOT NULL auto_increment, - # client_of int(11), - # name varchar(255), - # type varchar(100), - # PRIMARY KEY (id) - # ) - -Active Record automatically links the "Company" object to the "companies" table - - class Company < ActiveRecord::Base - has_many :people, :class_name => "Person" - end - - class Firm < Company - has_many :clients - - def people_with_all_clients - clients.inject([]) { |people, client| people + client.people } - end - end - -The foreign_key is only necessary because we didn't use "firm_id" in the data definition - - class Client < Company - belongs_to :firm, :foreign_key => "client_of" - end - - # CREATE TABLE people ( - # id int(11) unsigned NOT NULL auto_increment, - # name text, - # company_id text, - # PRIMARY KEY (id) - # ) -Active Record will also automatically link the "Person" object to the "people" table - - class Person < ActiveRecord::Base - belongs_to :company - end - -== Simple example (2/2): Using the domain - -Picking a database connection for all the Active Records - - ActiveRecord::Base.establish_connection( - :adapter => "mysql", - :host => "localhost", - :username => "me", - :password => "secret", - :database => "activerecord" - ) - -Create some fixtures - - firm = Firm.new("name" => "Next Angle") - # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm") - firm.save - - client = Client.new("name" => "37signals", "client_of" => firm.id) - # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm") - client.save - -Lots of different finders - - # SQL: SELECT * FROM companies WHERE id = 1 - next_angle = Company.find(1) - - # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm' - next_angle = Firm.find(1) - - # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle' - next_angle = Company.find(:first, :conditions => "name = 'Next Angle'") - - next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first - -The supertype, Company, will return subtype instances - - Firm === next_angle - -All the dynamic methods added by the has_many macro - - next_angle.clients.empty? # true - next_angle.clients.size # total number of clients - all_clients = next_angle.clients - -Constrained finds makes access security easier when ID comes from a web-app - - # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2 - thirty_seven_signals = next_angle.clients.find(2) - -Bi-directional associations thanks to the "belongs_to" macro - - thirty_seven_signals.firm.nil? # true +== Philosophy +Active Record is an implementation of the object-relational mapping (ORM) +pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same +name described by Martin Fowler: -== Philosophy + "An object that wraps a row in a database table or view, + encapsulates the database access, and adds domain logic on that data." Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is object-relational mapping. The prime directive for this mapping has been to minimize -- cgit v1.2.3 From 6b11d0bf68ecb1960f6a6dc5b11bb8cc904df5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 22 Jul 2010 09:23:22 +0200 Subject: remove unneeded "Dependencies" section from Action Mailer README Other READMEs don't have dependencies indicated, and installing a gem takes care of dependencies automatically. For developers, dependencies are indicated in the Gemfile. --- actionmailer/README.rdoc | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index 1b9cb8a570..64b0333c0a 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -119,17 +119,6 @@ The Base class has the full list of configuration options. Here's an example: :authentication => :plain # :plain, :login or :cram_md5 } -== Dependencies - -Action Mailer depends on Action Pack. - -Additionally, Action Mailer requires the {Mail gem}[http://github.com/mikel/mail]. - -== Bundled software - -* Text::Format 0.63 by Austin Ziegler released under OpenSource - Read more on http://www.halostatue.ca/ruby/Text__Format.html - == Download and installation -- cgit v1.2.3 From 3e196db6ad57abcd9fc07ec2c5044b85bb13217e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 22 Jul 2010 09:59:58 +0200 Subject: improve Active Model README - fix indentation problems - revised introductory text to state right away what the library is - improved examples: reduce heavy usage of ellipsis, don't mark paragraphs of text as code --- activemodel/README.rdoc | 124 ++++++++++++++++++++---------------------------- 1 file changed, 51 insertions(+), 73 deletions(-) diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 6f162ef408..89cacbcab4 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -1,21 +1,21 @@ -= Active Model - defined interfaces for Rails - -Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have -an object interact with Action Pack helpers, it was required to either -copy chunks of code from Rails, or monkey patch entire helpers to make them -handle objects that did not look like Active Record. This generated code -duplication and fragile applications that broke on upgrades. - -Active Model is a solution for this problem. - -Active Model provides a known set of interfaces that your objects can implement -to then present a common interface to the Action Pack helpers. You can include -functionality from the following modules: - -* Adding attribute magic to your objects - - Add prefixes and suffixes to defined attribute methods... - += Active Model -- model interfaces for Rails + +Active Model provides a known set of interfaces for usage in model classes. +They allow for Action Pack helpers to interact with non-ActiveRecord models, +for example. Active Model also helps building custom ORMs for use outside of +the Rails framework. + +Prior to Rails 3.0, if a plugin or gem developer wanted to have an object +interact with Action Pack helpers, it was required to either copy chunks of +code from Rails, or monkey patch entire helpers to make them handle objects +that did not exacly conform to the Active Record interface. This would result +in code duplication and fragile applications that broke on upgrades. + +Active Model solves this. You can include functionality from the following +modules: + +* Add attribute magic to objects + class Person include ActiveModel::AttributeMethods @@ -23,17 +23,18 @@ functionality from the following modules: define_attribute_methods [:name, :age] attr_accessor :name, :age - + def clear_attribute(attr) send("#{attr}=", nil) end end - ...gives you clear_name, clear_age. + person.clear_name + person.clear_age {Learn more}[link:classes/ActiveModel/AttributeMethods.html] -* Adding callbacks to your objects +* Callbacks for certain operations class Person extend ActiveModel::Callbacks @@ -45,26 +46,16 @@ functionality from the following modules: end end end - - ...gives you before_create, around_create and after_create class methods that - wrap your create method. - + + This generates +before_create+, +around_create+ and +after_create+ + class methods that wrap your create method. + {Learn more}[link:classes/ActiveModel/CallBacks.html] -* For classes that already look like an Active Record object +* Tracking value changes - class Person - include ActiveModel::Conversion - end - - ...returns the class itself when sent :to_model - - {Learn more}[link:classes/ActiveModel/Conversion.html] + The ActiveModel::Dirty module allows for tracking attribute changes: -* Tracking changes in your object - - Provides all the value tracking features implemented by ActiveRecord... - person = Person.new person.name # => nil person.changed? # => false @@ -75,14 +66,14 @@ functionality from the following modules: person.name = 'robert' person.save person.previous_changes # => {'name' => ['bob, 'robert']} - + {Learn more}[link:classes/ActiveModel/Dirty.html] -* Adding +errors+ support to your object +* Adding +errors+ interface to objects - Provides the error messages to allow your object to interact with Action Pack - helpers seamlessly... - + Exposing error messages allows objects to interact with Action Pack + helpers seamlessly. + class Person def initialize @@ -102,51 +93,38 @@ functionality from the following modules: end - ... gives you... - person.errors.full_messages # => ["Name Can not be nil"] + person.errors.full_messages # => ["Name Can not be nil"] {Learn more}[link:classes/ActiveModel/Errors.html] -* Testing the compliance of your object +* Model name introspection - Use ActiveModel::Lint to test the compliance of your object to the - basic ActiveModel API... - - {Learn more}[link:classes/ActiveModel/Lint/Tests.html] - -* Providing a human face to your object - - ActiveModel::Naming provides your model with the model_name convention - and a human_name attribute... - class NamedPerson extend ActiveModel::Naming end - ...gives you... - NamedPerson.model_name #=> "NamedPerson" NamedPerson.model_name.human #=> "Named person" {Learn more}[link:classes/ActiveModel/Naming.html] -* Adding observer support to your objects +* Observer support - ActiveModel::Observers allows your object to implement the Observer - pattern in a Rails App and take advantage of all the standard observer - functions. + ActiveModel::Observers allows your object to implement the Observer + pattern in a Rails App and take advantage of all the standard observer + functions. {Learn more}[link:classes/ActiveModel/Observer.html] -* Making your object serializable +* Making objects serializable - ActiveModel::Serialization provides a standard interface for your object - to provide to_json or to_xml serialization... - + ActiveModel::Serialization provides a standard interface for your object + to provide +to_json+ or +to_xml+ serialization. + s = SerialPerson.new s.serializable_hash # => {"name"=>nil} s.to_json # => "{\"name\":null}" @@ -154,36 +132,36 @@ functionality from the following modules: {Learn more}[link:classes/ActiveModel/Serialization.html] -* Integrating with Rail's internationalization (i18n) handling through - ActiveModel::Translations... +* Internationalization (i18n) support class Person extend ActiveModel::Translation end + + Person.human_attribute_name('my_attribute') + #=> "My attribute" {Learn more}[link:classes/ActiveModel/Translation.html] -* Providing a full Validation stack for your objects... +* Validation support class Person include ActiveModel::Validations attr_accessor :first_name, :last_name - validates_each :first_name, :last_name do |record, attr, value| record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z end end - person = Person.new person.first_name = 'zoolander' - person.valid? #=> false + person.valid? #=> false {Learn more}[link:classes/ActiveModel/Validations.html] -* Make custom validators +* Custom validators class Person include ActiveModel::Validations @@ -196,7 +174,7 @@ functionality from the following modules: record.errors[:name] = "must exist" if record.name.blank? end end - + p = ValidatorPerson.new p.valid? #=> false p.errors.full_messages #=> ["Name must exist"] -- cgit v1.2.3 From 2d2bde9f543a1508e9b149751a4566780033e3f0 Mon Sep 17 00:00:00 2001 From: Ivan Torres Date: Thu, 22 Jul 2010 10:10:38 -0500 Subject: [PATCH] Update guides after Jeremy Kemper's changes on fieldWithErrors to field_with_errors [#157 state:resolved] --- railties/guides/source/active_record_validations_callbacks.textile | 4 ++-- railties/guides/source/configuring.textile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index c7ba130a90..37a65d211e 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -799,7 +799,7 @@ h4. Customizing the Error Messages CSS The selectors to customize the style of error messages are: -* +.fieldWithErrors+ - Style for the form fields and labels with errors. +* +.field_with_errors+ - Style for the form fields and labels with errors. * +#errorExplanation+ - Style for the +div+ element with the error messages. * +#errorExplanation h2+ - Style for the header of the +div+ element. * +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element. @@ -811,7 +811,7 @@ The name of the class and the id can be changed with the +:class+ and +:id+ opti h4. Customizing the Error Messages HTML -By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override that. +By default, form fields with errors are displayed enclosed by a +div+ element with the +field_with_errors+ CSS class. However, it's possible to override that. The way form fields with errors are treated is defined by +ActionView::Base.field_error_proc+. This is a +Proc+ that receives two parameters: diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 86655746e4..2ab28596d8 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -181,7 +181,7 @@ There are only a few configuration options for Action View, starting with four o * +config.action_view.warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+. -* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" } +* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>" } * +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. -- cgit v1.2.3 From 38734aff2005f1739ce7229db9d12363106f68c6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 22 Jul 2010 09:35:50 -0700 Subject: adding a test for session save --- activerecord/test/cases/session_store/sql_bypass.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb index 6c4a70b9fe..0059467bc0 100644 --- a/activerecord/test/cases/session_store/sql_bypass.rb +++ b/activerecord/test/cases/session_store/sql_bypass.rb @@ -32,6 +32,16 @@ module ActiveRecord s = SqlBypass.new :data => 'hello' assert s.loaded?, 'it is loaded' end + + def test_save + SqlBypass.create_table! unless Session.table_exists? + session_id = 20 + s = SqlBypass.new :data => 'hello', :session_id => session_id + s.save + t = SqlBypass.find_by_session_id session_id + assert_equal s.session_id, t.session_id + assert_equal s.data, t.data + end end end end -- cgit v1.2.3 From 97f3c7387e22c7752310a6b9c74ddb9fd3a8eb2d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 22 Jul 2010 09:38:56 -0700 Subject: refactor to use instance methods and be kind to subclasses --- activerecord/lib/active_record/session_store.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index b352f9529d..c6cf91c15d 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -265,22 +265,23 @@ module ActiveRecord def save return false unless loaded? marshaled_data = self.class.marshal(data) + connect = connection if @new_record @new_record = false - @@connection.update <<-end_sql, 'Create session' - INSERT INTO #{@@table_name} ( - #{@@connection.quote_column_name(@@session_id_column)}, - #{@@connection.quote_column_name(@@data_column)} ) + connect.update <<-end_sql, 'Create session' + INSERT INTO #{table_name} ( + #{connect.quote_column_name(session_id_column)}, + #{connect.quote_column_name(data_column)} ) VALUES ( - #{@@connection.quote(session_id)}, - #{@@connection.quote(marshaled_data)} ) + #{connect.quote(session_id)}, + #{connect.quote(marshaled_data)} ) end_sql else - @@connection.update <<-end_sql, 'Update session' - UPDATE #{@@table_name} - SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)} - WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)} + connect.update <<-end_sql, 'Update session' + UPDATE #{table_name} + SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)} + WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)} end_sql end end -- cgit v1.2.3 From ba8d89c4c8d1c038c0d6fc9dbfe22f6d528d0da9 Mon Sep 17 00:00:00 2001 From: Carlhuda Date: Wed, 21 Jul 2010 16:29:26 -0700 Subject: Performance optimizations to handle cases of instrumentors that are not listened to. Also, fix a possible concurrency issue. --- activesupport/lib/active_support/notifications.rb | 24 ++++++++++++++++++++-- .../lib/active_support/notifications/fanout.rb | 11 +++++++--- .../active_support/notifications/instrumenter.rb | 2 +- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 93d1907edc..886d7183eb 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -41,10 +41,30 @@ module ActiveSupport autoload :Event, 'active_support/notifications/instrumenter' autoload :Fanout, 'active_support/notifications/fanout' + @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) } + class << self attr_writer :notifier - delegate :publish, :subscribe, :unsubscribe, :to => :notifier - delegate :instrument, :to => :instrumenter + delegate :publish, :unsubscribe, :to => :notifier + + def instrument(name, payload = {}) + if @instrumenters[name] + instrumenter.instrument(name, payload) { yield payload if block_given? } + else + yield payload if block_given? + end + end + + def subscribe(*args, &block) + notifier.subscribe(*args, &block).tap do + @instrumenters.clear + end + end + + def unsubscribe(*args) + notifier.unsubscribe(*args) + @instrumenters.clear + end def notifier @notifier ||= Fanout.new diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 64f315cb6a..adc34f3286 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -9,15 +9,16 @@ module ActiveSupport end def subscribe(pattern = nil, block = Proc.new) - @listeners_for.clear - Subscriber.new(pattern, block).tap do |s| + subscriber = Subscriber.new(pattern, block).tap do |s| @subscribers << s end + @listeners_for.clear + subscriber end def unsubscribe(subscriber) - @listeners_for.clear @subscribers.reject! {|s| s.matches?(subscriber)} + @listeners_for.clear end def publish(name, *args) @@ -28,6 +29,10 @@ module ActiveSupport @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } end + def listening?(name) + listeners_for(name).any? + end + # This is a sync queue, so there is not waiting. def wait end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index e98189f899..713d5a5f25 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -19,7 +19,7 @@ module ActiveSupport def instrument(name, payload={}) begin @started = Time.now - yield(payload) if block_given? + yield rescue Exception => e payload[:exception] = [e.class.name, e.message] raise e -- cgit v1.2.3 From 21c4b0942f4014c86117c27a452e68094a05d538 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 14:57:42 -0300 Subject: Don't shadow outer local variables --- activesupport/lib/active_support/descendants_tracker.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 6cba84d79e..4d1cfacc95 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -11,9 +11,9 @@ module ActiveSupport end def self.descendants(klass) - @@direct_descendants[klass].inject([]) do |descendants, klass| - descendants << klass - descendants.concat klass.descendants + @@direct_descendants[klass].inject([]) do |descendants, _klass| + descendants << _klass + descendants.concat _klass.descendants end end @@ -40,4 +40,4 @@ module ActiveSupport DescendantsTracker.descendants(self) end end -end \ No newline at end of file +end -- cgit v1.2.3 From 1b8cd5fb53d0414b2dbbcaf0bc4f8851b8550e34 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 12:07:16 -0300 Subject: MySQL can't index a TEXT column --- activerecord/lib/active_record/session_store.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index c6cf91c15d..bb472f7ab6 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -83,7 +83,7 @@ module ActiveRecord connection.execute <<-end_sql CREATE TABLE #{table_name} ( id INTEGER PRIMARY KEY, - #{connection.quote_column_name(session_id_column)} TEXT UNIQUE, + #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE, #{connection.quote_column_name(data_column_name)} TEXT(255) ) end_sql @@ -220,7 +220,7 @@ module ActiveRecord connection.execute <<-end_sql CREATE TABLE #{table_name} ( id INTEGER PRIMARY KEY, - #{connection.quote_column_name(session_id_column)} TEXT UNIQUE, + #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE, #{connection.quote_column_name(data_column)} TEXT ) end_sql -- cgit v1.2.3 From 7113f207c34e41ffcbbd0b3570f3fe41bbfbbcaf Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 12:08:37 -0300 Subject: This is a VARCHAR not a TEXT --- activerecord/lib/active_record/session_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index bb472f7ab6..bd976acdbb 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -84,7 +84,7 @@ module ActiveRecord CREATE TABLE #{table_name} ( id INTEGER PRIMARY KEY, #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE, - #{connection.quote_column_name(data_column_name)} TEXT(255) + #{connection.quote_column_name(data_column_name)} VARCHAR(255) ) end_sql end -- cgit v1.2.3 From 24f303b677e4a8f0ecf8c335d95c63c3f7f1bdcc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 22 Jul 2010 13:00:29 -0300 Subject: Makes current adapter decide the syntax of PRIMARY KEY column definition --- activerecord/lib/active_record/session_store.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index bd976acdbb..365fcd6222 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -82,7 +82,7 @@ module ActiveRecord def create_table! connection.execute <<-end_sql CREATE TABLE #{table_name} ( - id INTEGER PRIMARY KEY, + id #{connection.type_to_sql(:primary_key)}, #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE, #{connection.quote_column_name(data_column_name)} VARCHAR(255) ) @@ -219,7 +219,7 @@ module ActiveRecord def create_table! connection.execute <<-end_sql CREATE TABLE #{table_name} ( - id INTEGER PRIMARY KEY, + id #{connection.type_to_sql(:primary_key)}, #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE, #{connection.quote_column_name(data_column)} TEXT ) -- cgit v1.2.3 From cf5c2bacb09bb6aa18cf5fa9b1abe022613e33c6 Mon Sep 17 00:00:00 2001 From: Andrew Kaspick Date: Thu, 22 Jul 2010 14:59:52 -0500 Subject: update remote_function docs referencing link_to_remote --- actionpack/lib/action_view/helpers/prototype_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 28b8a27eef..99f9363a9a 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -102,7 +102,7 @@ module ActionView :form, :with, :update, :script, :type ]).merge(CALLBACKS) # Returns the JavaScript needed for a remote function. - # Takes the same arguments as link_to_remote. + # See the link_to_remote documentation at http://github.com/rails/prototype_legacy_helper as it takes the same arguments. # # Example: # # Generates: ', text_field("developer", "name") + ) + end + def test_check_box assert_dom_equal( '', -- cgit v1.2.3 From 11614bddc015deff7095bdf8bbe9f11e5d81ae1c Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Sun, 1 Aug 2010 15:52:10 -0300 Subject: Fix label form helper to use I18n and html options, without the need of 'nil' text param: Before: f.label :title, nil, :class => 'title' After : f.label :title, :class => 'title' [#5267 state:committed] Signed-off-by: Santiago Pastorino --- actionpack/lib/action_view/helpers/form_helper.rb | 7 ++++--- actionpack/test/template/form_helper_test.rb | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index f8e60faa2a..ebe055bebd 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -215,7 +215,7 @@ module ActionView # ... # <% end %> # - # If your resource has associations defined, for example, you want to add comments + # If your resource has associations defined, for example, you want to add comments # to the post given that the routes are set correctly: # # <%= form_for([@document, @comment]) do |f| %> @@ -583,8 +583,9 @@ module ActionView # 'Accept Terms.' # end def label(object_name, method, content_or_options = nil, options = nil, &block) - if block_given? - options = content_or_options if content_or_options.is_a?(Hash) + content_is_options = content_or_options.is_a?(Hash) + if content_is_options || block_given? + options = content_or_options if content_is_options text = nil else text = content_or_options diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index f248a38ae9..9086a23345 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -120,6 +120,13 @@ class FormHelperTest < ActionView::TestCase I18n.locale = old_locale end + def test_label_with_locales_and_options + old_locale, I18n.locale = I18n.locale, :label + assert_dom_equal('', label(:post, :body, :class => 'post_body')) + ensure + I18n.locale = old_locale + end + def test_label_with_for_attribute_as_symbol assert_dom_equal('', label(:post, :title, nil, :for => "my_for")) end @@ -620,7 +627,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_symbol_object_name form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f| - concat f.label(:title) + concat f.label(:title, :class => 'post_title') concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -628,7 +635,7 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do - "" + + "" + "" + "" + "" + -- cgit v1.2.3 From 111234e7c140398dba3bed4936e5d4d03681994c Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 1 Aug 2010 21:56:06 -0300 Subject: Use AS::OrderedHash when trusting in the order of the hash --- actionpack/test/controller/test_test.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 950ad9266f..13c9d9ee38 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'controller/fake_controllers' +require 'active_support/ordered_hash' class TestTest < ActionController::TestCase class TestController < ActionController::Base @@ -137,14 +138,14 @@ XML end def test_raw_post_handling - params = {:page => {:name => 'page name'}, 'some key' => 123} + params = ActiveSupport::OrderedHash[:page, {:name => 'page name'}, 'some key', 123] post :render_raw_post, params.dup assert_equal params.to_query, @response.body end def test_body_stream - params = { :page => { :name => 'page name' }, 'some key' => 123 } + params = ActiveSupport::OrderedHash[:page, { :name => 'page name' }, 'some key', 123] post :render_body, params.dup -- cgit v1.2.3 From 3b44b52fb0f09e437645d12d2feb6b6d37a804ee Mon Sep 17 00:00:00 2001 From: "Prashant P. Shah" Date: Fri, 30 Jul 2010 15:27:59 +0530 Subject: Corrected the rake test:units and test:functionals description [#5251 state:committed] Signed-off-by: Santiago Pastorino --- railties/lib/rails/test_unit/testing.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index ecd513c2c8..38c14fcd6b 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -70,7 +70,7 @@ module Kernel end end -desc 'Runs test:unit, test:functional, test:integration together (also available: test:benchmark, test:profile, test:plugins)' +desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile, test:plugins)' task :test do errors = %w(test:units test:functionals test:integration).collect do |task| begin -- cgit v1.2.3 From b29c23a618731a4e7e49f79c617c5eb3714d7d8d Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Sun, 1 Aug 2010 21:32:36 -0400 Subject: ensuring that documentation does not exceed 100 columns --- activerecord/lib/active_record/aggregations.rb | 13 +- .../lib/active_record/association_preload.rb | 16 +- activerecord/lib/active_record/associations.rb | 447 ++++++++++++--------- 3 files changed, 282 insertions(+), 194 deletions(-) diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 087462a5bb..83a9ab46c5 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -11,11 +11,11 @@ module ActiveRecord # Active Record implements aggregation through a macro-like class method called +composed_of+ # for representing attributes as value objects. It expresses relationships like "Account [is] - # composed of Money [among other things]" or "Person [is] - # composed of [an] address". Each call to the macro adds a description of how the value objects - # are created from the attributes of the entity object (when the entity is initialized either - # as a new object or from finding an existing object) and how it can be turned back into attributes - # (when the entity is saved to the database). + # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call + # to the macro adds a description of how the value objects are created from the attributes of + # the entity object (when the entity is initialized either as a new object or from finding an + # existing object) and how it can be turned back into attributes (when the entity is saved to + # the database). # # class Customer < ActiveRecord::Base # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount) @@ -83,8 +83,7 @@ module ActiveRecord # customer.balance < Money.new(5) # => false # # Value objects can also be composed of multiple attributes, such as the case of Address. The order - # of the mappings will - # determine the order of the parameters. + # of the mappings will determine the order of the parameters. # # customer.address_street = "Hyancintvej" # customer.address_city = "Copenhagen" diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 08601f8ef9..0f0fdc2e21 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -9,8 +9,8 @@ module ActiveRecord # Implements the details of eager loading of Active Record associations. # Application developers should not use this module directly. # - # ActiveRecord::Base is extended with this module. The source code in - # ActiveRecord::Base references methods defined in this module. + # ActiveRecord::Base is extended with this module. The source code in + # ActiveRecord::Base references methods defined in this module. # # Note that 'eager loading' and 'preloading' are actually the same thing. # However, there are two different eager loading strategies. @@ -55,7 +55,7 @@ module ActiveRecord # == Parameters # +records+ is an array of ActiveRecord::Base. This array needs not be flat, # i.e. +records+ itself may also contain arrays of records. In any case, - # +preload_associations+ will preload the associations all records by + # +preload_associations+ will preload the all associations records by # flattening +records+. # # +associations+ specifies one or more associations that you want to @@ -110,8 +110,8 @@ module ActiveRecord def preload_one_association(records, association, preload_options={}) class_to_reflection = {} # Not all records have the same class, so group then preload - # group on the reflection itself so that if various subclass share the same association then we do not split them - # unnecessarily + # group on the reflection itself so that if various subclass share the same association then + # we do not split them unnecessarily records.group_by { |record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, _records| raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection @@ -149,7 +149,8 @@ module ActiveRecord seen_keys = {} associated_records.each do |associated_record| #this is a has_one or belongs_to: there should only be one record. - #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please + #Unfortunately we can't (in portable way) ask the database for + #'all records where foo_id in (x,y,z), but please # only one row per distinct foo_id' so this where we enforce that next if seen_keys[associated_record[key].to_s] seen_keys[associated_record[key].to_s] = true @@ -304,7 +305,8 @@ module ActiveRecord polymorph_type = options[:foreign_type] klasses_and_ids = {} - # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records + # Construct a mapping from klass to a list of ids to load and a mapping of those ids back + # to their parent_records records.each do |record| if klass = record.send(polymorph_type) klass_id = record.send(primary_key_name) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9b9b843b42..05d0200f90 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -136,10 +136,12 @@ module ActiveRecord instance_variable_set("@#{name}", association) end - # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like - # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are - # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr* - # methods. Example: + # Associations are a set of macro-like class methods for tying objects together through + # foreign keys. They express relationships like "Project has one Project Manager" + # or "Project belongs to a Portfolio". Each macro adds a number of methods to the + # class which are specialized according to the collection or association symbol and the + # options hash. It works much the same way as Ruby's own attr* + # methods. # # class Project < ActiveRecord::Base # belongs_to :portfolio @@ -148,7 +150,8 @@ module ActiveRecord # has_and_belongs_to_many :categories # end # - # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships: + # The project class now has the following methods (and more) to ease the traversal and + # manipulation of its relationships: # * Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil? # * Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?, # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), @@ -159,8 +162,9 @@ module ActiveRecord # # === A word of warning # - # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association - # adds a method with that name to its model, it will override the inherited method and break things. + # Don't create associations that have the same name as instance methods of + # ActiveRecord::Base. Since the association adds a method with that name to + # its model, it will override the inherited method and break things. # For instance, +attributes+ and +connection+ would be bad choices for association names. # # == Auto-generated methods @@ -270,8 +274,8 @@ module ActiveRecord # # == Is it a +belongs_to+ or +has_one+ association? # - # Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class - # declaring the +belongs_to+ relationship. Example: + # Both express a 1-1 relationship. The difference is mostly where to place the foreign + # key, which goes on the table for the class declaring the +belongs_to+ relationship. # # class User < ActiveRecord::Base # # I reference an account. @@ -300,8 +304,9 @@ module ActiveRecord # # == Unsaved objects and associations # - # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be - # aware of, mostly involving the saving of associated objects. + # You can manipulate objects and associations before they are saved to the database, but + # there is some special behavior you should be aware of, mostly involving the saving of + # associated objects. # # You can set the :autosave option on a has_one, belongs_to, # has_many, or has_and_belongs_to_many association. Setting it @@ -310,26 +315,33 @@ module ActiveRecord # # === One-to-one associations # - # * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in - # order to update their primary keys - except if the parent object is unsaved (new_record? == true). - # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment - # is cancelled. - # * If you wish to assign an object to a +has_one+ association without saving it, use the association.build method (documented below). - # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It - # does not save the parent either. + # * Assigning an object to a +has_one+ association automatically saves that object and + # the object being replaced (if there is one), in order to update their primary + # keys - except if the parent object is unsaved (new_record? == true). + # * If either of these saves fail (due to one of the objects being invalid) the assignment + # statement returns +false+ and the assignment is cancelled. + # * If you wish to assign an object to a +has_one+ association without saving it, + # use the association.build method (documented below). + # * Assigning an object to a +belongs_to+ association does not save the object, since + # the foreign key field belongs on the parent. It does not save the parent either. # # === Collections # - # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object - # (the owner of the collection) is not yet stored in the database. - # * If saving any of the objects being added to a collection (via push or similar) fails, then push returns +false+. - # * You can add an object to a collection without automatically saving it by using the collection.build method (documented below). - # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved. + # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. + # * If saving any of the objects being added to a collection (via push or similar) + # fails, then push returns +false+. + # * You can add an object to a collection without automatically saving it by using the + # collection.build method (documented below). + # * All unsaved (new_record? == true) members of the collection are automatically + # saved when the parent is saved. # # === Association callbacks # - # Similar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get - # triggered when you add an object to or remove an object from an association collection. Example: + # Similar to the normal callbacks that hook into the lifecycle of an Active Record object, + # you can also define callbacks that get triggered when you add an object to or remove an + # object from an association collection. # # class Project # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity @@ -342,19 +354,21 @@ module ActiveRecord # It's possible to stack callbacks by passing them as an array. Example: # # class Project - # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # has_and_belongs_to_many :developers, + # :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] # end # # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. # - # Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with - # the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed. + # Should any of the +before_add+ callbacks throw an exception, the object does not get + # added to the collection. Same with the +before_remove+ callbacks; if an exception is + # thrown the object doesn't get removed. # # === Association extensions # - # The proxy objects that control the access to associations can be extended through anonymous modules. This is especially - # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association. - # Example: + # The proxy objects that control the access to associations can be extended through anonymous + # modules. This is especially beneficial for adding new finders, creators, and other + # factory-type methods that are only used as part of this association. # # class Account < ActiveRecord::Base # has_many :people do @@ -369,7 +383,8 @@ module ActiveRecord # person.first_name # => "David" # person.last_name # => "Heinemeier Hansson" # - # If you need to share the same extensions between many associations, you can use a named extension module. Example: + # If you need to share the same extensions between many associations, you can use a named + # extension module. # # module FindOrCreateByNameExtension # def find_or_create_by_name(name) @@ -386,9 +401,10 @@ module ActiveRecord # has_many :people, :extend => FindOrCreateByNameExtension # end # - # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option. - # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede - # those earlier in the array. Example: + # If you need to use multiple named extension modules, you can specify an array of modules + # with the :extend option. + # In the case of name conflicts between methods in the modules, methods in modules later + # in the array supercede those earlier in the array. # # class Account < ActiveRecord::Base # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension] @@ -399,12 +415,14 @@ module ActiveRecord # # * +proxy_owner+ - Returns the object the association is part of. # * +proxy_reflection+ - Returns the reflection object that describes the association. - # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+. + # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or + # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+. # # === Association Join Models # - # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This - # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations, + # Has Many associations can be configured with the :through option to use an + # explicit join model to retrieve the data. This operates similarly to a + # +has_and_belongs_to_many+ association. The advantage is that you're able to add validations, # callbacks, and extra attributes on the join model. Consider the following schema: # # class Author < ActiveRecord::Base @@ -418,7 +436,7 @@ module ActiveRecord # end # # @author = Author.find :first - # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to. + # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to # @author.books # selects all books by using the Authorship join model # # You can also go through a +has_many+ association on the join model: @@ -439,7 +457,7 @@ module ActiveRecord # # @firm = Firm.find :first # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm - # @firm.invoices # selects all invoices by going through the Client join model. + # @firm.invoices # selects all invoices by going through the Client join model # # Similarly you can go through a +has_one+ association on the join model: # @@ -461,16 +479,18 @@ module ActiveRecord # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group # @group.avatars # selects all avatars by going through the User join model. # - # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are - # *read-only*. For example, the following would not work following the previous example: + # An important caveat with going through +has_one+ or +has_many+ associations on the + # join model is that these associations are *read-only*. For example, the following + # would not work following the previous example: # - # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around. + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around # @group.avatars.delete(@group.avatars.last) # so would this # # === Polymorphic Associations # - # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they - # specify an interface that a +has_many+ association must adhere to. + # Polymorphic associations on models are not restricted on what types of models they + # can be associated with. Rather, they specify an interface that a +has_many+ association + # must adhere to. # # class Asset < ActiveRecord::Base # belongs_to :attachable, :polymorphic => true @@ -482,13 +502,16 @@ module ActiveRecord # # @asset.attachable = @post # - # 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 - # an +attachable_id+ integer column and an +attachable_type+ string column. + # 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 an +attachable_id+ integer column and an + # +attachable_type+ string column. # - # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order - # for the associations to work as expected, ensure that you store the base model for the STI models in the - # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts - # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table. + # Using polymorphic associations in combination with single table inheritance (STI) is + # a little tricky. In order for the associations to work as expected, ensure that you + # store the base model for the STI models in the type column of the polymorphic + # association. To continue with the asset example above, suppose there are guest posts + # and member posts that use the posts table for STI. In this case, there must be a +type+ + # column in the posts table. # # class Asset < ActiveRecord::Base # belongs_to :attachable, :polymorphic => true @@ -511,9 +534,10 @@ module ActiveRecord # # == Caching # - # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically - # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without - # worrying too much about performance at the first go. Example: + # All of the methods are built on a simple caching principle that will keep the result + # of the last query around unless specifically instructed not to. The cache is even + # shared across methods to make it even cheaper to use the macro-added methods without + # worrying too much about performance at the first go. # # project.milestones # fetches milestones from the database # project.milestones.size # uses the milestone cache @@ -523,9 +547,10 @@ module ActiveRecord # # == Eager loading of associations # - # Eager loading is a way to find objects of a certain class and a number of named associations. This is - # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author - # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example: + # Eager loading is a way to find objects of a certain class and a number of named associations. + # This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 + # posts that each need to display their author triggers 101 database queries. Through the + # use of eager loading, the 101 queries can be reduced to 2. # # class Post < ActiveRecord::Base # belongs_to :author @@ -540,44 +565,55 @@ module ActiveRecord # puts "Last comment on: " + post.comments.first.created_on # end # - # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author: + # To iterate over these one hundred posts, we'll generate 201 database queries. Let's + # first just optimize it for retrieving the author: # # for post in Post.find(:all, :include => :author) # - # This references the name of the +belongs_to+ association that also used the :author symbol. After loading the posts, find - # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102. + # This references the name of the +belongs_to+ association that also used the :author + # symbol. After loading the posts, find will collect the +author_id+ from each one and load + # all the referenced authors with one query. Doing so will cut down the number of queries + # from 201 to 102. # # We can improve upon the situation further by referencing both associations in the finder with: # # for post in Post.find(:all, :include => [ :author, :comments ]) # - # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries - # will be 1 plus the number of associations named (except if some of the associations are polymorphic +belongs_to+ - see below). + # This will load all comments with a single query. This reduces the total number of queries + # to 3. More generally the number of queries will be 1 plus the number of associations + # named (except if some of the associations are polymorphic +belongs_to+ - see below). # # To include a deep hierarchy of associations, use a hash: # # for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ]) # - # That'll grab not only all the comments but all their authors and gravatar pictures. You can mix and match - # symbols, arrays and hashes in any combination to describe the associations you want to load. + # That'll grab not only all the comments but all their authors and gravatar pictures. + # You can mix and match symbols, arrays and hashes in any combination to describe the + # associations you want to load. # - # All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced - # 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 - # 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. + # All of this power shouldn't fool you into thinking that you can pull out huge amounts + # of data with no performance penalty just because you've reduced 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 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. # - # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case - # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example + # Since only one table is loaded at a time, conditions or orders cannot reference tables + # other than the main one. If this is the case Active Record falls back to the previously + # used LEFT OUTER JOIN based strategy. For example # # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true]) # - # This will result in a single SQL query with joins along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id and - # LEFT OUTER JOIN authors ON authors.id = posts.author_id. Note that using conditions like this can have unintended consequences. - # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole - # and not just to the association. You must disambiguate column references for this fallback to happen, for example + # This will result in a single SQL query with joins along the lines of: + # LEFT OUTER JOIN comments ON comments.post_id = posts.id and + # LEFT OUTER JOIN authors ON authors.id = posts.author_id. Note that using conditions + # like this can have unintended consequences. + # In the above example posts with no approved comments are not returned at all, because + # the conditions apply to the SQL statement as a whole and not just to the association. + # You must disambiguate column references for this fallback to happen, for example # :order => "author.name DESC" will work but :order => "name DESC" will not. # - # If you do want eager load only some members of an association it is usually more natural to :include an association - # which has conditions defined on it: + # If you do want eager load only some members of an association it is usually more natural + # to :include an association which has conditions defined on it: # # class Post < ActiveRecord::Base # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true] @@ -585,9 +621,11 @@ module ActiveRecord # # Post.find(:all, :include => :approved_comments) # - # This will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved. + # This will load posts and eager load the +approved_comments+ association, which contains + # only those comments that have been approved. # - # If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects: + # If you eager load an association with a specified :limit option, it will be ignored, + # returning all the associated objects: # # class Picture < ActiveRecord::Base # has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10 @@ -595,8 +633,8 @@ module ActiveRecord # # Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments. # - # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated - # before the actual model exists. + # When eager loaded, conditions are interpolated in the context of the model class, not + # the model instance. Conditions are lazily interpolated before the actual model exists. # # Eager loading is supported with polymorphic associations. # @@ -608,17 +646,21 @@ module ActiveRecord # # Address.find(:all, :include => :addressable) # - # This will execute one query to load the addresses and load the addressables with one query per addressable type. - # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of - # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback - # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent - # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query. + # This will execute one query to load the addresses and load the addressables with one + # query per addressable type. + # For example if all the addressables are either of class Person or Company then a total + # of 3 queries will be executed. The list of addressable types to load is determined on + # the back of the addresses loaded. This is not supported if Active Record has to fallback + # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. + # The reason is that the parent model's type is a column value so its corresponding table + # name cannot be put in the +FROM+/+JOIN+ clauses of that query. # # == Table Aliasing # - # Active Record uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once, - # the standard table name is used. The second time, the table is aliased as #{reflection_name}_#{parent_table_name}. Indexes are appended - # for any more successive uses of the table name. + # Active Record uses table aliasing in the case that a table is referenced multiple times + # in a join. If a table is referenced only once, the standard table name is used. The + # second time, the table is aliased as #{reflection_name}_#{parent_table_name}. + # Indexes are appended for any more successive uses of the table name. # # Post.find :all, :joins => :comments # # => SELECT ... FROM posts INNER JOIN comments ON ... @@ -651,7 +693,8 @@ module ActiveRecord # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 # - # If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations: + # If you wish to specify your own custom joins using a :joins option, those table + # names will take precedence over the eager associations: # # Post.find :all, :joins => :comments, :joins => "inner join comments ..." # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... @@ -660,7 +703,8 @@ module ActiveRecord # INNER JOIN comments special_comments_posts ... # INNER JOIN comments ... # - # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database. + # Table aliases are automatically truncated according to the maximum length of table identifiers + # according to the specific database. # # == Modules # @@ -676,9 +720,10 @@ module ActiveRecord # end # end # - # When Firm#clients is called, it will in turn call MyApplication::Business::Client.find_all_by_firm_id(firm.id). - # If you want to associate with a class in another module scope, this can be done by specifying the complete class name. - # Example: + # When Firm#clients is called, it will in turn call + # MyApplication::Business::Client.find_all_by_firm_id(firm.id). + # If you want to associate with a class in another module scope, this can be done by + # specifying the complete class name. # # module MyApplication # module Business @@ -694,8 +739,8 @@ module ActiveRecord # # == Bi-directional associations # - # When you specify an association there is usually an association on the associated model that specifies the same - # relationship in reverse. For example, with the following models: + # When you specify an association there is usually an association on the associated model + # that specifies the same relationship in reverse. For example, with the following models: # # class Dungeon < ActiveRecord::Base # has_many :traps @@ -710,9 +755,11 @@ module ActiveRecord # belongs_to :dungeon # end # - # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are the inverse of each other and the - # inverse of the +dungeon+ association on +EvilWizard+ is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, - # Active Record doesn't know anything about these inverse relationships and so no object loading optimisation is possible. For example: + # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are + # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+ + # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, + # Active Record doesn't know anything about these inverse relationships and so no object + # loading optimisation is possible. For example: # # d = Dungeon.first # t = d.traps.first @@ -720,9 +767,11 @@ module ActiveRecord # d.level = 10 # d.level == t.dungeon.level # => false # - # The +Dungeon+ instances +d+ and t.dungeon in the above example refer to the same object data from the database, but are - # actually different in-memory copies of that data. Specifying the :inverse_of option on associations lets you tell - # Active Record about inverse relationships and it will optimise object loading. For example, if we changed our model definitions to: + # The +Dungeon+ instances +d+ and t.dungeon in the above example refer to + # the same object data from the database, but are actually different in-memory copies + # of that data. Specifying the :inverse_of option on associations lets you tell + # Active Record about inverse relationships and it will optimise object loading. For + # example, if we changed our model definitions to: # # class Dungeon < ActiveRecord::Base # has_many :traps, :inverse_of => :dungeon @@ -737,8 +786,8 @@ module ActiveRecord # belongs_to :dungeon, :inverse_of => :evil_wizard # end # - # Then, from our code snippet above, +d+ and t.dungeon are actually the same in-memory instance and our final d.level == t.dungeon.level - # will return +true+. + # Then, from our code snippet above, +d+ and t.dungeon are actually the same + # in-memory instance and our final d.level == t.dungeon.level will return +true+. # # There are limitations to :inverse_of support: # @@ -748,13 +797,13 @@ module ActiveRecord # # == Type safety with ActiveRecord::AssociationTypeMismatch # - # If you attempt to assign an object to an association that doesn't match the inferred or specified :class_name, you'll - # get an ActiveRecord::AssociationTypeMismatch. + # If you attempt to assign an object to an association that doesn't match the inferred + # or specified :class_name, you'll get an ActiveRecord::AssociationTypeMismatch. # # == Options # - # All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones - # possible. + # All of the association macros can be specialized through options. This makes cases + # more complex than the simple and guessable ones possible. module ClassMethods # Specifies a one-to-many association. The following methods for retrieval and query of # collections of associated objects will be added: @@ -828,20 +877,22 @@ module ActiveRecord # === Supported options # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So has_many :products will by default be linked to the Product class, but - # if the real class name is SpecialProduct, you'll have to specify it with this option. + # from the association name. So has_many :products will by default be linked + # to the Product class, but if the real class name is SpecialProduct, you'll have to + # specify it with this option. # [:conditions] # Specify the conditions that the associated objects must meet in order to be included as a +WHERE+ - # SQL fragment, such as price > 5 AND name LIKE 'B%'. Record creations from the association are scoped if a hash - # is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create - # or @blog.posts.build. + # SQL fragment, such as price > 5 AND name LIKE 'B%'. Record creations from + # the association are scoped if a hash is used. + # has_many :posts, :conditions => {:published => true} will create published + # posts with @blog.posts.create or @blog.posts.build. # [:order] # Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, # such as last_name, first_name DESC. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id" - # as the default :foreign_key. + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ + # association will use "person_id" as the default :foreign_key. # [:primary_key] # Specify the method that returns the primary key used for the association. By default this is +id+. # [:dependent] @@ -855,10 +906,12 @@ module ActiveRecord # # [:finder_sql] # Specify a complete SQL statement to fetch the association. This is a good way to go for complex - # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added. + # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ + # is _not_ added. # [:counter_sql] # Specify a complete SQL statement to fetch the size of the association. If :finder_sql is - # specified but not :counter_sql, :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM. + # specified but not :counter_sql, :counter_sql will be generated by + # replacing SELECT ... FROM with SELECT COUNT(*) FROM. # [:extend] # Specify a named module for extending the proxy. See "Association extensions". # [:include] @@ -866,25 +919,31 @@ module ActiveRecord # [:group] # An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. # [:having] - # Combined with +:group+ this can be used to filter the records that a GROUP BY returns. Uses the HAVING SQL-clause. + # Combined with +:group+ this can be used to filter the records that a GROUP BY + # returns. Uses the HAVING SQL-clause. # [:limit] # An integer determining the limit on the number of rows that should be returned. # [:offset] - # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # An integer determining the offset from where the rows should be fetched. So at 5, + # it would skip the first 4 rows. # [:select] - # By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join - # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. + # By default, this is * as in SELECT * FROM, but can be changed if + # you, for example, want to do a join but not include the joined columns. Do not forget + # to include the primary and foreign keys, otherwise it will raise an error. # [:as] # Specifies a polymorphic interface (See belongs_to). # [:through] - # Specifies a join model through which to perform the query. Options for :class_name and :foreign_key - # are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to - # has_one or has_many association on the join model. The collection of join models can be managed via the collection - # API. For example, new join models are created for newly associated objects, and if some are gone their rows are deleted (directly, + # Specifies a join model through which to perform the query. Options for :class_name + # and :foreign_key are ignored, as the association uses the source reflection. You + # can only use a :through query through a belongs_to, has_one + # or has_many association on the join model. The collection of join models + # can be managed via the collection API. For example, new join models are created for + # newly associated objects, and if some are gone their rows are deleted (directly, # no destroy callbacks are triggered). # [:source] - # Specifies the source association name used by has_many :through queries. Only use it if the name cannot be - # inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or + # Specifies the source association name used by has_many :through queries. + # Only use it if the name cannot be inferred from the association. + # has_many :subscribers, :through => :subscriptions will look for either :subscribers or # :subscriber on Subscription, unless a :source is given. # [:source_type] # Specifies type of the source association used by has_many :through queries where the source @@ -896,12 +955,14 @@ module ActiveRecord # [:validate] # If false, don't validate the associated objects when saving the parent object. true by default. # [:autosave] - # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object. + # If true, always save the associated objects or destroy them if marked for destruction, + # when saving the parent object. # If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. # [:inverse_of] - # Specifies the name of the belongs_to association on the associated object that is the inverse of this has_many - # association. Does not work in combination with :through or :as options. + # Specifies the name of the belongs_to association on the associated object + # that is the inverse of this has_many association. Does not work in combination + # with :through or :as options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # # Option examples: @@ -975,19 +1036,20 @@ module ActiveRecord # [:conditions] # Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as rank = 5. Record creation from the association is scoped if a hash - # is used. has_one :account, :conditions => {:enabled => true} will create an enabled account with @company.create_account - # or @company.build_account. + # is used. has_one :account, :conditions => {:enabled => true} will create + # an enabled account with @company.create_account or @company.build_account. # [:order] # Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, # such as last_name, first_name DESC. # [:dependent] # If set to :destroy, the associated object is destroyed when this object is. If set to - # :delete, the associated object is deleted *without* calling its destroy method. If set to :nullify, the associated - # object's foreign key is set to +NULL+. Also, association is assigned. + # :delete, the associated object is deleted *without* calling its destroy method. + # If set to :nullify, the associated object's foreign key is set to +NULL+. + # Also, association is assigned. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id" - # as the default :foreign_key. + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association + # will use "person_id" as the default :foreign_key. # [:primary_key] # Specify the method that returns the primary key used for the association. By default this is +id+. # [:include] @@ -995,15 +1057,18 @@ module ActiveRecord # [:as] # Specifies a polymorphic interface (See belongs_to). # [:select] - # By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join - # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. + # By default, this is * as in SELECT * FROM, but can be changed if, for example, + # you want to do a join but not include the joined columns. Do not forget to include the + # primary and foreign keys, otherwise it will raise an error. # [:through] - # Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key - # are ignored, as the association uses the source reflection. You can only use a :through query through a - # has_one or belongs_to association on the join model. + # Specifies a Join Model through which to perform the query. Options for :class_name + # and :foreign_key are ignored, as the association uses the source reflection. You + # can only use a :through query through a has_one or belongs_to + # association on the join model. # [:source] - # Specifies the source association name used by has_one :through queries. Only use it if the name cannot be - # inferred from the association. has_one :favorite, :through => :favorites will look for a + # Specifies the source association name used by has_one :through queries. + # Only use it if the name cannot be inferred from the association. + # has_one :favorite, :through => :favorites will look for a # :favorite on Favorite, unless a :source is given. # [:source_type] # Specifies type of the source association used by has_one :through queries where the source @@ -1013,17 +1078,19 @@ module ActiveRecord # [:validate] # If false, don't validate the associated object when saving the parent object. +false+ by default. # [:autosave] - # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. - # If false, never save or destroy the associated object. + # If true, always save the associated object or destroy it if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated object. # By default, only save the associated object if it's a new record. # [:inverse_of] - # Specifies the name of the belongs_to association on the associated object that is the inverse of this has_one - # association. Does not work in combination with :through or :as options. + # Specifies the name of the belongs_to association on the associated object + # that is the inverse of this has_one association. Does not work in combination + # with :through or :as options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card - # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it + # has_one :credit_card, :dependent => :nullify # updates the associated records foreign + # # key value to NULL rather than destroying it # has_one :last_comment, :class_name => "Comment", :order => "posted_on" # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'" # has_one :attachment, :as => :attachable @@ -1085,27 +1152,34 @@ module ActiveRecord # Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as authorized = 1. # [:select] - # By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join - # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. + # By default, this is * as in SELECT * FROM, but can be changed + # if, for example, you want to do a join but not include the joined columns. Do not + # forget to include the primary and foreign keys, otherwise it will raise an error. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name - # of the association with an "_id" suffix. So a class that defines a belongs_to :person association will use - # "person_id" as the default :foreign_key. Similarly, belongs_to :favorite_person, :class_name => "Person" - # will use a foreign key of "favorite_person_id". + # of the association with an "_id" suffix. So a class that defines a belongs_to :person + # association will use "person_id" as the default :foreign_key. Similarly, + # belongs_to :favorite_person, :class_name => "Person" will use a foreign key + # of "favorite_person_id". # [:primary_key] - # Specify the method that returns the primary key of associated object used for the association. By default this is id. + # Specify the method that returns the primary key of associated object used for the association. + # By default this is id. # [:dependent] # If set to :destroy, the associated object is destroyed when this object is. If set to - # :delete, the associated object is deleted *without* calling its destroy method. This option should not be specified when - # belongs_to is used in conjunction with a has_many relationship on another class because of the potential to leave + # :delete, the associated object is deleted *without* calling its destroy method. + # This option should not be specified when belongs_to is used in conjunction with + # a has_many relationship on another class because of the potential to leave # orphaned records behind. # [:counter_cache] # Caches the number of belonging objects on the associate class through the use of +increment_counter+ - # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's - # destroyed. This requires that a column named #{table_name}_count (such as +comments_count+ for a belonging Comment class) - # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing - # a column name instead of a +true+/+false+ value to this option (e.g., :counter_cache => :my_custom_counter.) - # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+. + # and +decrement_counter+. The counter cache is incremented when an object of this + # class is created and decremented when it's destroyed. This requires that a column + # named #{table_name}_count (such as +comments_count+ for a belonging Comment class) + # is used on the associate class (such as a Post class). You can also specify a custom counter + # cache column by providing a column name instead of a +true+/+false+ value to this + # option (e.g., :counter_cache => :my_custom_counter.) + # Note: Specifying a counter cache will add it to that model's list of readonly attributes + # using +attr_readonly+. # [:include] # Specify second-order associations that should be eager loaded when this object is loaded. # [:polymorphic] @@ -1117,15 +1191,18 @@ module ActiveRecord # [:validate] # If false, don't validate the associated objects when saving the parent object. +false+ by default. # [:autosave] - # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. + # If true, always save the associated object or destroy it if marked for destruction, when + # saving the parent object. # If false, never save or destroy the associated object. # By default, only save the associated object if it's a new record. # [:touch] - # If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or - # destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute. + # If true, the associated object will be touched (the updated_at/on attributes set to now) + # when this record is either saved or destroyed. If you specify a symbol, that attribute + # will be updated with the current time instead of the updated_at/on attribute. # [:inverse_of] - # Specifies the name of the has_one or has_many association on the associated object that is the inverse of this belongs_to - # association. Does not work in combination with the :polymorphic options. + # Specifies the name of the has_one or has_many association on the associated + # object that is the inverse of this belongs_to association. Does not work in + # combination with the :polymorphic options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # # Option examples: @@ -1159,9 +1236,10 @@ module ActiveRecord # Specifies a many-to-many relationship with another class. This associates two classes via an # intermediate join table. Unless the join table is explicitly specified as an option, it is # guessed using the lexical order of the class names. So a join between Developer and Project - # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence - # is calculated using the < operator for String. This means that if the strings are of different lengths, - # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher + # will give the default join table name of "developers_projects" because "D" outranks "P". + # Note that this precedence is calculated using the < operator for String. This + # means that if the strings are of different lengths, and the strings are equal when compared + # up to the shortest length, then the longer string is considered of higher # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the @@ -1183,9 +1261,10 @@ module ActiveRecord # end # end # - # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through - # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as - # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any + # Deprecated: Any additional fields added to the join table will be placed as attributes when + # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join + # tables with additional attributes will be marked as readonly (because we can't save changes + # to the additional attributes). It's strongly recommended that you upgrade any # associations with attributes to a real join model (see introduction). # # Adds the following methods for retrieval and query: @@ -1225,7 +1304,8 @@ module ActiveRecord # with +attributes+ and linked to this object through the join table, but has not yet been saved. # [collection.create(attributes = {})] # Returns a new object of the collection type that has been instantiated - # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation). + # with +attributes+, linked to this object through the join table, and that has already been + # saved (if it passed the validation). # # (+collection+ is replaced with the symbol passed as the first argument, so # has_and_belongs_to_many :categories would add among others categories.empty?.) @@ -1260,8 +1340,9 @@ module ActiveRecord # MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work. # [:foreign_key] # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association - # to Project will use "person_id" as the default :foreign_key. + # of this class in lower-case and "_id" suffixed. So a Person class that makes + # a +has_and_belongs_to_many+ association to Project will use "person_id" as the + # default :foreign_key. # [:association_foreign_key] # Specify the foreign key used for the association on the receiving side of the association. # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. @@ -1269,7 +1350,8 @@ module ActiveRecord # the association will use "project_id" as the default :association_foreign_key. # [:conditions] # Specify the conditions that the associated object must meet in order to be included as a +WHERE+ - # SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used. + # SQL fragment, such as authorized = 1. Record creations from the association are + # scoped if a hash is used. # has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create # or @blog.posts.build. # [:order] @@ -1281,7 +1363,8 @@ module ActiveRecord # Overwrite the default generated SQL statement used to fetch the association with a manual statement # [:counter_sql] # Specify a complete SQL statement to fetch the size of the association. If :finder_sql is - # specified but not :counter_sql, :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM. + # specified but not :counter_sql, :counter_sql will be generated by + # replacing SELECT ... FROM with SELECT COUNT(*) FROM. # [:delete_sql] # Overwrite the default generated SQL statement used to remove links between the associated # classes with a manual statement. @@ -1295,20 +1378,24 @@ module ActiveRecord # [:group] # An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. # [:having] - # Combined with +:group+ this can be used to filter the records that a GROUP BY returns. Uses the HAVING SQL-clause. + # Combined with +:group+ this can be used to filter the records that a GROUP BY returns. + # Uses the HAVING SQL-clause. # [:limit] # An integer determining the limit on the number of rows that should be returned. # [:offset] - # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # An integer determining the offset from where the rows should be fetched. So at 5, + # it would skip the first 4 rows. # [:select] - # By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join - # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. + # By default, this is * as in SELECT * FROM, but can be changed if, for example, + # you want to do a join but not include the joined columns. Do not forget to include the primary + # and foreign keys, otherwise it will raise an error. # [:readonly] # If true, all the associated objects are readonly through the association. # [:validate] # If false, don't validate the associated objects when saving the parent object. +true+ by default. # [:autosave] - # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object. + # If true, always save the associated objects or destroy them if marked for destruction, when + # saving the parent object. # If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. # -- cgit v1.2.3 From 21e81da33570ba9453831cfc59c4f42ff9cdc1b2 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 2 Aug 2010 01:37:57 -0700 Subject: update tests for mysql2 support --- Gemfile | 1 + activerecord/Rakefile | 14 +- .../cases/adapters/mysql/active_schema_test.rb | 2 +- .../cases/adapters/mysql2/active_schema_test.rb | 125 +++++++++++++++ .../test/cases/adapters/mysql2/connection_test.rb | 42 +++++ .../cases/adapters/mysql2/reserved_word_test.rb | 176 +++++++++++++++++++++ .../associations/belongs_to_associations_test.rb | 2 +- .../has_and_belongs_to_many_associations_test.rb | 9 +- activerecord/test/cases/base_test.rb | 17 +- activerecord/test/cases/calculations_test.rb | 2 +- activerecord/test/cases/column_definition_test.rb | 34 ++++ activerecord/test/cases/defaults_test.rb | 2 +- activerecord/test/cases/migration_test.rb | 12 +- activerecord/test/cases/query_cache_test.rb | 2 +- activerecord/test/cases/schema_dumper_test.rb | 4 +- .../test/connections/native_mysql2/connection.rb | 25 +++ activerecord/test/schema/mysql2_specific_schema.rb | 24 +++ 17 files changed, 467 insertions(+), 26 deletions(-) create mode 100644 activerecord/test/cases/adapters/mysql2/active_schema_test.rb create mode 100644 activerecord/test/cases/adapters/mysql2/connection_test.rb create mode 100644 activerecord/test/cases/adapters/mysql2/reserved_word_test.rb create mode 100644 activerecord/test/connections/native_mysql2/connection.rb create mode 100644 activerecord/test/schema/mysql2_specific_schema.rb diff --git a/Gemfile b/Gemfile index c8dbcc0507..eb6d377af8 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ platforms :ruby do group :db do gem "pg", ">= 0.9.0" gem "mysql", ">= 2.8.1" + gem "mysql2", :path => 'git://github.com/brianmario/mysql2.git' end end diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 36cd7e3e6c..c1e90cc099 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -24,14 +24,14 @@ def run_without_aborting(*tasks) abort "Errors running #{errors.join(', ')}" if errors.any? end -desc 'Run mysql, sqlite, and postgresql tests by default' +desc 'Run mysql, mysql2, sqlite, and postgresql tests by default' task :default => :test -desc 'Run mysql, sqlite, and postgresql tests' +desc 'Run mysql, mysql2, sqlite, and postgresql tests' task :test do tasks = defined?(JRUBY_VERSION) ? %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) : - %w(test_mysql test_sqlite3 test_postgresql) + %w(test_mysql test_mysql2 test_sqlite3 test_postgresql) run_without_aborting(*tasks) end @@ -39,15 +39,15 @@ namespace :test do task :isolated do tasks = defined?(JRUBY_VERSION) ? %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) : - %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql) + %w(isolated_test_mysql isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql) run_without_aborting(*tasks) end end -%w( mysql postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| +%w( mysql mysql2 postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| Rake::TestTask.new("test_#{adapter}") { |t| connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}" - adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/] + adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] t.libs << "test" << connection_path t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { |x| x =~ /\/adapters\// @@ -59,7 +59,7 @@ end task "isolated_test_#{adapter}" do connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}" - adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/] + adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] puts [adapter, adapter_short, connection_path].inspect ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) (Dir["test/cases/**/*_test.rb"].reject { diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index ed4efdc1c0..509baacaef 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -42,7 +42,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase assert_equal "DROP TABLE `people`", drop_table(:people) end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def test_create_mysql_database_with_encoding assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb new file mode 100644 index 0000000000..a83399d0cd --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -0,0 +1,125 @@ +require "cases/helper" + +class ActiveSchemaTest < ActiveRecord::TestCase + def setup + ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + alias_method :execute_without_stub, :execute + remove_method :execute + def execute(sql, name = nil) return sql end + end + end + + def teardown + ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + remove_method :execute + alias_method :execute, :execute_without_stub + end + end + + def test_add_index + # add_index calls index_name_exists? which can't work since execute is stubbed + ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*| + false + end + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)" + assert_equal expected, add_index(:people, :last_name, :length => nil) + + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))" + assert_equal expected, add_index(:people, :last_name, :length => 10) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15}) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) + ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?) + end + + def test_drop_table + assert_equal "DROP TABLE `people`", drop_table(:people) + end + + if current_adapter?(:Mysql2Adapter) + def test_create_mysql_database_with_encoding + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) + end + + def test_recreate_mysql_database_with_encoding + create_database(:luca, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) + end + end + + def test_add_column + assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string) + end + + def test_add_column_with_limit + assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32) + end + + def test_drop_table_with_specific_database + assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people') + end + + def test_add_timestamps + with_real_execute do + begin + ActiveRecord::Base.connection.create_table :delete_me do |t| + end + ActiveRecord::Base.connection.add_timestamps :delete_me + assert column_present?('delete_me', 'updated_at', 'datetime') + assert column_present?('delete_me', 'created_at', 'datetime') + ensure + ActiveRecord::Base.connection.drop_table :delete_me rescue nil + end + end + end + + def test_remove_timestamps + with_real_execute do + begin + ActiveRecord::Base.connection.create_table :delete_me do |t| + t.timestamps + end + ActiveRecord::Base.connection.remove_timestamps :delete_me + assert !column_present?('delete_me', 'updated_at', 'datetime') + assert !column_present?('delete_me', 'created_at', 'datetime') + ensure + ActiveRecord::Base.connection.drop_table :delete_me rescue nil + end + end + end + + private + def with_real_execute + #we need to actually modify some data, so we make execute point to the original method + ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + alias_method :execute_with_stub, :execute + remove_method :execute + alias_method :execute, :execute_without_stub + end + yield + ensure + #before finishing, we restore the alias to the mock-up method + ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + remove_method :execute + alias_method :execute, :execute_with_stub + end + end + + + def method_missing(method_symbol, *arguments) + ActiveRecord::Base.connection.send(method_symbol, *arguments) + end + + def column_present?(table_name, column_name, type) + results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'") + results.first && results.first['Type'] == type + end +end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb new file mode 100644 index 0000000000..b973da621b --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" + +class MysqlConnectionTest < ActiveRecord::TestCase + def setup + super + @connection = ActiveRecord::Base.connection + end + + def test_no_automatic_reconnection_after_timeout + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + assert !@connection.active? + end + + def test_successful_reconnection_after_timeout_with_manual_reconnect + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + @connection.reconnect! + assert @connection.active? + end + + def test_successful_reconnection_after_timeout_with_verify + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + @connection.verify! + assert @connection.active? + end + + private + + def run_without_connection + original_connection = ActiveRecord::Base.remove_connection + begin + yield original_connection + ensure + ActiveRecord::Base.establish_connection(original_connection) + end + end +end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb new file mode 100644 index 0000000000..90d8b0d923 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -0,0 +1,176 @@ +require "cases/helper" + +class Group < ActiveRecord::Base + Group.table_name = 'group' + belongs_to :select, :class_name => 'Select' + has_one :values +end + +class Select < ActiveRecord::Base + Select.table_name = 'select' + has_many :groups +end + +class Values < ActiveRecord::Base + Values.table_name = 'values' +end + +class Distinct < ActiveRecord::Base + Distinct.table_name = 'distinct' + has_and_belongs_to_many :selects + has_many :values, :through => :groups +end + +# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with +# reserved word names (ie: group, order, values, etc...) +class MysqlReservedWordTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + + # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() + # will fail with these table names if these test cases fail + + create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int', + 'select'=>'id int auto_increment primary key', + 'values'=>'id int auto_increment primary key, group_id int', + 'distinct'=>'id int auto_increment primary key', + 'distincts_selects'=>'distinct_id int, select_id int' + end + + def teardown + drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order'] + end + + # create tables with reserved-word names and columns + def test_create_tables + assert_nothing_raised { + @connection.create_table :order do |t| + t.column :group, :string + end + } + end + + # rename tables with reserved-word names + def test_rename_tables + assert_nothing_raised { @connection.rename_table(:group, :order) } + end + + # alter column with a reserved-word name in a table with a reserved-word name + def test_change_columns + assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') } + #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter + assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) } + assert_nothing_raised { @connection.rename_column(:group, :order, :values) } + end + + # dump structure of table with reserved word name + def test_structure_dump + assert_nothing_raised { @connection.structure_dump } + end + + # introspect table with reserved word name + def test_introspect + assert_nothing_raised { @connection.columns(:group) } + assert_nothing_raised { @connection.indexes(:group) } + end + + #fixtures + self.use_instantiated_fixtures = true + self.use_transactional_fixtures = false + + #fixtures :group + + def test_fixtures + f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + + assert_nothing_raised { + f.each do |x| + x.delete_existing_fixtures + end + } + + assert_nothing_raised { + f.each do |x| + x.insert_fixtures + end + } + end + + #activerecord model class with reserved-word table name + def test_activerecord_model + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + x = nil + assert_nothing_raised { x = Group.new } + x.order = 'x' + assert_nothing_raised { x.save } + x.order = 'y' + assert_nothing_raised { x.save } + assert_nothing_raised { y = Group.find_by_order('y') } + assert_nothing_raised { y = Group.find(1) } + x = Group.find(1) + end + + # has_one association with reserved-word table name + def test_has_one_associations + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + v = nil + assert_nothing_raised { v = Group.find(1).values } + assert_equal 2, v.id + end + + # belongs_to association with reserved-word table name + def test_belongs_to_associations + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + gs = nil + assert_nothing_raised { gs = Select.find(2).groups } + assert_equal gs.length, 2 + assert(gs.collect{|x| x.id}.sort == [2, 3]) + end + + # has_and_belongs_to_many with reserved-word table name + def test_has_and_belongs_to_many + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + s = nil + assert_nothing_raised { s = Distinct.find(1).selects } + assert_equal s.length, 2 + assert(s.collect{|x|x.id}.sort == [1, 2]) + end + + # activerecord model introspection with reserved-word table and column names + def test_activerecord_introspection + assert_nothing_raised { Group.table_exists? } + assert_nothing_raised { Group.columns } + end + + # Calculations + def test_calculations_work_with_reserved_words + assert_nothing_raised { Group.count } + end + + def test_associations_work_with_reserved_words + assert_nothing_raised { Select.find(:all, :include => [:groups]) } + end + + #the following functions were added to DRY test cases + + private + # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path + def create_test_fixtures(*fixture_names) + Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) + end + + # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name + def drop_tables_directly(table_names, connection = @connection) + table_names.each do |name| + connection.execute("DROP TABLE IF EXISTS `#{name}`") + end + end + + # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns + def create_tables_directly (tables, connection = @connection) + tables.each do |table_name, column_properties| + connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") + end + end + +end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 046433820d..a1ce9b1689 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -32,7 +32,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_belongs_to_with_primary_key_joins_on_correct_column sql = Client.joins(:firm_with_primary_key).to_sql - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql) assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql) elsif current_adapter?(:OracleAdapter) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 6b4a1d9408..ed7d9a782c 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -109,8 +109,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase record = con.select_rows(sql).last assert_not_nil record[2] assert_not_nil record[3] - assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2] - assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3] + if current_adapter?(:Mysql2Adapter) + assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db) + assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db) + else + assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2] + assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3] + end end def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index df6895f0d0..34cce04794 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -116,12 +116,17 @@ class BasicsTest < ActiveRecord::TestCase end end + if current_adapter?(:Mysql2Adapter) + def test_read_attributes_before_type_cast_on_boolean + bool = Booleantest.create({ "value" => false }) + assert_equal 0, bool.reload.attributes_before_type_cast["value"] + end + end + def test_read_attributes_before_type_cast_on_datetime developer = Developer.find(:first) # Oracle adapter returns Time before type cast - unless current_adapter?(:OracleAdapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] - else + if current_adapter?(:OracleAdapter) assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) developer.created_at = "345643456" @@ -131,6 +136,10 @@ class BasicsTest < ActiveRecord::TestCase developer.created_at = "2010-03-21T21:23:32+01:00" assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") + elsif current_adapter?(:Mysql2Adapter) + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + else + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] end end @@ -564,7 +573,7 @@ class BasicsTest < ActiveRecord::TestCase assert Topic.find(2).approved? end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def test_update_all_with_order_and_limit assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC') end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 2c9d23c80f..afef31396e 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -325,7 +325,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_from_option_with_specified_index - if Edge.connection.adapter_name == 'MySQL' + if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2' assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)') assert_equal Edge.count(:all, :conditions => 'sink_id < 5'), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5') diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index b5767344cd..cc6a6b44f2 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -68,6 +68,40 @@ class ColumnDefinitionTest < ActiveRecord::TestCase end end + if current_adapter?(:Mysql2Adapter) + def test_should_set_default_for_mysql_binary_data_types + binary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "binary(1)") + assert_equal "a", binary_column.default + + varbinary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "varbinary(1)") + assert_equal "a", varbinary_column.default + end + + def test_should_not_set_default_for_blob_and_text_data_types + assert_raise ArgumentError do + ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "blob") + end + + assert_raise ArgumentError do + ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "Hello", "text") + end + + text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text") + assert_equal nil, text_column.default + + not_null_text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text", false) + assert_equal "", not_null_text_column.default + end + + def test_has_default_should_return_false_for_blog_and_test_data_types + blob_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "blob") + assert !blob_column.has_default? + + text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text") + assert !text_column.has_default? + end + end + if current_adapter?(:PostgreSQLAdapter) def test_bigint_column_should_map_to_integer bigint_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new('number', nil, "bigint") diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index ef29422824..0e90128907 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -39,7 +39,7 @@ class DefaultTest < ActiveRecord::TestCase end end -if current_adapter?(:MysqlAdapter) +if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase # ActiveRecord::Base#create! (and #save and other related methods) will # open a new transaction. When in transactional fixtures mode, this will diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 2c3fc46831..0cf3979694 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -256,7 +256,7 @@ if ActiveRecord::Base.connection.supports_migrations? def test_create_table_with_defaults # MySQL doesn't allow defaults on TEXT or BLOB columns. - mysql = current_adapter?(:MysqlAdapter) + mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) Person.connection.create_table :testings do |t| t.column :one, :string, :default => "hello" @@ -313,7 +313,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 'integer', four.sql_type assert_equal 'bigint', eight.sql_type assert_equal 'integer', eleven.sql_type - elsif current_adapter?(:MysqlAdapter) + elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) assert_match 'int(11)', default.sql_type assert_match 'tinyint', one.sql_type assert_match 'int', four.sql_type @@ -581,7 +581,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_kind_of BigDecimal, bob.wealth end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def test_unabstracted_database_dependent_types Person.delete_all @@ -621,7 +621,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert !Person.column_methods_hash.include?(:last_name) end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def testing_table_for_positioning Person.connection.create_table :testings, :id => false do |t| t.column :first, :integer @@ -1447,7 +1447,7 @@ if ActiveRecord::Base.connection.supports_migrations? columns = Person.connection.columns(:binary_testings) data_column = columns.detect { |c| c.name == "data" } - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) assert_equal '', data_column.default else assert_nil data_column.default @@ -1748,7 +1748,7 @@ if ActiveRecord::Base.connection.supports_migrations? end def integer_column - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) 'int(11)' elsif current_adapter?(:OracleAdapter) 'NUMBER(38)' diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index f0d97a00d0..594db1d0ab 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -57,7 +57,7 @@ class QueryCacheTest < ActiveRecord::TestCase # Oracle adapter returns count() as Fixnum or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") - elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' + elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' or current_adapter?(:Mysql2Adapter) # Future versions of the sqlite3 adapter will return numeric assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 1c43e3c5b5..66446b6b7e 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -93,7 +93,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_4.*}, output assert_no_match %r{c_int_4.*:limit}, output - elsif current_adapter?(:MysqlAdapter) + elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) assert_match %r{c_int_1.*:limit => 1}, output assert_match %r{c_int_2.*:limit => 2}, output assert_match %r{c_int_3.*:limit => 3}, output @@ -169,7 +169,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved" end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def test_schema_dump_should_not_add_default_value_for_mysql_text_field output = standard_dump assert_match %r{t.text\s+"body",\s+:null => false$}, output diff --git a/activerecord/test/connections/native_mysql2/connection.rb b/activerecord/test/connections/native_mysql2/connection.rb new file mode 100644 index 0000000000..c6f198b1ac --- /dev/null +++ b/activerecord/test/connections/native_mysql2/connection.rb @@ -0,0 +1,25 @@ +print "Using native Mysql2\n" +require_dependency 'models/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; +# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'mysql2', + :username => 'rails', + :encoding => 'utf8', + :database => 'activerecord_unittest', + }, + 'arunit2' => { + :adapter => 'mysql2', + :username => 'rails', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb new file mode 100644 index 0000000000..c78d99f4af --- /dev/null +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -0,0 +1,24 @@ +ActiveRecord::Schema.define do + create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t| + t.binary :tiny_blob, :limit => 255 + t.binary :normal_blob, :limit => 65535 + t.binary :medium_blob, :limit => 16777215 + t.binary :long_blob, :limit => 2147483647 + t.text :tiny_text, :limit => 255 + t.text :normal_text, :limit => 65535 + t.text :medium_text, :limit => 16777215 + t.text :long_text, :limit => 2147483647 + end + + ActiveRecord::Base.connection.execute <<-SQL +DROP PROCEDURE IF EXISTS ten; +SQL + + ActiveRecord::Base.connection.execute <<-SQL +CREATE PROCEDURE ten() SQL SECURITY INVOKER +BEGIN + select 10; +END +SQL + +end -- cgit v1.2.3 From 2353e826b0446917eb655b60c47533f5a9c2a07f Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 2 Aug 2010 01:58:23 -0700 Subject: typo --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index eb6d377af8..b42723aaae 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ platforms :ruby do group :db do gem "pg", ">= 0.9.0" gem "mysql", ">= 2.8.1" - gem "mysql2", :path => 'git://github.com/brianmario/mysql2.git' + gem "mysql2", :git => 'git://github.com/brianmario/mysql2.git' end end -- cgit v1.2.3 From c544fcc8eb891e9066bae5eb1478c6823b1f6203 Mon Sep 17 00:00:00 2001 From: rohit Date: Mon, 2 Aug 2010 13:42:35 +0530 Subject: Failing test to check for route file corruption if legacy map parameter is used. [#5263 state:open] --- railties/test/generators/scaffold_generator_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index ea469cb3c8..f12445ae35 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -216,4 +216,19 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Stylesheets (should not be removed) assert_file "public/stylesheets/scaffold.css" end + + def test_scaffold_generator_on_revoke_does_not_mutilate_legacy_map_parameter + run_generator + + # Add a |map| parameter to the routes block manually + route_path = File.expand_path("config/routes.rb", destination_root) + content = File.read(route_path).gsub(/\.routes\.draw do/) do |match| + "#{match} |map|" + end + File.open(route_path, "wb") { |file| file.write(content) } + + run_generator ["product_line"], :behavior => :revoke + + assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/ + end end -- cgit v1.2.3 From e6331b1e97d608c46aaadd1814a1e370bdcdd40a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 31 Jul 2010 22:33:22 -0300 Subject: Makes rails destroy scaffold don't duplicate routes.draw do |map| |map| when using the deprecated syntax [#5263 state:committed] --- railties/lib/rails/generators/actions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 2280cc1507..668ef48892 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -275,7 +275,7 @@ module Rails # def route(routing_code) log :route, routing_code - sentinel = /\.routes\.draw do(\s*\|map\|)?\s*$/ + sentinel = /\.routes\.draw do(?:\s*\|map\|)?\s*$/ in_root do inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false } -- cgit v1.2.3 From 311ea94f73c95be87f9a474da122719eebee3f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ku=C5=BAma?= Date: Mon, 2 Aug 2010 11:07:43 +0200 Subject: added failing touch propagation test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/timestamp_test.rb | 17 +++++++++++++++++ activerecord/test/schema/schema.rb | 2 ++ 2 files changed, 19 insertions(+) diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index f765540808..e3d12f6214 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -82,4 +82,21 @@ class TimestampTest < ActiveRecord::TestCase ensure Pet.belongs_to :owner, :touch => true end + + def test_touching_a_record_touches_parent_record_and_grandparent_record + Toy.belongs_to :pet, :touch => true + Pet.belongs_to :owner, :touch => true + + toy = Toy.first + pet = toy.pet + owner = pet.owner + + previously_owner_updated_at = owner.updated_at + + toy.touch + + assert_not_equal previously_owner_updated_at, owner.updated_at + ensure + Toy.belongs_to :pet + end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index f3fd37cd61..a0e620c2ef 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -398,6 +398,7 @@ ActiveRecord::Schema.define do create_table :pets, :primary_key => :pet_id ,:force => true do |t| t.string :name t.integer :owner_id, :integer + t.timestamps end create_table :pirates, :force => true do |t| @@ -530,6 +531,7 @@ ActiveRecord::Schema.define do create_table :toys, :primary_key => :toy_id ,:force => true do |t| t.string :name t.integer :pet_id, :integer + t.timestamps end create_table :traffic_lights, :force => true do |t| -- cgit v1.2.3 From b613c3cc7b08b00595e33d1b5302cd5d42687d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 Aug 2010 16:16:02 +0200 Subject: Add an internal (private API) after_touch callback. [#5271 state:resolved] --- activerecord/lib/active_record/associations.rb | 1 + activerecord/lib/active_record/callbacks.rb | 8 ++++++-- activerecord/lib/active_record/persistence.rb | 13 +++++++++++++ activerecord/lib/active_record/timestamp.rb | 13 ------------- activerecord/test/cases/timestamp_test.rb | 8 ++++---- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f540aa7f25..9663a36edf 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1498,6 +1498,7 @@ module ActiveRecord end end after_save(method_name) + after_touch(method_name) after_destroy(method_name) end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 637dac450b..82c45a41b0 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -228,7 +228,7 @@ module ActiveRecord extend ActiveSupport::Concern CALLBACKS = [ - :after_initialize, :after_find, :before_validation, :after_validation, + :after_initialize, :after_find, :after_touch, :before_validation, :after_validation, :before_save, :around_save, :after_save, :before_create, :around_create, :after_create, :before_update, :around_update, :after_update, :before_destroy, :around_destroy, :after_destroy @@ -238,7 +238,7 @@ module ActiveRecord extend ActiveModel::Callbacks include ActiveModel::Validations::Callbacks - define_model_callbacks :initialize, :find, :only => :after + define_model_callbacks :initialize, :find, :touch, :only => :after define_model_callbacks :save, :create, :update, :destroy end @@ -256,6 +256,10 @@ module ActiveRecord _run_destroy_callbacks { super } end + def touch(*) #:nodoc: + _run_touch_callbacks { super } + end + def deprecated_callback_method(symbol) #:nodoc: if respond_to?(symbol, true) ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead") diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 38b91652ee..cbc2220e96 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -218,6 +218,19 @@ module ActiveRecord self end + # Saves the record with the updated_at/on attributes set to the current time. + # Please note that no validation is performed and no callbacks are executed. + # If an attribute name is passed, that attribute is updated along with + # updated_at/on attributes. + # + # Examples: + # + # product.touch # updates updated_at/on + # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + def touch(attribute = nil) + update_attribute(attribute, current_time_from_proper_timezone) + end + private def create_or_update raise ReadOnlyRecord if readonly? diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 92f7a7753d..32b3f03f13 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -31,19 +31,6 @@ module ActiveRecord class_inheritable_accessor :record_timestamps, :instance_writer => false self.record_timestamps = true end - - # Saves the record with the updated_at/on attributes set to the current time. - # Please note that no validation is performed and no callbacks are executed. - # If an attribute name is passed, that attribute is updated along with - # updated_at/on attributes. - # - # Examples: - # - # product.touch # updates updated_at/on - # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on - def touch(attribute = nil) - update_attribute(attribute, current_time_from_proper_timezone) - end private diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index e3d12f6214..06ab7aa9c7 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -2,9 +2,10 @@ require 'cases/helper' require 'models/developer' require 'models/owner' require 'models/pet' +require 'models/toy' class TimestampTest < ActiveRecord::TestCase - fixtures :developers, :owners, :pets + fixtures :developers, :owners, :pets, :toys def setup @developer = Developer.first @@ -91,11 +92,10 @@ class TimestampTest < ActiveRecord::TestCase pet = toy.pet owner = pet.owner - previously_owner_updated_at = owner.updated_at - + owner.update_attribute(:updated_at, (time = 3.days.ago)) toy.touch - assert_not_equal previously_owner_updated_at, owner.updated_at + assert_not_equal time, owner.updated_at ensure Toy.belongs_to :pet end -- cgit v1.2.3 From 59cf514a5b769257a1538736d91f48ee0900e236 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Fri, 23 Jul 2010 19:09:27 -0500 Subject: Add missing require in ActiveSupport::HashWithIndifferentAccess [#5189 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activesupport/lib/active_support/hash_with_indifferent_access.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index f64f0f44cc..eec5d4cf47 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/keys' # This class has dubious semantics and we only have it so that -- cgit v1.2.3 From aeaab06c79a9e3cfa9988270847fa8c6f863570a Mon Sep 17 00:00:00 2001 From: Alex Le Date: Fri, 30 Jul 2010 15:47:26 -0500 Subject: ActiveModel::Errors json serialization to work as Rails 3b4 [#5254 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activemodel/lib/active_model/errors.rb | 4 ++-- .../cases/serializeration/json_serialization_test.rb | 16 +++++++++++++++- activemodel/test/cases/validations_test.rb | 8 +++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index bf93126d27..f39678db83 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -169,9 +169,9 @@ module ActiveModel to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true) end - # Returns an array as JSON representation for this object. + # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object. def as_json(options=nil) - to_a + self end # Adds +message+ to the error messages on +attribute+, which will be returned on a call to diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializeration/json_serialization_test.rb index 04b50e5bb8..1ac991a8f1 100644 --- a/activemodel/test/cases/serializeration/json_serialization_test.rb +++ b/activemodel/test/cases/serializeration/json_serialization_test.rb @@ -89,7 +89,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"preferences":\{"shows":"anime"\}}, json end - test "methds are called on object" do + test "methods are called on object" do # Define methods on fixture. def @contact.label; "Has cheezburger"; end def @contact.favorite_quote; "Constraints are liberating"; end @@ -102,4 +102,18 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"label":"Has cheezburger"}, methods_json assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end + + test "should return OrderedHash for errors" do + car = Automobile.new + + # run the validation + car.valid? + + hash = ActiveSupport::OrderedHash.new + hash[:make] = "can't be blank" + hash[:model] = "is too short (minimum is 2 characters)" + assert_equal hash.to_json, car.errors.to_json + end + + end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index e94d8ce88c..8d6bdeb6a5 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -170,9 +170,11 @@ class ValidationsTest < ActiveModel::TestCase assert_match %r{}, xml assert_match %r{Title can't be blank}, xml assert_match %r{Content can't be blank}, xml - - json = t.errors.to_json - assert_equal t.errors.to_a.to_json, json + + hash = ActiveSupport::OrderedHash.new + hash[:title] = "can't be blank" + hash[:content] = "can't be blank" + assert_equal t.errors.to_json, hash.to_json end def test_validation_order -- cgit v1.2.3 From cdad483dff4fef1b640dc3c750719c325b252f89 Mon Sep 17 00:00:00 2001 From: Fred Wu Date: Wed, 28 Jul 2010 22:55:57 +1000 Subject: Improved how AppGenerator generates the application name. It now detects the current app name whenever possible. This means that renaming the residing directory will not effect the app name generated by AppGenerator. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5225 state:resolved] Signed-off-by: José Valim --- .../rails/generators/rails/app/app_generator.rb | 6 +++++- railties/test/generators/app_generator_test.rb | 24 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 96c49a81bb..dd18588b39 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -356,8 +356,12 @@ module Rails @app_name ||= File.basename(destination_root) end + def defined_app_const_base + Rails.application.class.name.sub(/::Application$/, "") if Rails.application.instance_of?(Rails::Application) + end + def app_const_base - @app_const_base ||= app_name.gsub(/\W/, '_').squeeze('_').camelize + @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize end def app_const diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 1e0b3bf4c7..21725a380c 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -106,6 +106,30 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "things-43/config/application.rb", /^module Things43$/ end + def test_application_name_is_detected_if_it_exists_and_app_folder_renamed + app_root = File.join(destination_root, "myapp") + app_moved_root = File.join(destination_root, "myapp_moved") + + run_generator [app_root] + + Rails.application.config.root = app_moved_root + Rails.application.class.stubs(:name).returns("Myapp") + Rails.application.stubs(:instance_of?).returns(Rails::Application) + + FileUtils.mv(app_root, app_moved_root) + + # forces the shell to automatically overwrite all files + Thor::Base.shell.send(:attr_accessor, :always_force) + shell = Thor::Base.shell.new + shell.send(:always_force=, true) + + generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, + :destination_root => app_moved_root, :shell => shell + generator.send(:app_const) + silence(:stdout){ generator.send(:create_config_files) } + assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/ + end + def test_application_names_are_not_singularized run_generator [File.join(destination_root, "hats")] assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/ -- cgit v1.2.3 From 558ee6e95ccd6c2098595f2edfa59e8aa9108167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 Aug 2010 16:40:02 +0200 Subject: Handle edge cases in the previous patch. --- railties/lib/rails/generators/rails/app/app_generator.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index dd18588b39..a90f109844 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -357,7 +357,8 @@ module Rails end def defined_app_const_base - Rails.application.class.name.sub(/::Application$/, "") if Rails.application.instance_of?(Rails::Application) + Rails.respond_to?(:application) && defined?(Rails::Application) && + Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "") end def app_const_base -- cgit v1.2.3 From f8b53f35b9cbf2a134a7d9184a044ce95764acfa Mon Sep 17 00:00:00 2001 From: Robert Pankowecki Date: Tue, 27 Jul 2010 22:57:27 +0200 Subject: test and fix collection_singular_ids= with string primary keys [#5125 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/associations.rb | 4 ++- .../has_many_through_associations_test.rb | 40 +++++++++++++++++++++- activerecord/test/fixtures/subscriptions.yml | 2 +- activerecord/test/models/book.rb | 3 ++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9663a36edf..bd90cfc5d5 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1439,7 +1439,9 @@ module ActiveRecord end redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| - ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i) + pk_column = reflection.klass.columns.find{|c| c.name == reflection.klass.primary_key } + ids = (new_value || []).reject { |nid| nid.blank? } + ids.map!{|i| pk_column.type_cast(i)} send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index e4dd810732..3940e75ad6 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -14,9 +14,14 @@ require 'models/toy' require 'models/contract' require 'models/company' require 'models/developer' +require 'models/subscriber' +require 'models/book' +require 'models/subscription' class HasManyThroughAssociationsTest < ActiveRecord::TestCase - fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies + fixtures :posts, :readers, :people, :comments, :authors, + :owners, :pets, :toys, :jobs, :references, :companies, + :subscribers, :books, :subscriptions # Dummies to force column loads so query counts are clean. def setup @@ -383,4 +388,37 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) }, ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end + + def test_collection_singular_ids_getter_with_string_primary_keys + book = books(:awdr) + assert_equal 2, book.subscriber_ids.size + assert_equal [subscribers(:first).nick, subscribers(:second).nick].sort, book.subscriber_ids.sort + end + + def test_collection_singular_ids_setter + company = companies(:rails_core) + dev = Developer.find(:first) + + company.developer_ids = [dev.id] + assert_equal [dev], company.developers + end + + def test_collection_singular_ids_setter_with_string_primary_keys + assert_nothing_raised do + book = books(:awdr) + book.subscriber_ids = [subscribers(:second).nick] + assert_equal [subscribers(:second)], book.subscribers(true) + + book.subscriber_ids = [] + assert_equal [], book.subscribers(true) + end + + end + + def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set + company = companies(:rails_core) + ids = [Developer.find(:first).id, -9999] + assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids} + end + end diff --git a/activerecord/test/fixtures/subscriptions.yml b/activerecord/test/fixtures/subscriptions.yml index 371bfd3422..5a93c12193 100644 --- a/activerecord/test/fixtures/subscriptions.yml +++ b/activerecord/test/fixtures/subscriptions.yml @@ -9,4 +9,4 @@ webster_rfr: alterself_awdr: id: 3 subscriber_id: alterself - book_id: 3 \ No newline at end of file + book_id: 1 diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index cfd07abddc..1e030b4f59 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -1,4 +1,7 @@ class Book < ActiveRecord::Base has_many :citations, :foreign_key => 'book1_id' has_many :references, :through => :citations, :source => :reference_of, :uniq => true + + has_many :subscriptions + has_many :subscribers, :through => :subscriptions end -- cgit v1.2.3 From e1344bf5048934b0f231974c3597dfbc1c76154f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 Aug 2010 16:51:08 +0200 Subject: Tidy up previous commit. --- activerecord/lib/active_record/associations.rb | 6 ++--- activerecord/lib/active_record/reflection.rb | 26 ++++++++++------------ .../has_many_through_associations_test.rb | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index bd90cfc5d5..1dc094b893 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1437,11 +1437,11 @@ module ActiveRecord association.replace(new_value) association end - + redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| - pk_column = reflection.klass.columns.find{|c| c.name == reflection.klass.primary_key } + pk_column = reflection.primary_key_column ids = (new_value || []).reject { |nid| nid.blank? } - ids.map!{|i| pk_column.type_cast(i)} + ids.map!{ |i| pk_column.type_cast(i) } send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 03a932f642..7f47a812eb 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -91,25 +91,19 @@ module ActiveRecord # # composed_of :balance, :class_name => 'Money' returns :balance # has_many :clients returns :clients - def name - @name - end + attr_reader :name # Returns the macro type. # # composed_of :balance, :class_name => 'Money' returns :composed_of # has_many :clients returns :has_many - def macro - @macro - end + attr_reader :macro # Returns the hash of options used for the macro. # # composed_of :balance, :class_name => 'Money' returns { :class_name => "Money" } # has_many :clients returns +{}+ - def options - @options - end + attr_reader :options # Returns the class for the macro. # @@ -137,11 +131,6 @@ module ActiveRecord @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions] end - # Returns +true+ if +self+ is a +belongs_to+ reflection. - def belongs_to? - macro == :belongs_to - end - private def derive_class_name name.to_s.camelize @@ -213,6 +202,10 @@ module ActiveRecord @primary_key_name ||= options[:foreign_key] || derive_primary_key_name end + def primary_key_column + @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key } + end + def association_foreign_key @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key end @@ -307,6 +300,11 @@ module ActiveRecord dependent_conditions end + # Returns +true+ if +self+ is a +belongs_to+ reflection. + def belongs_to? + macro == :belongs_to + end + private def derive_class_name class_name = name.to_s.camelize diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 3940e75ad6..0eaadac5ae 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -21,7 +21,7 @@ require 'models/subscription' class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies, - :subscribers, :books, :subscriptions + :subscribers, :books, :subscriptions, :developers # Dummies to force column loads so query counts are clean. def setup -- cgit v1.2.3 From 59693c4c49cce5e4cf53eb54e42e3da07a98467e Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Tue, 27 Jul 2010 09:55:33 +0200 Subject: fix loading of different elements in array then int and string [#5036 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activeresource/lib/active_resource/base.rb | 6 +++--- activeresource/test/cases/base/load_test.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 72ad2f5872..62420725ad 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1222,10 +1222,10 @@ module ActiveResource when Array resource = find_or_create_resource_for_collection(key) value.map do |attrs| - if attrs.is_a?(String) || attrs.is_a?(Numeric) - attrs.duplicable? ? attrs.dup : attrs - else + if attrs.is_a?(Hash) resource.new(attrs) + else + attrs.duplicable? ? attrs.dup : attrs end end when Hash diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index 7745a9439b..228dc36d9b 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -47,6 +47,8 @@ class BaseLoadTest < Test::Unit::TestCase { :id => 1, :name => 'Willamette' }, { :id => 2, :name => 'Columbia', :rafted_by => @matz }], :postal_codes => [ 97018, 1234567890 ], + :dates => [ Time.now ], + :votes => [ true, false, true ], :places => [ "Columbia City", "Unknown" ]}}} @person = Person.new @@ -149,6 +151,16 @@ class BaseLoadTest < Test::Unit::TestCase assert_kind_of Array, places assert_kind_of String, places.first assert_equal @deep[:street][:state][:places].first, places.first + + dates = state.dates + assert_kind_of Array, dates + assert_kind_of Time, dates.first + assert_equal @deep[:street][:state][:dates].first, dates.first + + votes = state.votes + assert_kind_of Array, votes + assert_kind_of TrueClass, votes.first + assert_equal @deep[:street][:state][:votes].first, votes.first end def test_nested_collections_within_the_same_namespace -- cgit v1.2.3 From b8d9d9ce0a72218fa0891485063d3fcb3e77cae8 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 2 Aug 2010 11:09:31 -0400 Subject: updating documentation to ensure line does not exceed 100 columns --- activerecord/lib/active_record/base.rb | 216 +++++++++++++++++------------ activerecord/lib/active_record/relation.rb | 14 +- 2 files changed, 136 insertions(+), 94 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 391c287fe4..5535079cd8 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -26,17 +26,19 @@ require 'active_record/log_subscriber' module ActiveRecord #:nodoc: # = Active Record # - # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with - # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change - # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain + # Active Record objects don't specify their attributes directly, but rather infer them from + # the table definition with which they're linked. Adding, removing, and changing attributes + # and their type is done directly in the database. Any change is instantly reflected in the + # Active Record objects. The mapping that binds a given Active Record class to a certain # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones. # # See the mapping rules in table_name and the full example in link:files/README.html for more insight. # # == Creation # - # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when - # you're receiving the data from somewhere else, like an HTTP request. It works like this: + # Active Records accept constructor parameters either in a hash or as a block. The hash + # method is especially useful when you're receiving the data from somewhere else, like an + # HTTP request. It works like this: # # user = User.new(:name => "David", :occupation => "Code Artist") # user.name # => "David" @@ -75,14 +77,17 @@ module ActiveRecord #:nodoc: # end # end # - # The authenticate_unsafely method inserts the parameters directly into the query and is thus susceptible to SQL-injection - # attacks if the user_name and +password+ parameters come directly from an HTTP request. The authenticate_safely and - # authenticate_safely_simply both will sanitize the user_name and +password+ before inserting them in the query, - # which will ensure that an attacker can't escape the query and fake the login (or worse). + # The authenticate_unsafely method inserts the parameters directly into the query + # and is thus susceptible to SQL-injection attacks if the user_name and +password+ + # parameters come directly from an HTTP request. The authenticate_safely and + # authenticate_safely_simply both will sanitize the user_name and +password+ + # before inserting them in the query, which will ensure that an attacker can't escape the + # query and fake the login (or worse). # - # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth - # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing - # the question marks with symbols and supplying a hash with values for the matching symbol keys: + # When using multiple parameters in the conditions, it can easily become hard to read exactly + # what the fourth or fifth question mark is supposed to represent. In those cases, you can + # resort to named bind variables instead. That's done by replacing the question marks with + # symbols and supplying a hash with values for the matching symbol keys: # # Company.where( # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", @@ -103,18 +108,19 @@ module ActiveRecord #:nodoc: # # Student.where(:grade => [9,11,12]) # - # When joining tables, nested hashes or keys written in the form 'table_name.column_name' can be used to qualify the table name of a - # particular condition. For instance: + # When joining tables, nested hashes or keys written in the form 'table_name.column_name' + # can be used to qualify the table name of a particular condition. For instance: # # Student.joins(:schools).where(:schools => { :type => 'public' }) # Student.joins(:schools).where('schools.type' => 'public' ) # # == Overwriting default accessors # - # All column values are automatically available through basic accessors on the Active Record object, but sometimes you - # want to specialize this behavior. This can be done by overwriting the default accessors (using the same - # name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things. - # Example: + # All column values are automatically available through basic accessors on the Active Record + # object, but sometimes you want to specialize this behavior. This can be done by overwriting + # the default accessors (using the same name as the attribute) and calling + # read_attribute(attr_name) and write_attribute(attr_name, value) to actually + # change things. # # class Song < ActiveRecord::Base # # Uses an integer of seconds to hold the length of the song @@ -128,8 +134,8 @@ module ActiveRecord #:nodoc: # end # end # - # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and - # read_attribute(:attribute) as a shorter form. + # You can alternatively use self[:attribute]=(value) and self[:attribute] + # instead of write_attribute(:attribute, value) and read_attribute(:attribute). # # == Attribute query methods # @@ -147,24 +153,29 @@ module ActiveRecord #:nodoc: # # == Accessing attributes before they have been typecasted # - # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first. - # That can be done by using the _before_type_cast accessors that all attributes have. For example, if your Account model - # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast. + # Sometimes you want to be able to read the raw attribute data without having the column-determined + # typecast run its course first. That can be done by using the _before_type_cast + # accessors that all attributes have. For example, if your Account model has a balance attribute, + # you can call account.balance_before_type_cast or account.id_before_type_cast. # - # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display - # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you - # want. + # This is especially useful in validation situations where the user might supply a string for an + # integer field and you want to display the original string back in an error message. Accessing the + # attribute normally would typecast the string to 0, which isn't what you want. # # == Dynamic attribute-based finders # - # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by - # appending the name of an attribute to find_by_, find_last_by_, or find_all_by_, so you get finders like Person.find_by_user_name, - # Person.find_all_by_last_name, and Payment.find_by_transaction_id. So instead of writing + # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects + # by simple queries without turning to SQL. They work by appending the name of an attribute + # to find_by_, find_last_by_, or find_all_by_, so you get finders + # like Person.find_by_user_name, Person.find_all_by_last_name, and + # Payment.find_by_transaction_id. So instead of writing # Person.where(:user_name => user_name).first, you just do Person.find_by_user_name(user_name). - # And instead of writing Person.where(:last_name => last_name).all, you just do Person.find_all_by_last_name(last_name). + # And instead of writing Person.where(:last_name => last_name).all, you just do + # Person.find_all_by_last_name(last_name). # - # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like - # Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing + # It's also possible to use multiple attributes in the same find by separating them with "_and_", + # so you get finders like Person.find_by_user_name_and_password or even + # Payment.find_by_purchaser_and_state_and_country. So instead of writing # Person.where(:user_name => user_name, :password => password).first, you just do # Person.find_by_user_name_and_password(user_name, password). # @@ -173,8 +184,10 @@ module ActiveRecord #:nodoc: # Payment.order("created_on").find_all_by_amount(50) # Payment.pending.find_last_by_amount(100) # - # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with - # find_or_create_by_ and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example: + # The same dynamic finder style can be used to create the object if it doesn't already exist. + # This dynamic finder is called with find_or_create_by_ and will return the object if + # it already exists and otherwise creates it, then returns it. Protected attributes won't be set + # unless they are given in a block. For example: # # # No 'Summer' tag exists # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer") @@ -185,23 +198,27 @@ module ActiveRecord #:nodoc: # # Now 'Bob' exist and is an 'admin' # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true } # - # Use the find_or_initialize_by_ finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example: + # Use the find_or_initialize_by_ finder if you want to return a new record without + # saving it first. Protected attributes won't be set unless they are given in a block. # # # No 'Winter' tag exists # winter = Tag.find_or_initialize_by_name("Winter") # winter.new_record? # true # # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of - # a list of parameters. For example: + # a list of parameters. # # Tag.find_or_create_by_name(:name => "rails", :creator => current_user) # - # That will either find an existing tag named "rails", or create a new one while setting the user that created it. + # That will either find an existing tag named "rails", or create a new one while setting the + # user that created it. # # == Saving arrays, hashes, and other non-mappable objects in text columns # - # 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+. - # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example: + # 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+. + # This makes it possible to store arrays, hashes, and other non-mappable objects without doing + # any additional work. # # class User < ActiveRecord::Base # serialize :preferences @@ -210,8 +227,8 @@ module ActiveRecord #:nodoc: # user = User.create(:preferences => { "background" => "black", "display" => large }) # User.find(user.id).preferences # => { "background" => "black", "display" => large } # - # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a - # descendant of a class not in the hierarchy. Example: + # You can also specify a class option as the second parameter that'll raise an exception + # if a serialized object is retrieved as a descendant of a class not in the hierarchy. # # class User < ActiveRecord::Base # serialize :preferences, Hash @@ -222,52 +239,63 @@ module ActiveRecord #:nodoc: # # == Single table inheritance # - # Active Record allows inheritance by storing the name of the class in a column that by default is named "type" (can be changed - # by overwriting Base.inheritance_column). This means that an inheritance looking like this: + # Active Record allows inheritance by storing the name of the class in a column that by + # default is named "type" (can be changed by overwriting Base.inheritance_column). + # This means that an inheritance looking like this: # # class Company < ActiveRecord::Base; end # class Firm < Company; end # class Client < Company; end # class PriorityClient < Client; end # - # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then - # fetch this row again using Company.where(:name => '37signals').first and it will return a Firm object. + # When you do Firm.create(:name => "37signals"), this record will be saved in + # the companies table with type = "Firm". You can then fetch this row again using + # Company.where(:name => '37signals').first and it will return a Firm object. # - # 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 - # like normal subclasses with no special magic for differentiating between them or reloading the right type with find. + # 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 like normal subclasses with no special magic + # for differentiating between them or reloading the right type with find. # # Note, all the attributes for all the cases are kept in the same table. Read more: # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html # # == Connection to multiple databases in different models # - # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection. - # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection. - # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection + # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved + # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this + # connection. But you can also set a class-specific connection. For example, if Course is an + # ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection # and Course and all of its subclasses will use this connection instead. # - # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is - # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool. + # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is + # a Hash indexed by the class. If a connection is requested, the retrieve_connection method + # will go up the class-hierarchy until a connection is found in the connection pool. # # == Exceptions # # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record. # * AdapterNotSpecified - The configuration hash used in establish_connection didn't include an # :adapter key. - # * AdapterNotFound - The :adapter key used in establish_connection specified a non-existent adapter + # * AdapterNotFound - The :adapter key used in establish_connection specified a + # non-existent adapter # (or a bad spelling of an existing one). - # * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition. + # * AssociationTypeMismatch - The object assigned to the association wasn't of the type + # specified in the association definition. # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter. - # * ConnectionNotEstablished+ - No connection has been established. Use establish_connection before querying. + # * ConnectionNotEstablished+ - No connection has been established. Use establish_connection + # before querying. # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal # nothing was found, please check its documentation for further details. # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message. # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the - # attributes= method. The +errors+ property of this exception contains an array of AttributeAssignmentError + # attributes= method. The +errors+ property of this exception contains an array of + # AttributeAssignmentError # objects that should be inspected to determine which attributes triggered the errors. - # * AttributeAssignmentError - An error occurred while doing a mass assignment through the attributes= method. - # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error. + # * AttributeAssignmentError - An error occurred while doing a mass assignment through the + # attributes= method. + # You can inspect the +attribute+ property of the exception object to determine which attribute + # triggered the error. # # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level). # So it's possible to assign a logger to the class through Base.logger= which will then be used by all @@ -275,8 +303,9 @@ module ActiveRecord #:nodoc: class Base ## # :singleton-method: - # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed - # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+. + # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, + # which is then passed on to any new database connections made and which can be retrieved on both + # a class and instance level by calling +logger+. cattr_accessor :logger, :instance_writer => false class << self @@ -323,21 +352,24 @@ module ActiveRecord #:nodoc: ## # :singleton-method: - # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and - # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as - # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember + # Accessor for the prefix type that will be prepended to every primary key column name. + # The options are :table_name and :table_name_with_underscore. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember # that this is a global setting for all Active Records. cattr_accessor :primary_key_prefix_type, :instance_writer => false @@primary_key_prefix_type = nil ## # :singleton-method: - # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all - # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace - # for tables in a shared database. By default, the prefix is the empty string. + # Accessor for the name of the prefix string to prepend to every table name. So if set + # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people", + # etc. This is a convenient way of creating a namespace for tables in a shared database. + # By default, the prefix is the empty string. # - # If you are organising your models within modules you can add a prefix to the models within a namespace by defining - # a singleton method in the parent module called table_name_prefix which returns your chosen prefix. + # If you are organising your models within modules you can add a prefix to the models within + # a namespace by defining a singleton method in the parent module called table_name_prefix which + # returns your chosen prefix. class_attribute :table_name_prefix, :instance_writer => false self.table_name_prefix = "" @@ -358,8 +390,8 @@ module ActiveRecord #:nodoc: ## # :singleton-method: - # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database. - # This is set to :local by default. + # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling + # dates and times from the database. This is set to :local by default. cattr_accessor :default_timezone, :instance_writer => false @@default_timezone = :local @@ -505,15 +537,18 @@ module ActiveRecord #:nodoc: serialized_attributes[attr_name.to_s] = class_name end - # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values. + # Returns a hash of all the attributes that have been specified for serialization as + # keys and their class restriction as values. def serialized_attributes read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {}) end - # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending - # directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used - # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class - # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb. + # Guesses the table name (in forced lower-case) based on the name of the class in the + # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy + # looks like: Reply < Message < ActiveRecord::Base, then Message is used + # to guess the table name even when called on Reply. The rules used to do the guess + # are handled by the Inflector class in Active Support, which knows almost all common + # English inflections. You can add new inflections in config/initializers/inflections.rb. # # Nested classes are given table names prefixed by the singular form of # the parent's table name. Enclosing modules are not considered. @@ -923,15 +958,18 @@ module ActiveRecord #:nodoc: end end - # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) - # that are turned into where(:user_name => user_name).first and where(:user_name => user_name, :password => :password).first - # respectively. Also works for all by using find_all_by_amount(50) that is turned into where(:amount => 50).all. + # Enables dynamic finders like find_by_user_name(user_name) and + # find_by_user_name_and_password(user_name, password) that are turned into + # where(:user_name => user_name).first and + # where(:user_name => user_name, :password => :password).first + # respectively. Also works for all by using find_all_by_amount(50) + # that is turned into where(:amount => 50).all. # - # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+ - # is actually find_all_by_amount(amount, options). + # It's even possible to use all the additional parameters to +find+. For example, the + # full interface for +find_all_by_amount+ is actually find_all_by_amount(amount, options). # - # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future - # attempts to use it do not run through method_missing. + # Each dynamic finder, scope or initializer/creator is also defined in the class after it + # is first invoked, so that future attempts to use it do not run through method_missing. def method_missing(method_id, *arguments, &block) if match = DynamicFinderMatch.match(method_id) attribute_names = match.attribute_names @@ -1607,10 +1645,11 @@ MSG private - # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant. - # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to - # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the - # Message class in that example. + # Sets the attribute used for single table inheritance to this class name if this is not the + # ActiveRecord::Base descendant. + # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to + # do Reply.new without having to set Reply[Reply.inheritance_column] = "Reply" yourself. + # No such attribute would be set for objects of the Message class in that example. def ensure_proper_type unless self.class.descends_from_active_record? write_attribute(self.class.inheritance_column, self.class.sti_name) @@ -1659,8 +1698,9 @@ MSG # by calling new on the column type or aggregation type (through composed_of) object with these parameters. # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the - # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float, - # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil. + # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, + # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the + # attribute will be set to nil. def assign_multiparameter_attributes(pairs) execute_callstack_for_multiparameter_attributes( extract_callstack_for_multiparameter_attributes(pairs) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7962f52738..deacced627 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -67,7 +67,8 @@ module ActiveRecord preload += @includes_values unless eager_loading? preload.each {|associations| @klass.send(:preload_associations, @records, associations) } - # @readonly_value is true only if set explicitly. @implicit_readonly is true if there are JOINS and no explicit SELECT. + # @readonly_value is true only if set explicitly. @implicit_readonly is true if there + # are JOINS and no explicit SELECT. readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value @records.each { |record| record.readonly! } if readonly @@ -130,7 +131,8 @@ module ActiveRecord # ==== Parameters # # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. - # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro. + # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. + # See conditions in the intro. # * +options+ - Additional options are :limit and :order, see the examples for usage. # # ==== Examples @@ -144,7 +146,7 @@ module ActiveRecord # # Update all avatars migrated more than a week ago # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago] # - # # Update all books that match our conditions, but limit it to 5 ordered by date + # # Update all books that match conditions, but limit it to 5 ordered by date # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 def update_all(updates, conditions = nil, options = {}) if conditions || options.present? @@ -165,14 +167,14 @@ module ActiveRecord # ==== Parameters # # * +id+ - This should be the id or an array of ids to be updated. - # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. + # * +attributes+ - This should be a hash of attributes or an array of hashes. # # ==== Examples # - # # Updating one record: + # # Updates one record # Person.update(15, :user_name => 'Samuel', :group => 'expert') # - # # Updating multiple records: + # # Updates multiple records # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } # Person.update(people.keys, people.values) def update(id, attributes) -- cgit v1.2.3 From 009aa8825b6932b006f005ac351b82ad8100d7f1 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 26 Jul 2010 15:12:36 -0400 Subject: Eager loading an association should not change the count of children MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#4971 state:resolved] Signed-off-by: José Valim --- activerecord/lib/active_record/associations.rb | 4 ++++ activerecord/test/cases/associations_test.rb | 13 +++++++++++++ activerecord/test/models/electron.rb | 3 +++ activerecord/test/models/liquid.rb | 5 +++++ activerecord/test/models/molecule.rb | 4 ++++ activerecord/test/schema/schema.rb | 13 +++++++++++++ 6 files changed, 42 insertions(+) create mode 100644 activerecord/test/models/electron.rb create mode 100644 activerecord/test/models/liquid.rb create mode 100644 activerecord/test/models/molecule.rb diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 1dc094b893..9cd3b9662f 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1815,6 +1815,10 @@ module ActiveRecord when Hash associations.keys.each do |name| reflection = base.reflections[name] + + if records.any? && reflection.options && reflection.options[:uniq] + records.each { |record| record.send(reflection.name).target.uniq! } + end parent_records = [] records.each do |record| diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index a1c794c084..d328ca630b 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -14,11 +14,24 @@ require 'models/reader' require 'models/parrot' require 'models/ship_part' require 'models/ship' +require 'models/liquid' +require 'models/molecule' +require 'models/electron' class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, :computers, :people, :readers + def test_eager_loading_should_not_change_count_of_children + liquid = Liquid.create(:name => 'salty') + molecule = liquid.molecules.create(:name => 'molecule_1') + molecule.electrons.create(:name => 'electron_1') + molecule.electrons.create(:name => 'electron_2') + + liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null') + assert_equal 1, liquids[0].molecules.length + end + def test_loading_the_association_target_should_keep_child_records_marked_for_destruction ship = Ship.create!(:name => "The good ship Dollypop") part = ship.parts.create!(:name => "Mast") diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb new file mode 100644 index 0000000000..35af9f679b --- /dev/null +++ b/activerecord/test/models/electron.rb @@ -0,0 +1,3 @@ +class Electron < ActiveRecord::Base + belongs_to :molecule +end diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb new file mode 100644 index 0000000000..b96c054f6c --- /dev/null +++ b/activerecord/test/models/liquid.rb @@ -0,0 +1,5 @@ +class Liquid < ActiveRecord::Base + set_table_name :liquid + has_many :molecules, :uniq => true +end + diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb new file mode 100644 index 0000000000..69325b8d29 --- /dev/null +++ b/activerecord/test/models/molecule.rb @@ -0,0 +1,4 @@ +class Molecule < ActiveRecord::Base + belongs_to :liquid + has_many :electrons +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index a0e620c2ef..fc3810f82b 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -618,6 +618,19 @@ ActiveRecord::Schema.define do t.datetime :updated_at end + create_table :liquid, :force => true do |t| + t.string :name + end + create_table :molecules, :force => true do |t| + t.integer :liquid_id + t.string :name + end + create_table :electrons, :force => true do |t| + t.integer :molecule_id + t.string :name + end + + except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, :force => true do |t| -- cgit v1.2.3 From 9effe3cc18182abbcf0a8b01635a34ca77b67e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 Aug 2010 17:20:17 +0200 Subject: Remove duplicated logic. --- activerecord/lib/active_record/associations.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9cd3b9662f..fdc203e298 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1805,9 +1805,7 @@ module ActiveRecord case associations when Symbol, String reflection = base.reflections[associations] - if reflection && reflection.collection? - records.each { |record| record.send(reflection.name).target.uniq! } - end + remove_uniq_by_reflection(reflection, records) when Array associations.each do |association| remove_duplicate_results!(base, records, association) @@ -1815,10 +1813,7 @@ module ActiveRecord when Hash associations.keys.each do |name| reflection = base.reflections[name] - - if records.any? && reflection.options && reflection.options[:uniq] - records.each { |record| record.send(reflection.name).target.uniq! } - end + remove_uniq_by_reflection(reflection, records) parent_records = [] records.each do |record| @@ -1837,6 +1832,7 @@ module ActiveRecord end protected + def build(associations, parent = nil, join_class = Arel::InnerJoin) parent ||= @joins.last case associations @@ -1859,6 +1855,12 @@ module ActiveRecord end end + def remove_uniq_by_reflection(reflection, records) + if reflection && reflection.collection? + records.each { |record| record.send(reflection.name).target.uniq! } + end + end + def build_join_association(reflection, parent) JoinAssociation.new(reflection, self, parent) end -- cgit v1.2.3 From 1ce40ca56216ae76e93cde78ec2752de110400c0 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 2 Aug 2010 12:25:26 -0400 Subject: ensuring that description does not exceed 100 columns --- .../has_and_belongs_to_many_association.rb | 7 +- .../associations/has_many_through_association.rb | 7 +- .../attribute_methods/time_zone_conversion.rb | 3 +- .../lib/active_record/attribute_methods/write.rb | 4 +- activerecord/lib/active_record/callbacks.rb | 66 +++++++++--------- .../abstract/schema_definitions.rb | 10 +-- .../connection_adapters/mysql_adapter.rb | 3 +- .../connection_adapters/postgresql_adapter.rb | 12 ++-- .../connection_adapters/sqlite_adapter.rb | 4 +- activerecord/lib/active_record/errors.rb | 14 ++-- activerecord/lib/active_record/fixtures.rb | 50 ++++++++------ activerecord/lib/active_record/named_scope.rb | 21 +++--- activerecord/lib/active_record/observer.rb | 4 +- activerecord/lib/active_record/persistence.rb | 4 +- .../lib/active_record/relation/calculations.rb | 79 ++++++++++++++-------- .../lib/active_record/relation/finder_methods.rb | 23 ++++--- activerecord/lib/active_record/timestamp.rb | 3 +- .../lib/active_record/validations/associated.rb | 7 +- .../lib/active_record/validations/uniqueness.rb | 22 +++--- 19 files changed, 205 insertions(+), 138 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index e61af93d1e..bec123e7a2 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -106,9 +106,10 @@ module ActiveRecord :limit => @reflection.options[:limit] } } end - # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select - # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has - # an id column. This will then overwrite the id column of the records coming back. + # Join tables with additional columns on top of the two foreign keys must be considered + # ambiguous unless a select clause has been explicitly defined. Otherwise you can get + # broken records back, if, for example, the join column also has an id column. This will + # then overwrite the id column of the records coming back. def finding_with_ambiguous_select?(select_clause) !select_clause && columns.size != 2 end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 17f850756f..608b1c741a 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -24,9 +24,10 @@ module ActiveRecord end end - # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and - # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero, - # and you need to fetch that collection afterwards, it'll take one fewer SELECT query if you use #length. + # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been + # loaded and calling collection.size if it has. If it's more likely than not that the collection does + # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer + # SELECT query if you use #length. def size return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter? return @target.size if loaded? diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 783d61383b..8f0aacba42 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -14,7 +14,8 @@ module ActiveRecord module ClassMethods protected # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone. + # This enhanced read method automatically converts the UTC time stored in the database to the time + # zone stored in Time.zone. def define_method_attribute(attr_name) if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body, line = <<-EOV, __LINE__ + 1 diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index e31acac050..7a2de3bf80 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -14,8 +14,8 @@ module ActiveRecord end end - # Updates the attribute identified by attr_name with the specified +value+. Empty strings for fixnum and float - # columns are turned into +nil+. + # Updates the attribute identified by attr_name with the specified +value+. Empty strings + # for fixnum and float columns are turned into +nil+. def write_attribute(attr_name, value) attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 637dac450b..b2b1374838 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -26,8 +26,8 @@ module ActiveRecord # after_rollback. # # That's a total of ten callbacks, which gives you immense power to react and prepare for each state in the - # Active Record lifecycle. The sequence for calling Base#save for an existing record is similar, except that each - # _on_create callback is replaced by the corresponding _on_update callback. + # Active Record lifecycle. The sequence for calling Base#save for an existing record is similar, + # except that each _on_create callback is replaced by the corresponding _on_update callback. # # Examples: # class CreditCard < ActiveRecord::Base @@ -55,9 +55,9 @@ module ActiveRecord # # == Inheritable callback queues # - # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros. - # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance - # hierarchy. Example: + # Besides the overwritable callback methods, it's also possible to register callbacks through the + # use of the callback macros. Their main advantage is that the macros add behavior into a callback + # queue that is kept intact down through an inheritance hierarchy. # # class Topic < ActiveRecord::Base # before_destroy :destroy_author @@ -67,9 +67,9 @@ module ActiveRecord # before_destroy :destroy_readers # end # - # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is run, both +destroy_author+ and - # +destroy_readers+ are called. Contrast this to the situation where we've implemented the save behavior through overwriteable - # methods: + # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is + # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the situation where + # we've implemented the save behavior through overwriteable methods: # # class Topic < ActiveRecord::Base # def before_destroy() destroy_author end @@ -79,20 +79,21 @@ module ActiveRecord # def before_destroy() destroy_readers end # end # - # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when - # you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods - # when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks. + # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. + # So, use the callback macros when you want to ensure that a certain callback is called for the entire + # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant + # to decide whether they want to call +super+ and trigger the inherited callbacks. # - # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the - # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't - # be inherited. + # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the + # callbacks before specifying the associations. Otherwise, you might trigger the loading of a + # child before the parent has registered the callbacks and they won't be inherited. # # == Types of callbacks # # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects, - # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the - # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline - # eval methods are deprecated. + # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects + # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for + # creating mix-ins), and inline eval methods are deprecated. # # The method reference callbacks work by specifying a protected or private method available in the object, like this: # @@ -169,15 +170,15 @@ module ActiveRecord # end # end # - # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string", - # which will then be evaluated within the binding of the callback. Example: + # The callback macros usually accept a symbol for the method they're supposed to run, but you can also + # pass a "method string", which will then be evaluated within the binding of the callback. Example: # # class Topic < ActiveRecord::Base # before_destroy 'self.class.delete_all "parent_id = #{id}"' # end # - # Notice that single quotes (') are used so the #{id} part isn't evaluated until the callback is triggered. Also note that these - # inline callbacks can be stacked just like the regular ones: + # Notice that single quotes (') are used so the #{id} part isn't evaluated until the callback + # is triggered. Also note that these inline callbacks can be stacked just like the regular ones: # # class Topic < ActiveRecord::Base # before_destroy 'self.class.delete_all "parent_id = #{id}"', @@ -186,22 +187,24 @@ module ActiveRecord # # == The +after_find+ and +after_initialize+ exceptions # - # 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 - # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, +after_find+ and - # +after_initialize+ will only be run if an explicit implementation is defined (def after_find). In that case, all of the + # 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 to implement a simple performance constraint (50% more speed + # on a simple test case). Unlike all the other callbacks, +after_find+ and +after_initialize+ will only be + # run if an explicit implementation is defined (def after_find). In that case, all of the # callback types will be called. # # == before_validation* returning statements # - # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and Base#save will return +false+. - # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception. - # Nothing will be appended to the errors object. + # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be + # aborted and Base#save will return +false+. If Base#save! is called it will raise a + # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object. # # == Canceling callbacks # - # If a before_* callback returns +false+, all the later callbacks and the associated action are cancelled. If an after_* callback returns - # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks - # defined as methods on the model, which are called last. + # If a before_* callback returns +false+, all the later callbacks and the associated action are + # cancelled. If an after_* callback returns +false+, all the later callbacks are cancelled. + # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as + # methods on the model, which are called last. # # == Transactions # @@ -217,7 +220,8 @@ module ActiveRecord # # == Debugging callbacks # - # To list the methods and procs registered with a particular callback, append _callback_chain to the callback name that you wish to list and send that to your class from the Rails console: + # To list the methods and procs registered with a particular callback, append _callback_chain to + # the callback name that you wish to list and send that to your class from the Rails console: # # >> Topic.after_save_callback_chain # => [#supplier_id
in supplier_id int(11). # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. - # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in company_name varchar(60). + # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in + # company_name varchar(60). # It will be mapped to one of the standard Rails SQL types in the type attribute. # +null+ determines if this column allows +NULL+ values. def initialize(name, default, sql_type = nil, null = true) @@ -359,7 +360,8 @@ module ActiveRecord # # Available options are (none of these exists by default): # * :limit - - # Requests a maximum column length. This is number of characters for :string and :text columns and number of bytes for :binary and :integer columns. + # Requests a maximum column length. This is number of characters for :string and + # :text columns and number of bytes for :binary and :integer columns. # * :default - # The column's default value. Use nil for NULL. # * :null - @@ -462,8 +464,8 @@ module ActiveRecord # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes. # # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type - # column if the :polymorphic option is supplied. If :polymorphic is a hash of options, these will be - # used when creating the _type column. So what can be written like this: + # column if the :polymorphic option is supplied. If :polymorphic is a hash of + # options, these will be used when creating the _type column. So what can be written like this: # # create_table :taggings do |t| # t.integer :tag_id, :tagger_id, :taggable_id diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index b403443d8e..cc7c07dc35 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -278,7 +278,8 @@ module ActiveRecord rows end - # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it. + # Executes a SQL query and returns a MySQL::Result object. Note that you have to free + # the Result object after you're done using it. def execute(sql, name = nil) #:nodoc: if name == :skip_logging @connection.query(sql) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 37e94502a4..6fae899e87 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -183,10 +183,14 @@ module ActiveRecord # * :username - Defaults to nothing. # * :password - Defaults to nothing. # * :database - The name of the database. No default, must be provided. - # * :schema_search_path - 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. - # * :encoding - An optional client encoding that is used in a SET client_encoding TO call on the connection. - # * :min_messages - An optional client min messages that is used in a SET client_min_messages TO call on the connection. - # * :allow_concurrency - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. + # * :schema_search_path - 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. + # * :encoding - An optional client encoding that is used in a SET client_encoding TO + # call on the connection. + # * :min_messages - An optional client min messages that is used in a + # SET client_min_messages TO call on the connection. + # * :allow_concurrency - If true, use async query methods so Ruby threads don't deadlock; + # otherwise, use blocking query methods. class PostgreSQLAdapter < AbstractAdapter ADAPTER_NAME = 'PostgreSQL'.freeze diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index e812a0371b..82ad0a3b8e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -29,8 +29,8 @@ module ActiveRecord end end - # 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 - # from http://rubyforge.org/projects/sqlite-ruby/). + # 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 from http://rubyforge.org/projects/sqlite-ruby/). # # Options: # diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 7aa725d095..e9ac5516ec 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -30,7 +30,8 @@ module ActiveRecord class SerializationTypeMismatch < ActiveRecordError end - # Raised when adapter not specified on connection (or configuration file config/database.yml misses adapter field). + # Raised when adapter not specified on connection (or configuration file config/database.yml + # misses adapter field). class AdapterNotSpecified < ActiveRecordError end @@ -38,7 +39,8 @@ module ActiveRecord class AdapterNotFound < ActiveRecordError end - # Raised when connection to the database could not been established (for example when connection= is given a nil object). + # Raised when connection to the database could not been established (for example when connection= + # is given a nil object). class ConnectionNotEstablished < ActiveRecordError end @@ -51,7 +53,8 @@ module ActiveRecord class RecordNotSaved < ActiveRecordError end - # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old). + # Raised when SQL statement cannot be executed by the database (for example, it's often the case for + # MySQL when Ruby driver used is too old). class StatementInvalid < ActiveRecordError end @@ -78,7 +81,8 @@ module ActiveRecord class InvalidForeignKey < WrappedDatabaseException end - # Raised when number of bind variables in statement given to :condition key (for example, when using +find+ method) + # Raised when number of bind variables in statement given to :condition key (for example, + # when using +find+ method) # does not match number of expected variables. # # For example, in @@ -165,4 +169,4 @@ module ActiveRecord @errors = errors end end -end \ No newline at end of file +end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 657303fd14..e44102b538 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -39,9 +39,10 @@ end # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures # in a non-verbose, human-readable format. It ships with Ruby 1.8.1+. # -# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed -# by ActiveSupport::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just -# put your files in /test/fixtures/). The fixture file ends with the .yml file extension (Rails example: +# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed +# in the directory appointed by ActiveSupport::TestCase.fixture_path=(path) (this is +# automatically configured for Rails, so you can just put your files in /test/fixtures/). +# The fixture file ends with the .yml file extension (Rails example: # /test/fixtures/web_sites.yml). The format of a YAML fixture file looks like this: # # rubyonrails: @@ -58,7 +59,8 @@ end # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing # pleasure. # -# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html +# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. +# See http://yaml.org/type/omap.html # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table. # This is commonly needed for tree structures. Example: # @@ -79,7 +81,8 @@ end # (Rails example: /test/fixtures/web_sites.csv). # # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us -# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised +# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the +# file is then comprised # of the actual data (1 per line). Here's an example: # # id, name, url @@ -99,15 +102,16 @@ end # # == Single-file fixtures # -# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats. -# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory -# appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just -# put your files in /test/fixtures// -- +# This type of fixture was the original format for Active Record that has since been deprecated in +# favor of the YAML and CSV formats. +# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) +# to the directory appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically +# configured for Rails, so you can just put your files in /test/fixtures// -- # like /test/fixtures/web_sites/ for the WebSite model). # # Each text file placed in this directory represents a "record". Usually these types of fixtures are named without -# extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. Here's what the -# above example might look like: +# extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. +# Here's what the above example might look like: # # web_sites/google # web_sites/yahoo.txt @@ -133,7 +137,8 @@ end # end # end # -# By default, the test_helper module will load all of your fixtures into your test database, so this test will succeed. +# By default, the test_helper module will load all of your fixtures into your test database, +# so this test will succeed. # The testing environment will automatically load the all fixtures into the database before each test. # To ensure consistent data, the environment deletes the fixtures before running the load. # @@ -182,13 +187,15 @@ end # This will create 1000 very simple YAML fixtures. # # Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>. -# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable -# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application -# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell. +# This is however a feature to be used with some caution. The point of fixtures are that they're +# stable units of predictable sample data. If you feel that you need to inject dynamic values, then +# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values +# in fixtures are to be considered a code smell. # # = Transactional fixtures # -# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case. +# TestCases can use begin+rollback to isolate their changes to the database instead of having to +# delete+insert for every test case. # # class FooTest < ActiveSupport::TestCase # self.use_transactional_fixtures = true @@ -205,15 +212,18 @@ end # end # # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, -# 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. +# 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. # # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide -# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+) +# access to fixture data for every table that has been loaded through fixtures (depending on the +# value of +use_instantiated_fixtures+) # # When *not* to use transactional fixtures: # -# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit, -# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify +# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until +# all parent transactions commit, particularly, the fixtures transaction which is begun in setup +# and rolled back in teardown. Thus, you won't be able to verify # the results of your transaction until Active Record supports nested transactions or savepoints (in progress). # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. # Use InnoDB, MaxDB, or NDB instead. diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 417ff4b5eb..0e560418dc 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -48,18 +48,21 @@ module ActiveRecord # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query Shirt.where(:color => 'red'). # - # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object - # constructed by a has_many declaration. For instance, you can invoke Shirt.red.first, Shirt.red.count, - # Shirt.red.where(:size => 'small'). Also, just as with the association objects, named \scopes act like an Array, - # implementing Enumerable; Shirt.red.each(&block), Shirt.red.first, and Shirt.red.inject(memo, &block) + # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it + # resembles the association object constructed by a has_many declaration. For instance, + # you can invoke Shirt.red.first, Shirt.red.count, Shirt.red.where(:size => 'small'). + # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable; + # Shirt.red.each(&block), Shirt.red.first, and Shirt.red.inject(memo, &block) # all behave as if Shirt.red really was an Array. # - # These named \scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only. - # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments - # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count). + # These named \scopes are composable. For instance, Shirt.red.dry_clean_only will produce + # all shirts that are both red and dry clean only. + # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count + # returns the number of garments for which these criteria obtain. Similarly with + # Shirt.red.dry_clean_only.average(:thread_count). # - # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to - # has_many associations. If, + # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which + # the \scopes were defined. But they are also available to has_many associations. If, # # class Person < ActiveRecord::Base # has_many :shirts diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index ce002f5e1a..78bac55bf2 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -67,8 +67,8 @@ module ActiveRecord # # == Configuration # - # In order to activate an observer, list it in the config.active_record.observers configuration setting in your - # config/application.rb file. + # In order to activate an observer, list it in the config.active_record.observers configuration + # setting in your config/application.rb file. # # config.active_record.observers = :comment_observer, :signup_observer # diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 38b91652ee..d5a0295b4b 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -91,8 +91,8 @@ module ActiveRecord # like render :partial => @client.becomes(Company) to render that # instance using the companies/company partial instead of clients/client. # - # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either - # instance will affect the other. + # Note: The new instance will share a link to the same attributes as the original class. + # So any change to the attributes in either instance will affect the other. def becomes(klass) became = klass.new became.instance_variable_set("@attributes", @attributes) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 44baeb6c84..f8412bc604 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -5,26 +5,33 @@ module ActiveRecord # Count operates using three different approaches. # # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model. - # * Count using column: By passing a column name to count, it will return a count of all the rows for the model with supplied column present + # * Count using column: By passing a column name to count, it will return a count of all the + # rows for the model with supplied column present # * Count using options will find the row count matched by the options used. # # The third approach, count using options, accepts an option hash as the only parameter. The options are: # - # * :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base. + # * :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. + # See conditions in the intro to ActiveRecord::Base. # * :joins: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed) - # or named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s). - # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. + # or named associations in the same form used for the :include option, which will + # perform an INNER JOIN on the associated table(s). + # If the value is a string, then the records will be returned read-only since they will have + # attributes that do not correspond to the table's columns. # Pass :readonly => false to override. - # * :include: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer - # to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting. + # * :include: Named associations that should be loaded alongside using LEFT OUTER JOINs. + # The symbols named refer to already defined associations. When using named associations, count + # returns the number of DISTINCT items for the model you're counting. # See eager loading under Associations. # * :order: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations). # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. - # * :select: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not + # * :select: By default, this is * as in SELECT * FROM, but can be changed if you, for example, + # want to do a join but not # include the joined columns. - # * :distinct: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ... - # * :from - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name - # of a database view). + # * :distinct: Set this to true to make this a distinct calculation, such as + # SELECT COUNT(DISTINCT posts.id) ... + # * :from - By default, this is the table name of the class, but can be changed to an + # alternate table name (or even the name of a database view). # # Examples for counting all: # Person.count # returns the total count of all people @@ -34,12 +41,19 @@ module ActiveRecord # # Examples for count with options: # Person.count(:conditions => "age > 26") - # 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. - # 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. + # + # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN. + # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) + # + # # finds the number of rows matching the conditions and joins. + # Person.count(:conditions => "age > 26 AND job.salary > 60000", + # :joins => "LEFT JOIN jobs on jobs.person_id = person.id") + # # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id) # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*') # - # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead. + # Note: Person.count(:all) will not work because it will use :all as the condition. + # Use Person.count instead. def count(column_name = nil, options = {}) column_name, options = nil, column_name if column_name.is_a?(Hash) calculate(:count, column_name, options) @@ -80,13 +94,15 @@ module ActiveRecord calculate(:sum, column_name, options) end - # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts. - # Options such as :conditions, :order, :group, :having, and :joins can be passed to customize the query. + # This calculates aggregate values in the given column. Methods for count, sum, average, + # minimum, and maximum have been added as shortcuts. Options such as :conditions, + # :order, :group, :having, and :joins can be passed to customize the query. # # There are two basic forms of output: - # * 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. - # * 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 - # of a belongs_to association. + # * 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. + # * 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 of a belongs_to association. # # values = Person.maximum(:age, :group => 'last_name') # puts values["Drake"] @@ -102,21 +118,30 @@ module ActiveRecord # end # # Options: - # * :conditions - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base. - # * :include: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses. - # * :joins - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). - # The records will be returned read-only since they will have attributes that do not correspond to the table's columns. + # * :conditions - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. + # See conditions in the intro to ActiveRecord::Base. + # * :include: Eager loading, see Associations for details. Since calculations don't load anything, + # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses. + # * :joins - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". + # (Rarely needed). + # The records will be returned read-only since they will have attributes that do not correspond to the + # table's columns. # * :order - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations). # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. - # * :select - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not - # include the joined columns. - # * :distinct - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ... + # * :select - By default, this is * as in SELECT * FROM, but can be changed if you for example + # want to do a join, but not include the joined columns. + # * :distinct - Set this to true to make this a distinct calculation, such as + # SELECT COUNT(DISTINCT posts.id) ... # # Examples: # Person.calculate(:count, :all) # The same as Person.count # Person.average(:age) # SELECT AVG(age) FROM people... - # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake' - # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors + # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for + # # everyone with a last name other than 'Drake' + # + # # Selects the minimum age for any family without any minors + # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) + # # Person.sum("2 * age") def calculate(operation, column_name, options = {}) if options.except(:distinct).present? diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3bf4c5bdd1..a192e044ea 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -21,23 +21,28 @@ module ActiveRecord # # ==== Parameters # - # * :conditions - An SQL fragment like "administrator = 1", [ "user_name = ?", username ], or ["user_name = :user_name", { :user_name => user_name }]. See conditions in the intro. + # * :conditions - An SQL fragment like "administrator = 1", [ "user_name = ?", username ], + # or ["user_name = :user_name", { :user_name => user_name }]. See conditions in the intro. # * :order - An SQL fragment like "created_at DESC, name". # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. - # * :having - Combined with +:group+ this can be used to filter the records that a GROUP BY returns. Uses the HAVING SQL-clause. + # * :having - Combined with +:group+ this can be used to filter the records that a + # GROUP BY returns. Uses the HAVING SQL-clause. # * :limit - An integer determining the limit on the number of rows that should be returned. - # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4. + # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, + # it would skip rows 0 through 4. # * :joins - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed), - # named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s), + # named associations in the same form used for the :include option, which will perform an + # INNER JOIN on the associated table(s), # or an array containing a mixture of both strings and named associations. - # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. + # If the value is a string, then the records will be returned read-only since they will + # have attributes that do not correspond to the table's columns. # Pass :readonly => false to override. # * :include - Names associations that should be loaded alongside. The symbols named refer # to already defined associations. See eager loading under Associations. - # * :select - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not - # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name"). - # * :from - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name - # of a database view). + # * :select - By default, this is "*" as in "SELECT * FROM", but can be changed if you, + # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name"). + # * :from - By default, this is the table name of the class, but can be changed + # to an alternate table name (or even the name of a database view). # * :readonly - Mark the returned records read-only so they cannot be saved or updated. # * :lock - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE". # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE". diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 92f7a7753d..83137433e8 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -21,7 +21,8 @@ module ActiveRecord # # This feature can easily be turned off by assigning value false . # - # If your attributes are time zone aware and you desire to skip time zone conversion for certain attributes then you can do following: + # If your attributes are time zone aware and you desire to skip time zone conversion for certain + # attributes then you can do following: # # Topic.skip_time_zone_conversion_for_attributes = [:written_on] module Timestamp diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 0b0f5682aa..15b587de45 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -27,8 +27,9 @@ module ActiveRecord # # this would specify a circular dependency and cause infinite recursion. # - # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association - # is both present and guaranteed to be valid, you also need to use +validates_presence_of+. + # NOTE: This validation will not fail if the association hasn't been assigned. If you want to + # ensure that the association is both present and guaranteed to be valid, you also need to + # use +validates_presence_of+. # # Configuration options: # * :message - A custom error message (default is: "is invalid") @@ -44,4 +45,4 @@ module ActiveRecord end end end -end \ No newline at end of file +end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 1c9ecc7b1b..bf863c7063 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -78,22 +78,25 @@ module ActiveRecord end module ClassMethods - # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user + # Validates whether the value of the specified attributes are unique across the system. + # Useful for making sure that only one user # can be named "davidhh". # # class Person < ActiveRecord::Base # validates_uniqueness_of :user_name, :scope => :account_id # end # - # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example, - # making sure that a teacher can only be on the schedule once per semester for a particular class. + # It can also validate whether the value of the specified attributes are unique based on multiple + # scope parameters. For example, making sure that a teacher can only be on the schedule once + # per semester for a particular class. # # class TeacherSchedule < ActiveRecord::Base # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] # end # - # 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 - # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself. + # 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 attribute (that maps to a column). When the record is updated, + # the same check is made but disregarding the record itself. # # Configuration options: # * :message - Specifies a custom error message (default is: "has already been taken"). @@ -102,11 +105,12 @@ module ActiveRecord # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+). # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+). # * :if - Specifies a method, proc or string to call to determine if the validation should - # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The - # method, proc or string should return or evaluate to a true or false value. + # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). + # The method, proc or string should return or evaluate to a true or false value. # * :unless - Specifies a method, proc or string to call to determine if the validation should - # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The - # method, proc or string should return or evaluate to a true or false value. + # not occur (e.g. :unless => :skip_validation, or + # :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should + # return or evaluate to a true or false value. # # === Concurrency and integrity # -- cgit v1.2.3 From 88b5f938cf7d3eb26ad204451a4dbb9c2cf4f571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 Aug 2010 18:40:20 +0200 Subject: Bring returning back to ease migration. --- .../lib/active_support/core_ext/object.rb | 1 + .../active_support/core_ext/object/returning.rb | 43 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 activesupport/lib/active_support/core_ext/object/returning.rb diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index 790a26f5c1..d671da6711 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/object/acts_like' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/try' +require 'active_support/core_ext/object/returning' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' diff --git a/activesupport/lib/active_support/core_ext/object/returning.rb b/activesupport/lib/active_support/core_ext/object/returning.rb new file mode 100644 index 0000000000..07250b2a27 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/returning.rb @@ -0,0 +1,43 @@ +class Object + # Returns +value+ after yielding +value+ to the block. This simplifies the + # process of constructing an object, performing work on the object, and then + # returning the object from a method. It is a Ruby-ized realization of the K + # combinator, courtesy of Mikael Brockman. + # + # ==== Examples + # + # # Without returning + # def foo + # values = [] + # values << "bar" + # values << "baz" + # return values + # end + # + # foo # => ['bar', 'baz'] + # + # # returning with a local variable + # def foo + # returning values = [] do + # values << 'bar' + # values << 'baz' + # end + # end + # + # foo # => ['bar', 'baz'] + # + # # returning with a block argument + # def foo + # returning [] do |values| + # values << 'bar' + # values << 'baz' + # end + # end + # + # foo # => ['bar', 'baz'] + def returning(value) + ActiveSupport::Deprecation.warn('Object#returning has been deprecated in favor of Object#tap.', caller) + yield(value) + value + end +end \ No newline at end of file -- cgit v1.2.3 From d3eacf9352ee34e2965c0b1781cdb8a1799686ec Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 2 Aug 2010 13:07:18 -0400 Subject: Adding to AR::Base documentation about dynamically scopeded_by query User.scoped_by_user_name('David') --- activerecord/lib/active_record/base.rb | 26 ++++++++++++++-------- .../lib/active_record/dynamic_finder_match.rb | 4 ++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5535079cd8..335f26d221 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -166,20 +166,22 @@ module ActiveRecord #:nodoc: # # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects # by simple queries without turning to SQL. They work by appending the name of an attribute - # to find_by_, find_last_by_, or find_all_by_, so you get finders + # to find_by_, find_last_by_, or find_all_by_ and thus produces finders # like Person.find_by_user_name, Person.find_all_by_last_name, and - # Payment.find_by_transaction_id. So instead of writing + # Payment.find_by_transaction_id. Instead of writing # Person.where(:user_name => user_name).first, you just do Person.find_by_user_name(user_name). # And instead of writing Person.where(:last_name => last_name).all, you just do # Person.find_all_by_last_name(last_name). # - # It's also possible to use multiple attributes in the same find by separating them with "_and_", - # so you get finders like Person.find_by_user_name_and_password or even - # Payment.find_by_purchaser_and_state_and_country. So instead of writing - # Person.where(:user_name => user_name, :password => password).first, you just do - # Person.find_by_user_name_and_password(user_name, password). + # It's also possible to use multiple attributes in the same find by separating them with "_and_". + # + # Person.where(:user_name => user_name, :password => password).first + # Person.find_by_user_name_and_password #with dynamic finder + # + # Person.where(:user_name => user_name, :password => password, :gender => 'male').first + # Payment.find_by_user_name_and_password_and_gender # - # It's even possible to call these dynamic finder methods on relations and named scopes. For example : + # It's even possible to call these dynamic finder methods on relations and named scopes. # # Payment.order("created_on").find_all_by_amount(50) # Payment.pending.find_last_by_amount(100) @@ -187,7 +189,7 @@ module ActiveRecord #:nodoc: # The same dynamic finder style can be used to create the object if it doesn't already exist. # This dynamic finder is called with find_or_create_by_ and will return the object if # it already exists and otherwise creates it, then returns it. Protected attributes won't be set - # unless they are given in a block. For example: + # unless they are given in a block. # # # No 'Summer' tag exists # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer") @@ -213,6 +215,12 @@ module ActiveRecord #:nodoc: # That will either find an existing tag named "rails", or create a new one while setting the # user that created it. # + # Just like find_by_*, you can also use scoped_by_* to retrieve data. The good thing about + # using this feature is that the very first time result is returned using method_missing technique + # but after that the method is declared on the class. Henceforth method_missing will not be hit. + # + # User.scoped_by_user_name('David') + # # == Saving arrays, hashes, and other non-mappable objects in text columns # # Active Record can serialize any object in text columns using YAML. To do so, you must diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index dfb8a3ba60..0dc965bd26 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -2,8 +2,8 @@ module ActiveRecord # = Active Record Dynamic Finder Match # - # Provides dynamic attribute-based finders such as find_by_country - # if, for example, the Person has an attribute with that name. + # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info + # class DynamicFinderMatch def self.match(method) df_match = self.new(method) -- cgit v1.2.3 From db0e3e5ad1db9bc0204db1dbc4075180bce3bc93 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 2 Aug 2010 13:10:32 -0400 Subject: Correcting the documentation which wrongly states that each dynamic finder creates method on the class. Only dynamic finders using scoped_by_* creates new methods. --- activerecord/lib/active_record/base.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 335f26d221..12736d3d5b 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -966,17 +966,14 @@ module ActiveRecord #:nodoc: end end - # Enables dynamic finders like find_by_user_name(user_name) and - # find_by_user_name_and_password(user_name, password) that are turned into - # where(:user_name => user_name).first and - # where(:user_name => user_name, :password => :password).first - # respectively. Also works for all by using find_all_by_amount(50) - # that is turned into where(:amount => 50).all. + # Enables dynamic finders like User.find_by_user_name(user_name) and + # User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders + # section at the top of this file for more detailed information. # # It's even possible to use all the additional parameters to +find+. For example, the # full interface for +find_all_by_amount+ is actually find_all_by_amount(amount, options). # - # Each dynamic finder, scope or initializer/creator is also defined in the class after it + # Each dynamic finder using scoped_by_* is also defined in the class after it # is first invoked, so that future attempts to use it do not run through method_missing. def method_missing(method_id, *arguments, &block) if match = DynamicFinderMatch.match(method_id) -- cgit v1.2.3 From ab64499911184faee11147bc1c88dd2616e848ab Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 2 Aug 2010 14:51:57 -0700 Subject: reload the owner model after update --- activerecord/test/cases/timestamp_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 06ab7aa9c7..a57b3dfba1 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -93,6 +93,7 @@ class TimestampTest < ActiveRecord::TestCase owner = pet.owner owner.update_attribute(:updated_at, (time = 3.days.ago)) + owner.reload toy.touch assert_not_equal time, owner.updated_at -- cgit v1.2.3 From 42035fd112fb8034a706d033bfc63ee2a2797a96 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 2 Aug 2010 15:10:34 -0700 Subject: move reload after touch --- activerecord/test/cases/timestamp_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index a57b3dfba1..401439994d 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -93,8 +93,8 @@ class TimestampTest < ActiveRecord::TestCase owner = pet.owner owner.update_attribute(:updated_at, (time = 3.days.ago)) - owner.reload toy.touch + owner.reload assert_not_equal time, owner.updated_at ensure -- cgit v1.2.3 From d8b90114ddd2a432280d114b9fe4ef1fdb38d132 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 2 Aug 2010 15:11:00 -0700 Subject: skip the before_type_cast_on_datetime test entirely for mysql2 --- activerecord/test/cases/base_test.rb | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 34cce04794..0992b2fefb 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -123,23 +123,24 @@ class BasicsTest < ActiveRecord::TestCase end end - def test_read_attributes_before_type_cast_on_datetime - developer = Developer.find(:first) - # Oracle adapter returns Time before type cast - if current_adapter?(:OracleAdapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) - - developer.created_at = "345643456" - assert_equal developer.created_at_before_type_cast, "345643456" - assert_equal developer.created_at, nil - - developer.created_at = "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") - elsif current_adapter?(:Mysql2Adapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) - else - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] + unless current_adapter?(:Mysql2Adapter) + def test_read_attributes_before_type_cast_on_datetime + developer = Developer.find(:first) + # Oracle adapter returns Time before type cast + if current_adapter?(:OracleAdapter) + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + + developer.created_at = "345643456" + assert_equal developer.created_at_before_type_cast, "345643456" + assert_equal developer.created_at, nil + + developer.created_at = "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + else + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] + end end end -- cgit v1.2.3 From 74f7e172c7a660286bfd2b265e299c55078fc68e Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 3 Aug 2010 04:14:01 -0400 Subject: fixing documentation --- activemodel/lib/active_model/errors.rb | 4 ++-- activerecord/lib/active_record/base.rb | 18 +++++++++--------- activerecord/lib/active_record/callbacks.rb | 4 ++-- activerecord/lib/active_record/counter_cache.rb | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index f39678db83..edafb53ad5 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -37,11 +37,11 @@ module ActiveModel # send(attr) # end # - # def ErrorsPerson.human_attribute_name(attr, options = {}) + # def Person.human_attribute_name(attr, options = {}) # attr # end # - # def ErrorsPerson.lookup_ancestors + # def Person.lookup_ancestors # [self] # end # diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 12736d3d5b..7371a1ecb2 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -516,7 +516,8 @@ module ActiveRecord #:nodoc: connection.select_value(sql, "#{name} Count").to_i end - # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards. + # Attributes listed as readonly will be used to create a new record but update operations will + # ignore these fields. def attr_readonly(*attributes) write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || [])) end @@ -604,8 +605,8 @@ module ActiveRecord #:nodoc: (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix end - # Defines the column name for use with single table inheritance - # -- can be set in subclasses like so: self.inheritance_column = "type_id" + # Defines the column name for use with single table inheritance. Use + # set_inheritance_column to set a different value. def inheritance_column @inheritance_column ||= "type".freeze end @@ -622,8 +623,8 @@ module ActiveRecord #:nodoc: default end - # Sets the table name to use to the given value, or (if the value - # is nil or false) to the value returned by the given block. + # Sets the table name. If the value is nil or false then the value returned by the given + # block is used. # # class Project < ActiveRecord::Base # set_table_name "project" @@ -1034,8 +1035,8 @@ module ActiveRecord #:nodoc: end protected - # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash. - # method_name may be :find or :create. :find parameter is Relation while + # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be + # :find or :create. :find parameter is Relation while # :create parameters are an attributes hash. # # class Article < ActiveRecord::Base @@ -1080,8 +1081,7 @@ module ActiveRecord #:nodoc: # end # end # - # *Note*: the +:find+ scope also has effect on update and deletion methods, - # like +update_all+ and +delete_all+. + # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+. def with_scope(method_scoping = {}, action = :merge, &block) method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping) diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 7f10fbf8b0..aa92bf999f 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -68,8 +68,8 @@ module ActiveRecord # end # # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is - # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the situation where - # we've implemented the save behavior through overwriteable methods: + # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation + # where the +before_destroy+ methis is overriden: # # class Topic < ActiveRecord::Base # def before_destroy() destroy_author end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 237cd56683..b0e0b45e16 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -1,7 +1,7 @@ module ActiveRecord # = Active Record Counter Cache module CounterCache - # Resets one or more counter caches to their correct value using an SQL + # Resets one or more counter caches to their correct value using a SQL # count query. This is useful when adding new counter caches, or if the # counter has been corrupted or modified directly by SQL. # -- cgit v1.2.3 From 807239f5a191205080b43c8d7316d4de60b13f6b Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 2 Aug 2010 19:43:32 -0400 Subject: Making Active Record base_test.rb thinner by moving tests to relevant files. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Number of assertions before refactoring: 2391 tests, 7579 assertions, 0 failures, 0 errors Number of assertions after refactoring: 2391 tests, 7579 assertions, 0 failures, 0 errors Signed-off-by: José Valim --- activerecord/test/cases/associations_test.rb | 22 + activerecord/test/cases/attribute_methods_test.rb | 278 ++++++++++- activerecord/test/cases/base_test.rb | 532 ---------------------- activerecord/test/cases/persistence_test.rb | 113 +++++ activerecord/test/cases/serialization_test.rb | 134 ++++++ 5 files changed, 545 insertions(+), 534 deletions(-) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index d328ca630b..b31611e27a 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -32,6 +32,28 @@ class AssociationsTest < ActiveRecord::TestCase assert_equal 1, liquids[0].molecules.length end + def test_clear_association_cache_stored + firm = Firm.find(1) + assert_kind_of Firm, firm + + firm.clear_association_cache + assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort + end + + def test_clear_association_cache_new_record + firm = Firm.new + client_stored = Client.find(3) + client_new = Client.new + client_new.name = "The Joneses" + clients = [ client_stored, client_new ] + + firm.clients << clients + assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set + + firm.clear_association_cache + assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set + end + def test_loading_the_association_target_should_keep_child_records_marked_for_destruction ship = Ship.create!(:name => "The good ship Dollypop") part = ship.parts.create!(:name => "Mast") diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index d59fa0a632..d20b762853 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,9 +1,15 @@ require "cases/helper" -require 'models/topic' require 'models/minimalistic' +require 'models/developer' +require 'models/auto_id' +require 'models/computer' +require 'models/topic' +require 'models/company' +require 'models/category' +require 'models/reply' class AttributeMethodsTest < ActiveRecord::TestCase - fixtures :topics + fixtures :topics, :developers, :companies, :computers def setup @old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup @@ -16,6 +22,274 @@ class AttributeMethodsTest < ActiveRecord::TestCase ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end + def test_attribute_present + t = Topic.new + t.title = "hello there!" + t.written_on = Time.now + assert t.attribute_present?("title") + assert t.attribute_present?("written_on") + assert !t.attribute_present?("content") + end + + def test_attribute_keys_on_new_instance + t = Topic.new + assert_equal nil, t.title, "The topics table has a title column, so it should be nil" + assert_raise(NoMethodError) { t.title2 } + end + + def test_boolean_attributes + assert ! Topic.find(1).approved? + assert Topic.find(2).approved? + end + + def test_set_attributes + topic = Topic.find(1) + topic.attributes = { "title" => "Budget", "author_name" => "Jason" } + topic.save + assert_equal("Budget", topic.title) + assert_equal("Jason", topic.author_name) + assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) + end + + def test_set_attributes_without_hash + topic = Topic.new + assert_nothing_raised { topic.attributes = '' } + end + + def test_integers_as_nil + test = AutoId.create('value' => '') + assert_nil AutoId.find(test.id).value + end + + def test_set_attributes_with_block + topic = Topic.new do |t| + t.title = "Budget" + t.author_name = "Jason" + end + + assert_equal("Budget", topic.title) + assert_equal("Jason", topic.author_name) + end + + def test_respond_to? + topic = Topic.find(1) + assert_respond_to topic, "title" + assert_respond_to topic, "title?" + assert_respond_to topic, "title=" + assert_respond_to topic, :title + assert_respond_to topic, :title? + assert_respond_to topic, :title= + assert_respond_to topic, "author_name" + assert_respond_to topic, "attribute_names" + assert !topic.respond_to?("nothingness") + assert !topic.respond_to?(:nothingness) + end + + def test_array_content + topic = Topic.new + topic.content = %w( one two three ) + topic.save + + assert_equal(%w( one two three ), Topic.find(topic.id).content) + end + + def test_read_attributes_before_type_cast + category = Category.new({:name=>"Test categoty", :type => nil}) + category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil} + assert_equal category_attrs , category.attributes_before_type_cast + end + + if current_adapter?(:MysqlAdapter) + def test_read_attributes_before_type_cast_on_boolean + bool = Booleantest.create({ "value" => false }) + assert_equal "0", bool.reload.attributes_before_type_cast["value"] + end + end + + def test_read_attributes_before_type_cast_on_datetime + developer = Developer.find(:first) + # Oracle adapter returns Time before type cast + unless current_adapter?(:OracleAdapter) + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] + else + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + + developer.created_at = "345643456" + assert_equal developer.created_at_before_type_cast, "345643456" + assert_equal developer.created_at, nil + + developer.created_at = "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") + end + end + + def test_hash_content + topic = Topic.new + topic.content = { "one" => 1, "two" => 2 } + topic.save + + assert_equal 2, Topic.find(topic.id).content["two"] + + topic.content_will_change! + topic.content["three"] = 3 + topic.save + + assert_equal 3, Topic.find(topic.id).content["three"] + end + + def test_update_array_content + topic = Topic.new + topic.content = %w( one two three ) + + topic.content.push "four" + assert_equal(%w( one two three four ), topic.content) + + topic.save + + topic = Topic.find(topic.id) + topic.content << "five" + assert_equal(%w( one two three four five ), topic.content) + end + + def test_case_sensitive_attributes_hash + # DB2 is not case-sensitive + return true if current_adapter?(:DB2Adapter) + + assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes + end + + def test_hashes_not_mangled + new_topic = { :title => "New Topic" } + new_topic_values = { :title => "AnotherTopic" } + + topic = Topic.new(new_topic) + assert_equal new_topic[:title], topic.title + + topic.attributes= new_topic_values + assert_equal new_topic_values[:title], topic.title + end + + def test_create_through_factory + topic = Topic.create("title" => "New Topic") + topicReloaded = Topic.find(topic.id) + assert_equal(topic, topicReloaded) + end + + def test_write_attribute + topic = Topic.new + topic.send(:write_attribute, :title, "Still another topic") + assert_equal "Still another topic", topic.title + + topic.send(:write_attribute, "title", "Still another topic: part 2") + assert_equal "Still another topic: part 2", topic.title + end + + def test_read_attribute + topic = Topic.new + topic.title = "Don't change the topic" + assert_equal "Don't change the topic", topic.send(:read_attribute, "title") + assert_equal "Don't change the topic", topic["title"] + + assert_equal "Don't change the topic", topic.send(:read_attribute, :title) + assert_equal "Don't change the topic", topic[:title] + end + + def test_read_attribute_when_false + topic = topics(:first) + topic.approved = false + assert !topic.approved?, "approved should be false" + topic.approved = "false" + assert !topic.approved?, "approved should be false" + end + + def test_read_attribute_when_true + topic = topics(:first) + topic.approved = true + assert topic.approved?, "approved should be true" + topic.approved = "true" + assert topic.approved?, "approved should be true" + end + + def test_read_write_boolean_attribute + topic = Topic.new + # puts "" + # puts "New Topic" + # puts topic.inspect + topic.approved = "false" + # puts "Expecting false" + # puts topic.inspect + assert !topic.approved?, "approved should be false" + topic.approved = "false" + # puts "Expecting false" + # puts topic.inspect + assert !topic.approved?, "approved should be false" + topic.approved = "true" + # puts "Expecting true" + # puts topic.inspect + assert topic.approved?, "approved should be true" + topic.approved = "true" + # puts "Expecting true" + # puts topic.inspect + assert topic.approved?, "approved should be true" + # puts "" + end + + def test_query_attribute_string + [nil, "", " "].each do |value| + assert_equal false, Topic.new(:author_name => value).author_name? + end + + assert_equal true, Topic.new(:author_name => "Name").author_name? + end + + def test_query_attribute_number + [nil, 0, "0"].each do |value| + assert_equal false, Developer.new(:salary => value).salary? + end + + assert_equal true, Developer.new(:salary => 1).salary? + assert_equal true, Developer.new(:salary => "1").salary? + end + + def test_query_attribute_boolean + [nil, "", false, "false", "f", 0].each do |value| + assert_equal false, Topic.new(:approved => value).approved? + end + + [true, "true", "1", 1].each do |value| + assert_equal true, Topic.new(:approved => value).approved? + end + end + + def test_query_attribute_with_custom_fields + object = Company.find_by_sql(<<-SQL).first + SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value + FROM companies c1, companies c2 + WHERE c1.firm_id = c2.id + AND c1.id = 2 + SQL + + assert_equal "Firm", object.string_value + assert object.string_value? + + object.string_value = " " + assert !object.string_value? + + assert_equal 1, object.int_value.to_i + assert object.int_value? + + object.int_value = "0" + assert !object.int_value? + end + + def test_non_attribute_access_and_assignment + topic = Topic.new + assert !topic.respond_to?("mumbo") + assert_raise(NoMethodError) { topic.mumbo } + assert_raise(NoMethodError) { topic.mumbo = 5 } + end + def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing topic = @target.new(:title => 'Budget') assert topic.respond_to?('title') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index df6895f0d0..56ec4ca58c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -52,254 +52,6 @@ class BasicsTest < ActiveRecord::TestCase assert Topic.table_exists? end - def test_set_attributes - topic = Topic.find(1) - topic.attributes = { "title" => "Budget", "author_name" => "Jason" } - topic.save - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) - assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) - end - - def test_set_attributes_without_hash - topic = Topic.new - assert_nothing_raised { topic.attributes = '' } - end - - def test_integers_as_nil - test = AutoId.create('value' => '') - assert_nil AutoId.find(test.id).value - end - - def test_set_attributes_with_block - topic = Topic.new do |t| - t.title = "Budget" - t.author_name = "Jason" - end - - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) - end - - def test_respond_to? - topic = Topic.find(1) - assert_respond_to topic, "title" - assert_respond_to topic, "title?" - assert_respond_to topic, "title=" - assert_respond_to topic, :title - assert_respond_to topic, :title? - assert_respond_to topic, :title= - assert_respond_to topic, "author_name" - assert_respond_to topic, "attribute_names" - assert !topic.respond_to?("nothingness") - assert !topic.respond_to?(:nothingness) - end - - def test_array_content - topic = Topic.new - topic.content = %w( one two three ) - topic.save - - assert_equal(%w( one two three ), Topic.find(topic.id).content) - end - - def test_read_attributes_before_type_cast - category = Category.new({:name=>"Test categoty", :type => nil}) - category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil} - assert_equal category_attrs , category.attributes_before_type_cast - end - - if current_adapter?(:MysqlAdapter) - def test_read_attributes_before_type_cast_on_boolean - bool = Booleantest.create({ "value" => false }) - assert_equal "0", bool.reload.attributes_before_type_cast["value"] - end - end - - def test_read_attributes_before_type_cast_on_datetime - developer = Developer.find(:first) - # Oracle adapter returns Time before type cast - unless current_adapter?(:OracleAdapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] - else - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) - - developer.created_at = "345643456" - assert_equal developer.created_at_before_type_cast, "345643456" - assert_equal developer.created_at, nil - - developer.created_at = "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") - end - end - - def test_hash_content - topic = Topic.new - topic.content = { "one" => 1, "two" => 2 } - topic.save - - assert_equal 2, Topic.find(topic.id).content["two"] - - topic.content_will_change! - topic.content["three"] = 3 - topic.save - - assert_equal 3, Topic.find(topic.id).content["three"] - end - - def test_update_array_content - topic = Topic.new - topic.content = %w( one two three ) - - topic.content.push "four" - assert_equal(%w( one two three four ), topic.content) - - topic.save - - topic = Topic.find(topic.id) - topic.content << "five" - assert_equal(%w( one two three four five ), topic.content) - end - - def test_case_sensitive_attributes_hash - # DB2 is not case-sensitive - return true if current_adapter?(:DB2Adapter) - - assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes - end - - def test_hashes_not_mangled - new_topic = { :title => "New Topic" } - new_topic_values = { :title => "AnotherTopic" } - - topic = Topic.new(new_topic) - assert_equal new_topic[:title], topic.title - - topic.attributes= new_topic_values - assert_equal new_topic_values[:title], topic.title - end - - def test_create_through_factory - topic = Topic.create("title" => "New Topic") - topicReloaded = Topic.find(topic.id) - assert_equal(topic, topicReloaded) - end - - def test_write_attribute - topic = Topic.new - topic.send(:write_attribute, :title, "Still another topic") - assert_equal "Still another topic", topic.title - - topic.send(:write_attribute, "title", "Still another topic: part 2") - assert_equal "Still another topic: part 2", topic.title - end - - def test_read_attribute - topic = Topic.new - topic.title = "Don't change the topic" - assert_equal "Don't change the topic", topic.send(:read_attribute, "title") - assert_equal "Don't change the topic", topic["title"] - - assert_equal "Don't change the topic", topic.send(:read_attribute, :title) - assert_equal "Don't change the topic", topic[:title] - end - - def test_read_attribute_when_false - topic = topics(:first) - topic.approved = false - assert !topic.approved?, "approved should be false" - topic.approved = "false" - assert !topic.approved?, "approved should be false" - end - - def test_read_attribute_when_true - topic = topics(:first) - topic.approved = true - assert topic.approved?, "approved should be true" - topic.approved = "true" - assert topic.approved?, "approved should be true" - end - - def test_read_write_boolean_attribute - topic = Topic.new - # puts "" - # puts "New Topic" - # puts topic.inspect - topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect - assert !topic.approved?, "approved should be false" - topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect - assert !topic.approved?, "approved should be false" - topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect - assert topic.approved?, "approved should be true" - topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect - assert topic.approved?, "approved should be true" - # puts "" - end - - def test_query_attribute_string - [nil, "", " "].each do |value| - assert_equal false, Topic.new(:author_name => value).author_name? - end - - assert_equal true, Topic.new(:author_name => "Name").author_name? - end - - def test_query_attribute_number - [nil, 0, "0"].each do |value| - assert_equal false, Developer.new(:salary => value).salary? - end - - assert_equal true, Developer.new(:salary => 1).salary? - assert_equal true, Developer.new(:salary => "1").salary? - end - - def test_query_attribute_boolean - [nil, "", false, "false", "f", 0].each do |value| - assert_equal false, Topic.new(:approved => value).approved? - end - - [true, "true", "1", 1].each do |value| - assert_equal true, Topic.new(:approved => value).approved? - end - end - - def test_query_attribute_with_custom_fields - object = Company.find_by_sql(<<-SQL).first - SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value - FROM companies c1, companies c2 - WHERE c1.firm_id = c2.id - AND c1.id = 2 - SQL - - assert_equal "Firm", object.string_value - assert object.string_value? - - object.string_value = " " - assert !object.string_value? - - assert_equal 1, object.int_value.to_i - assert object.int_value? - - object.int_value = "0" - assert !object.int_value? - end - - def test_non_attribute_access_and_assignment - topic = Topic.new - assert !topic.respond_to?("mumbo") - assert_raise(NoMethodError) { topic.mumbo } - assert_raise(NoMethodError) { topic.mumbo = 5 } - end - def test_preserving_date_objects if current_adapter?(:SybaseAdapter) # Sybase ctlib does not (yet?) support the date type; use datetime instead. @@ -531,38 +283,6 @@ class BasicsTest < ActiveRecord::TestCase GUESSED_CLASSES.each(&:reset_table_name) end - def test_destroy_all - conditions = "author_name = 'Mary'" - topics_by_mary = Topic.all(:conditions => conditions, :order => 'id') - assert ! topics_by_mary.empty? - - assert_difference('Topic.count', -topics_by_mary.size) do - destroyed = Topic.destroy_all(conditions).sort_by(&:id) - assert_equal topics_by_mary, destroyed - assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen" - end - end - - def test_destroy_many - clients = Client.find([2, 3], :order => 'id') - - assert_difference('Client.count', -2) do - destroyed = Client.destroy([2, 3]).sort_by(&:id) - assert_equal clients, destroyed - assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" - end - end - - def test_delete_many - original_count = Topic.count - Topic.delete(deleting = [1, 2]) - assert_equal original_count - deleting.size, Topic.count - end - - def test_boolean_attributes - assert ! Topic.find(1).approved? - assert Topic.find(2).approved? - end if current_adapter?(:MysqlAdapter) def test_update_all_with_order_and_limit @@ -570,63 +290,6 @@ class BasicsTest < ActiveRecord::TestCase end end - # Oracle UPDATE does not support ORDER BY - unless current_adapter?(:OracleAdapter) - def test_update_all_ignores_order_without_limit_from_association - author = authors(:david) - assert_nothing_raised do - assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ]) - end - end - - def test_update_all_with_order_and_limit_updates_subset_only - author = authors(:david) - assert_nothing_raised do - assert_equal 1, author.posts_sorted_by_id_limited.size - assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size - assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) - assert_equal "bulk update!", posts(:welcome).body - assert_not_equal "bulk update!", posts(:thinking).body - end - end - end - - def test_update_many - topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } - updated = Topic.update(topic_data.keys, topic_data.values) - - assert_equal 2, updated.size - assert_equal "1 updated", Topic.find(1).content - assert_equal "2 updated", Topic.find(2).content - end - - def test_delete_all - assert Topic.count > 0 - - assert_equal Topic.count, Topic.delete_all - end - - def test_update_by_condition - Topic.update_all "content = 'bulk updated!'", ["approved = ?", true] - assert_equal "Have a nice day", Topic.find(1).content - assert_equal "bulk updated!", Topic.find(2).content - end - - def test_attribute_present - t = Topic.new - t.title = "hello there!" - t.written_on = Time.now - assert t.attribute_present?("title") - assert t.attribute_present?("written_on") - assert !t.attribute_present?("content") - end - - def test_attribute_keys_on_new_instance - t = Topic.new - assert_equal nil, t.title, "The topics table has a title column, so it should be nil" - assert_raise(NoMethodError) { t.title2 } - end - def test_null_fields assert_nil Topic.find(1).parent_id assert_nil Topic.create("title" => "Hey you").parent_id @@ -710,8 +373,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end - - def test_readonly_attributes assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes @@ -1313,49 +974,6 @@ class BasicsTest < ActiveRecord::TestCase end end - def test_increment_attribute - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).increment! :credit_limit - assert_equal 51, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) - assert_equal 53, accounts(:signals37, :reload).credit_limit - end - - def test_increment_nil_attribute - assert_nil topics(:first).parent_id - topics(:first).increment! :parent_id - assert_equal 1, topics(:first).parent_id - end - - def test_increment_attribute_by - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).increment! :credit_limit, 5 - assert_equal 55, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) - assert_equal 59, accounts(:signals37, :reload).credit_limit - end - - def test_decrement_attribute - assert_equal 50, accounts(:signals37).credit_limit - - accounts(:signals37).decrement!(:credit_limit) - assert_equal 49, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) - assert_equal 47, accounts(:signals37, :reload).credit_limit - end - - def test_decrement_attribute_by - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).decrement! :credit_limit, 5 - assert_equal 45, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) - assert_equal 41, accounts(:signals37, :reload).credit_limit - end - def test_toggle_attribute assert !topics(:first).approved? topics(:first).toggle!(:approved) @@ -1482,28 +1100,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal res6, res7 end - def test_clear_association_cache_stored - firm = Firm.find(1) - assert_kind_of Firm, firm - - firm.clear_association_cache - assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort - end - - def test_clear_association_cache_new_record - firm = Firm.new - client_stored = Client.find(3) - client_new = Client.new - client_new.name = "The Joneses" - clients = [ client_stored, client_new ] - - firm.clients << clients - assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set - - firm.clear_association_cache - assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set - end - def test_interpolate_sql assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') } assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') } @@ -1703,134 +1299,6 @@ class BasicsTest < ActiveRecord::TestCase assert_no_queries { assert true } end - def test_to_xml - xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) - bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema - written_on_in_current_timezone = topics(:first).written_on.xmlschema - last_read_in_current_timezone = topics(:first).last_read.xmlschema - - assert_equal "topic", xml.root.name - assert_equal "The First Topic" , xml.elements["//title"].text - assert_equal "David" , xml.elements["//author-name"].text - assert_match "Have a nice day", xml.elements["//content"].text - - assert_equal "1", xml.elements["//id"].text - assert_equal "integer" , xml.elements["//id"].attributes['type'] - - assert_equal "1", xml.elements["//replies-count"].text - assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] - - assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text - assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] - - assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text - - assert_equal nil, xml.elements["//parent-id"].text - assert_equal "integer", xml.elements["//parent-id"].attributes['type'] - assert_equal "true", xml.elements["//parent-id"].attributes['nil'] - - if current_adapter?(:SybaseAdapter) - assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text - assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] - else - # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) - assert_equal "2004-04-15", xml.elements["//last-read"].text - assert_equal "date" , xml.elements["//last-read"].attributes['type'] - end - - # Oracle and DB2 don't have true boolean or time-only fields - unless current_adapter?(:OracleAdapter, :DB2Adapter) - assert_equal "false", xml.elements["//approved"].text - assert_equal "boolean" , xml.elements["//approved"].attributes['type'] - - assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text - assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] - end - end - - def test_to_xml_skipping_attributes - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) - assert_equal "", xml.first(7) - assert !xml.include?(%(The First Topic)) - assert xml.include?(%(David)) - - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) - assert !xml.include?(%(The First Topic)) - assert !xml.include?(%(David)) - end - - def test_to_xml_including_has_many_association - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) - assert_equal "", xml.first(7) - assert xml.include?(%()) - assert xml.include?(%(The Second Topic of the day)) - end - - def test_array_to_xml_including_has_many_association - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) - assert xml.include?(%()) - end - - def test_array_to_xml_including_methods - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) - assert xml.include?(%(#{topics(:first).topic_id})), xml - assert xml.include?(%(#{topics(:second).topic_id})), xml - end - - def test_array_to_xml_including_has_one_association - xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) - assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_array_to_xml_including_belongs_to_association - xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_to_xml_including_belongs_to_association - xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert !xml.include?("") - - xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?("") - end - - def test_to_xml_including_multiple_associations - xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) - assert_equal "", xml.first(6) - assert xml.include?(%()) - assert xml.include?(%()) - end - - def test_to_xml_including_multiple_associations_with_options - xml = companies(:first_firm).to_xml( - :indent => 0, :skip_instruct => true, - :include => { :clients => { :only => :name } } - ) - - assert_equal "", xml.first(6) - assert xml.include?(%(Summit)) - assert xml.include?(%()) - end - - def test_to_xml_including_methods - xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) - assert_equal "", xml.first(9) - assert xml.include?(%(I am Jack's profound disappointment)) - end - - def test_to_xml_with_block - value = "Rockin' the block" - xml = Company.new.to_xml(:skip_instruct => true) do |_xml| - _xml.tag! "arbitrary-element", value - end - assert_equal "", xml.first(9) - assert xml.include?(%(#{value})) - end - def test_to_param_should_return_string assert_kind_of String, Client.find(:first).to_param end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 1cc3a337c3..d7666b19f6 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -19,6 +19,119 @@ class PersistencesTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans + # Oracle UPDATE does not support ORDER BY + unless current_adapter?(:OracleAdapter) + def test_update_all_ignores_order_without_limit_from_association + author = authors(:david) + assert_nothing_raised do + assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ]) + end + end + + def test_update_all_with_order_and_limit_updates_subset_only + author = authors(:david) + assert_nothing_raised do + assert_equal 1, author.posts_sorted_by_id_limited.size + assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size + assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) + assert_equal "bulk update!", posts(:welcome).body + assert_not_equal "bulk update!", posts(:thinking).body + end + end + end + + def test_update_many + topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } + updated = Topic.update(topic_data.keys, topic_data.values) + + assert_equal 2, updated.size + assert_equal "1 updated", Topic.find(1).content + assert_equal "2 updated", Topic.find(2).content + end + + def test_delete_all + assert Topic.count > 0 + + assert_equal Topic.count, Topic.delete_all + end + + def test_update_by_condition + Topic.update_all "content = 'bulk updated!'", ["approved = ?", true] + assert_equal "Have a nice day", Topic.find(1).content + assert_equal "bulk updated!", Topic.find(2).content + end + + def test_increment_attribute + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).increment! :credit_limit + assert_equal 51, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) + assert_equal 53, accounts(:signals37, :reload).credit_limit + end + + def test_increment_nil_attribute + assert_nil topics(:first).parent_id + topics(:first).increment! :parent_id + assert_equal 1, topics(:first).parent_id + end + + def test_increment_attribute_by + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).increment! :credit_limit, 5 + assert_equal 55, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) + assert_equal 59, accounts(:signals37, :reload).credit_limit + end + + def test_destroy_all + conditions = "author_name = 'Mary'" + topics_by_mary = Topic.all(:conditions => conditions, :order => 'id') + assert ! topics_by_mary.empty? + + assert_difference('Topic.count', -topics_by_mary.size) do + destroyed = Topic.destroy_all(conditions).sort_by(&:id) + assert_equal topics_by_mary, destroyed + assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen" + end + end + + def test_destroy_many + clients = Client.find([2, 3], :order => 'id') + + assert_difference('Client.count', -2) do + destroyed = Client.destroy([2, 3]).sort_by(&:id) + assert_equal clients, destroyed + assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" + end + end + + def test_delete_many + original_count = Topic.count + Topic.delete(deleting = [1, 2]) + assert_equal original_count - deleting.size, Topic.count + end + + def test_decrement_attribute + assert_equal 50, accounts(:signals37).credit_limit + + accounts(:signals37).decrement!(:credit_limit) + assert_equal 49, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) + assert_equal 47, accounts(:signals37, :reload).credit_limit + end + + def test_decrement_attribute_by + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).decrement! :credit_limit, 5 + assert_equal 45, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) + assert_equal 41, accounts(:signals37, :reload).credit_limit + end + def test_create topic = Topic.new topic.title = "New Topic" diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 8c385af97c..dab81530af 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,7 +1,13 @@ require "cases/helper" require 'models/contact' +require 'models/topic' +require 'models/reply' +require 'models/company' class SerializationTest < ActiveRecord::TestCase + + fixtures :topics, :companies, :accounts + FORMATS = [ :xml, :json ] def setup @@ -17,6 +23,134 @@ class SerializationTest < ActiveRecord::TestCase @contact = Contact.new(@contact_attributes) end + def test_to_xml + xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) + bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema + written_on_in_current_timezone = topics(:first).written_on.xmlschema + last_read_in_current_timezone = topics(:first).last_read.xmlschema + + assert_equal "topic", xml.root.name + assert_equal "The First Topic" , xml.elements["//title"].text + assert_equal "David" , xml.elements["//author-name"].text + assert_match "Have a nice day", xml.elements["//content"].text + + assert_equal "1", xml.elements["//id"].text + assert_equal "integer" , xml.elements["//id"].attributes['type'] + + assert_equal "1", xml.elements["//replies-count"].text + assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] + + assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text + assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] + + assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text + + assert_equal nil, xml.elements["//parent-id"].text + assert_equal "integer", xml.elements["//parent-id"].attributes['type'] + assert_equal "true", xml.elements["//parent-id"].attributes['nil'] + + if current_adapter?(:SybaseAdapter) + assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text + assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] + else + # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) + assert_equal "2004-04-15", xml.elements["//last-read"].text + assert_equal "date" , xml.elements["//last-read"].attributes['type'] + end + + # Oracle and DB2 don't have true boolean or time-only fields + unless current_adapter?(:OracleAdapter, :DB2Adapter) + assert_equal "false", xml.elements["//approved"].text + assert_equal "boolean" , xml.elements["//approved"].attributes['type'] + + assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text + assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] + end + end + + def test_to_xml_skipping_attributes + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) + assert_equal "", xml.first(7) + assert !xml.include?(%(The First Topic)) + assert xml.include?(%(David)) + + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) + assert !xml.include?(%(The First Topic)) + assert !xml.include?(%(David)) + end + + def test_to_xml_including_has_many_association + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) + assert_equal "", xml.first(7) + assert xml.include?(%()) + assert xml.include?(%(The Second Topic of the day)) + end + + def test_array_to_xml_including_has_many_association + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) + assert xml.include?(%()) + end + + def test_array_to_xml_including_methods + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) + assert xml.include?(%(#{topics(:first).topic_id})), xml + assert xml.include?(%(#{topics(:second).topic_id})), xml + end + + def test_array_to_xml_including_has_one_association + xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) + assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_array_to_xml_including_belongs_to_association + xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_to_xml_including_belongs_to_association + xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert !xml.include?("") + + xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?("") + end + + def test_to_xml_including_multiple_associations + xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) + assert_equal "", xml.first(6) + assert xml.include?(%()) + assert xml.include?(%()) + end + + def test_to_xml_including_multiple_associations_with_options + xml = companies(:first_firm).to_xml( + :indent => 0, :skip_instruct => true, + :include => { :clients => { :only => :name } } + ) + + assert_equal "", xml.first(6) + assert xml.include?(%(Summit)) + assert xml.include?(%()) + end + + def test_to_xml_including_methods + xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) + assert_equal "", xml.first(9) + assert xml.include?(%(I am Jack's profound disappointment)) + end + + def test_to_xml_with_block + value = "Rockin' the block" + xml = Company.new.to_xml(:skip_instruct => true) do |_xml| + _xml.tag! "arbitrary-element", value + end + assert_equal "", xml.first(9) + assert xml.include?(%(#{value})) + end + def test_serialize_should_be_reversible for format in FORMATS @serialized = Contact.new.send("to_#{format}") -- cgit v1.2.3 From db1c484c55758e25c56615d6efdab8a22cff4a46 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 2 Aug 2010 21:22:02 -0400 Subject: Dynamic finder method like scoped_by_* create methods so that method_missing is not hit next time. Adding a test for this scenario. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/test/cases/named_scope_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 7c037b20c5..c42dda2ccb 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -478,4 +478,10 @@ class DynamicScopeTest < ActiveRecord::TestCase assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1) assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"}) end + + def test_dynamic_scope_should_create_methods_after_hitting_method_missing + assert Developer.methods.grep(/scoped_by_created_at/).blank? + Developer.scoped_by_created_at(nil) + assert Developer.methods.grep(/scoped_by_created_at/).present? + end end -- cgit v1.2.3 From fb2b8fec245a96da015c5c6223ad264fa47d90bd Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 3 Aug 2010 04:32:48 -0400 Subject: adding test cases for ActiveModel::Errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activemodel/test/cases/errors_test.rb | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 activemodel/test/cases/errors_test.rb diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb new file mode 100644 index 0000000000..79b45bb298 --- /dev/null +++ b/activemodel/test/cases/errors_test.rb @@ -0,0 +1,65 @@ +require "cases/helper" + +class ErrorsTest < ActiveModel::TestCase + class Person + extend ActiveModel::Naming + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "can not be nil") if name == nil + end + + def read_attribute_for_validation(attr) + send(attr) + end + + def self.human_attribute_name(attr, options = {}) + attr + end + + def self.lookup_ancestors + [self] + end + + end + + test "method validate! should work" do + person = Person.new + person.validate! + assert_equal ["name can not be nil"], person.errors.full_messages + assert_equal ["can not be nil"], person.errors[:name] + + end + + test 'should be able to assign error' do + person = Person.new + person.errors[:name] = 'should not be nil' + assert_equal ["should not be nil"], person.errors[:name] + end + + test 'should be able to add an error on an attribute' do + person = Person.new + person.errors.add(:name, "can not be blank") + assert_equal ["can not be blank"], person.errors[:name] + end + + test 'should respond to size' do + person = Person.new + person.errors.add(:name, "can not be blank") + assert_equal 1, person.errors.size + end + + test 'to_a should return an array' do + person = Person.new + person.errors.add(:name, "can not be blank") + person.errors.add(:name, "can not be nil") + assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a + + end + +end -- cgit v1.2.3 From 2c8a4a53a8c38a43a62342b9d46014242e781d18 Mon Sep 17 00:00:00 2001 From: Tore Darell Date: Mon, 2 Aug 2010 21:15:44 +0200 Subject: Remove or fix non-working examples and add a few tests to Dirty [#5185 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activemodel/lib/active_model/dirty.rb | 9 +--- activemodel/test/cases/dirty_test.rb | 90 +++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 5ea7636427..2516377afd 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -37,12 +37,13 @@ module ActiveModel # end # # def name=(val) - # name_will_change! + # name_will_change! unless val == @name # @name = val # end # # def save # @previously_changed = changes + # @changed_attributes.clear # end # # end @@ -77,12 +78,6 @@ module ActiveModel # person.changed # => ['name'] # person.changes # => { 'name' => ['Bill', 'Bob'] } # - # Resetting an attribute returns it to its original state: - # person.reset_name! # => 'Bill' - # person.changed? # => false - # person.name_changed? # => false - # person.name # => 'Bill' - # # If an attribute is modified in-place then make use of [attribute_name]_will_change! # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to # in-place attributes. diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index e1a35be384..858ae9cb69 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -3,10 +3,11 @@ require "cases/helper" class DirtyTest < ActiveModel::TestCase class DirtyModel include ActiveModel::Dirty - define_attribute_methods [:name] + define_attribute_methods [:name, :color] def initialize @name = nil + @color = nil end def name @@ -17,13 +18,92 @@ class DirtyTest < ActiveModel::TestCase name_will_change! @name = val end + + def color + @color + end + + def color=(val) + color_will_change! unless val == @color + @color = val + end + + def save + @previously_changed = changes + @changed_attributes.clear + end + end + + setup do + @model = DirtyModel.new + end + + test "setting attribute will result in change" do + assert !@model.changed? + assert !@model.name_changed? + @model.name = "Ringo" + assert @model.changed? + assert @model.name_changed? + end + + test "list of changed attributes" do + assert_equal [], @model.changed + @model.name = "Paul" + assert_equal ['name'], @model.changed + end + + test "changes to attribute values" do + assert !@model.changes['name'] + @model.name = "John" + assert_equal [nil, "John"], @model.changes['name'] end test "changes accessible through both strings and symbols" do - model = DirtyModel.new - model.name = "David" - assert_not_nil model.changes[:name] - assert_not_nil model.changes['name'] + @model.name = "David" + assert_not_nil @model.changes[:name] + assert_not_nil @model.changes['name'] + end + + test "attribute mutation" do + @model.instance_variable_set("@name", "Yam") + assert !@model.name_changed? + @model.name.replace("Hadad") + assert !@model.name_changed? + @model.name_will_change! + @model.name.replace("Baal") + assert @model.name_changed? + end + + test "resetting attribute" do + @model.name = "Bob" + @model.reset_name! + assert_nil @model.name + #assert !@model.name_changed #Doesn't work yet + end + + test "setting color to same value should not result in change being recorded" do + @model.color = "red" + assert @model.color_changed? + @model.save + assert !@model.color_changed? + assert !@model.changed? + @model.color = "red" + assert !@model.color_changed? + assert !@model.changed? + end + + test "saving should reset model's changed status" do + @model.name = "Alf" + assert @model.changed? + @model.save + assert !@model.changed? + assert !@model.name_changed? + end + + test "saving should preserve previous changes" do + @model.name = "Jericho Cane" + @model.save + assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] end end -- cgit v1.2.3 From 1466f312bad35809fa9fe343068d2fc8a814ffe6 Mon Sep 17 00:00:00 2001 From: Curtis Cablegram Date: Wed, 28 Jul 2010 19:34:04 -0500 Subject: Change log-tailer to properly track multi-byte characters. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When end-of-line is represented within a file as "\r\n", it is represented in memory as a single "\n". This patch eliminates the discrepancy between size on disk and size in memory. Signed-off-by: José Valim --- railties/lib/rails/rack/log_tailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb index 3fa45156c3..2ca6b5f8da 100644 --- a/railties/lib/rails/rack/log_tailer.rb +++ b/railties/lib/rails/rack/log_tailer.rb @@ -24,7 +24,7 @@ module Rails if mod > @last_checked contents = @file.read @last_checked = mod - @cursor += contents.size + @cursor = @file.tell $stdout.print contents end end -- cgit v1.2.3 From e1142dfcae036bd8c6400962f9e07112b750b730 Mon Sep 17 00:00:00 2001 From: Curtis Cablegram Date: Wed, 28 Jul 2010 19:47:53 -0500 Subject: Refactor log-tailer to depend on File#eof? rather than File#mtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [#5220 state:resolved] Eliminate 1 instance variable and 1 local variable. Signed-off-by: José Valim --- railties/lib/rails/rack/log_tailer.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb index 2ca6b5f8da..011ac6cecc 100644 --- a/railties/lib/rails/rack/log_tailer.rb +++ b/railties/lib/rails/rack/log_tailer.rb @@ -6,7 +6,6 @@ module Rails path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath @cursor = ::File.size(path) - @last_checked = Time.now.to_f @file = ::File.open(path, 'r') end @@ -20,10 +19,8 @@ module Rails def tail! @file.seek @cursor - mod = @file.mtime.to_f - if mod > @last_checked + if !@file.eof? contents = @file.read - @last_checked = mod @cursor = @file.tell $stdout.print contents end -- cgit v1.2.3 From f01184ad9e7efbbcdb8581c93b92e16c61c7a6bd Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Tue, 3 Aug 2010 11:25:15 +0200 Subject: Avoid potentially expensive inspect call in router. [#4491 state:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_dispatch/routing/route_set.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 77688fe397..d23b580d97 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -392,10 +392,9 @@ module ActionDispatch end def generate - error = ActionController::RoutingError.new("No route matches #{options.inspect}") path, params = @set.set.generate(:path_info, named_route, options, recall, opts) - raise error unless path + raise_routing_error unless path params.reject! {|k,v| !v } @@ -404,7 +403,7 @@ module ActionDispatch path << "?#{params.to_query}" if params.any? "#{script_name}#{path}" rescue Rack::Mount::RoutingError - raise error + raise_routing_error end def opts @@ -421,6 +420,10 @@ module ActionDispatch {:parameterize => parameterize} end + def raise_routing_error + raise ActionController::RoutingError.new("No route matches #{options.inspect}") + end + def different_controller? return false unless current_controller controller.to_param != current_controller.to_param -- cgit v1.2.3 From 621246f997887eccf61c1737c76b1eefc9217263 Mon Sep 17 00:00:00 2001 From: rohit Date: Tue, 3 Aug 2010 16:28:55 +0530 Subject: Failing test for validates_length_of, when both too_short and too_long messages are set [#5283 state:open] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../test/cases/validations/length_validation_test.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 012c5a2f37..1e6180a938 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -229,6 +229,20 @@ class LengthValidationTest < ActiveModel::TestCase assert_equal ["hoo 5"], t.errors["title"] end + def test_validates_length_of_custom_errors_for_both_too_short_and_too_long + Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long' + + t = Topic.new(:title => 'a') + assert t.invalid? + assert t.errors[:title].any? + assert_equal ['too short'], t.errors['title'] + + t = Topic.new(:title => 'aaaaaa') + assert t.invalid? + assert t.errors[:title].any? + assert_equal ['too long'], t.errors['title'] + end + def test_validates_length_of_custom_errors_for_is_with_message Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") -- cgit v1.2.3 From f23bc8444b1f1193b2f9135474545436be97b195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 Aug 2010 15:22:54 +0200 Subject: validates_length_of should not change the options hash in place. [#5283 state:resolved] --- activemodel/lib/active_model/validations/length.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index c8a77ad666..a7af4f2b4d 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -40,8 +40,6 @@ module ActiveModel CHECKS.each do |key, validity_check| next unless check_value = options[key] - default_message = options[MESSAGES[key]] - options[:message] ||= default_message if default_message valid_value = if key == :maximum value.nil? || value.size.send(validity_check, check_value) @@ -51,8 +49,13 @@ module ActiveModel next if valid_value - record.errors.add(attribute, MESSAGES[key], - options.except(*RESERVED_OPTIONS).merge!(:count => check_value)) + errors_options = options.except(*RESERVED_OPTIONS) + errors_options[:count] = check_value + + default_message = options[MESSAGES[key]] + errors_options[:message] ||= default_message if default_message + + record.errors.add(attribute, MESSAGES[key], errors_options) end end end -- cgit v1.2.3 From 84081fcc548b309a79580b03aab6a39b726e0b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 Aug 2010 15:34:31 +0200 Subject: Freeze options so we raise an error when people modify it in place. --- activemodel/lib/active_model/validations.rb | 6 ++++-- activemodel/lib/active_model/validator.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 1a58d4c4fb..3407c59e7a 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -118,11 +118,13 @@ module ActiveModel # end # def validate(*args, &block) - options = args.last - if options.is_a?(Hash) && options.key?(:on) + options = args.extract_options! + if options.key?(:on) + options = options.dup options[:if] = Array.wrap(options[:if]) options[:if] << "validation_context == :#{options[:on]}" end + args << options set_callback(:validate, *args, &block) end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 52192d5988..163124d531 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -111,7 +111,7 @@ module ActiveModel #:nodoc: # Accepts options that will be made available through the +options+ reader. def initialize(options) - @options = options + @options = options.freeze end # Return the kind for this validator. -- cgit v1.2.3 From b8634dddabb84f74a1e5314ea1f26833a6feebc4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 3 Aug 2010 02:05:00 -0300 Subject: Stub is_a? not instance_of? here --- railties/test/generators/app_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 21725a380c..08cfb585f9 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -114,7 +114,7 @@ class AppGeneratorTest < Rails::Generators::TestCase Rails.application.config.root = app_moved_root Rails.application.class.stubs(:name).returns("Myapp") - Rails.application.stubs(:instance_of?).returns(Rails::Application) + Rails.application.stubs(:is_a?).returns(Rails::Application) FileUtils.mv(app_root, app_moved_root) -- cgit v1.2.3 From 41c1aa607dc9633c05e7946b7d2cf6f5c6954317 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 3 Aug 2010 09:16:01 -0700 Subject: order query is already a string, no need to to_s --- activerecord/lib/active_record/relation/query_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a92d180442..5ef00206dd 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -95,7 +95,7 @@ module ActiveRecord order_clause = arel.send(:order_clauses).join(', ') relation = except(:order) - if order_clause.present? + unless order_clauses.blank? relation.order(reverse_sql_order(order_clause)) else relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") @@ -238,7 +238,7 @@ module ActiveRecord end def reverse_sql_order(order_query) - order_query.to_s.split(/,/).each { |s| + order_query.split(',').each { |s| if s.match(/\s(asc|ASC)$/) s.gsub!(/\s(asc|ASC)$/, ' DESC') elsif s.match(/\s(desc|DESC)$/) -- cgit v1.2.3 From 8d9d8bc93cd603d76c39779a55142715d8153e88 Mon Sep 17 00:00:00 2001 From: Subba Rao Pasupuleti Date: Tue, 3 Aug 2010 12:35:58 -0400 Subject: Tidy up error.rb code [#5288 state:committed] Signed-off-by: Santiago Pastorino --- activemodel/lib/active_model/errors.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index f39678db83..05e353ce18 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -86,11 +86,7 @@ module ActiveModel # p.errors[:name] # => ["can not be nil"] # p.errors['name'] # => ["can not be nil"] def [](attribute) - if errors = get(attribute.to_sym) - errors - else - set(attribute.to_sym, []) - end + get(attribute.to_sym) || set(attribute.to_sym, []) end # Adds to the supplied attribute the supplied error message. -- cgit v1.2.3 From b3bb684ecbde94884eaf4e69c5c68d7274485caa Mon Sep 17 00:00:00 2001 From: Mark Hayes Date: Tue, 3 Aug 2010 12:12:16 -0700 Subject: removed duplicate word --- actionpack/README.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index 0e7d164623..b297ceb0e2 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -1,6 +1,6 @@ = Action Pack -- From request to response -Action Pack is a framework for handling and responding to web requests. It it +Action Pack is a framework for handling and responding to web requests. It provides mechanisms for *routing* (mapping request URLs to actions), defining *controllers* that implement actions, and generating responses by rendering *views*, which are templates of various formats. In short, Action Pack -- cgit v1.2.3 From c0fa4de65b32fc5e8251dafab1330f044d682a1e Mon Sep 17 00:00:00 2001 From: wycats Date: Tue, 3 Aug 2010 12:22:10 -0700 Subject: Allow :name to be a Symbol (was this removed by accident?) --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index ffc3847a31..7dee68502f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -327,6 +327,8 @@ module ActiveRecord # # Note: SQLite doesn't support index length def add_index(table_name, column_name, options = {}) + options[:name] = options[:name].to_s if options.key?(:name) + column_names = Array.wrap(column_name) index_name = index_name(table_name, :column => column_names) -- cgit v1.2.3 From 02a5842cd09bd75de4c2fdb6b474c6c0ff163ebf Mon Sep 17 00:00:00 2001 From: wycats Date: Tue, 3 Aug 2010 14:54:28 -0700 Subject: Put lib back on the autoload path --- railties/lib/rails/engine/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 521ed95447..db0b20df33 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -20,7 +20,7 @@ module Rails paths.app.models "app/models", :eager_load => true paths.app.mailers "app/mailers", :eager_load => true paths.app.views "app/views" - paths.lib "lib", :load_path => true + paths.lib "lib", :eager_load => true paths.lib.tasks "lib/tasks", :glob => "**/*.rake" paths.config "config" paths.config.initializers "config/initializers", :glob => "**/*.rb" -- cgit v1.2.3 From 7e4e1f0ca5722008b288224221405eba486345c1 Mon Sep 17 00:00:00 2001 From: RainerBlessing Date: Tue, 3 Aug 2010 20:37:46 +0200 Subject: query value is converted to_s instead of to_yaml Signed-off-by: Santiago Pastorino --- .../lib/active_record/connection_adapters/abstract/quoting.rb | 2 +- activerecord/test/cases/finder_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index d7b5bf8e31..e2b3773a99 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -30,7 +30,7 @@ module ActiveRecord if value.acts_like?(:date) || value.acts_like?(:time) "'#{quoted_date(value)}'" else - "'#{quote_string(value.to_yaml)}'" + "'#{quote_string(value.to_s)}'" end end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index a107c1a474..4f3e43d77d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -693,6 +693,14 @@ class FinderTest < ActiveRecord::TestCase assert_equal [], Topic.find_all_by_title("The First Topic!!") end + def test_find_all_by_one_attribute_which_is_a_symbol + topics = Topic.find_all_by_content("Have a nice day".to_sym) + assert_equal 2, topics.size + assert topics.include?(topics(:first)) + + assert_equal [], Topic.find_all_by_title("The First Topic!!") + end + def test_find_all_by_one_attribute_that_is_an_aggregate balance = customers(:david).balance assert_kind_of Money, balance -- cgit v1.2.3 From 6d6ed5532416aa75889cd34a669696ba640abeb5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 3 Aug 2010 17:57:36 -0700 Subject: avoid passing AR::Base objects to Arel when we can --- activerecord/lib/active_record/relation/finder_methods.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a192e044ea..bad9af250d 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -169,6 +169,8 @@ module ActiveRecord # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists? def exists?(id = nil) + id = id.id if ActiveRecord::Base === id + case id when Array, Hash where(id).exists? -- cgit v1.2.3 From 8bf79739b4219eb1d6464e6eb4853e92e81d7621 Mon Sep 17 00:00:00 2001 From: wycats Date: Wed, 4 Aug 2010 02:16:48 -0700 Subject: require_dependency should require using the normal mechanism if possible to avoid double-requires --- activesupport/lib/active_support/dependencies.rb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 2b80bd214f..1b93eac7ee 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -276,14 +276,22 @@ module ActiveSupport #:nodoc: end def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb") - path = search_for_file(file_name) - require_or_load(path || file_name) - rescue LoadError => load_error - unless swallow_load_errors - if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] - raise LoadError.new(message % file_name).copy_blame!(load_error) + #path = search_for_file(file_name) + require_or_load(file_name) + rescue LoadError + begin + if path = search_for_file(file_name) + require_or_load(path) + else + raise + end + rescue LoadError => load_error + unless swallow_load_errors + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + raise LoadError.new(message % file_name).copy_blame!(load_error) + end + raise end - raise end end -- cgit v1.2.3 From 991cd59a225b90ab1ba31867810b8fc0942713eb Mon Sep 17 00:00:00 2001 From: wycats Date: Wed, 4 Aug 2010 03:21:37 -0700 Subject: If a file is in the load path, require it without its full path (in more places) --- activesupport/lib/active_support/dependencies.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 1b93eac7ee..198c124292 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -388,8 +388,13 @@ module ActiveSupport #:nodoc: end # Search for a file in autoload_paths matching the provided suffix. - def search_for_file(path_suffix) - path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb") + def search_for_file(file) + path_suffix = file.sub(/(\.rb)?$/, ".rb") + + $:.each do |root| + path = File.join(root, path_suffix) + return file if File.file?(path) + end autoload_paths.each do |root| path = File.join(root, path_suffix) -- cgit v1.2.3 From 847c123ca5ce47f1c27e8553d845b33689191ad4 Mon Sep 17 00:00:00 2001 From: wycats Date: Wed, 4 Aug 2010 03:20:44 -0700 Subject: Concernify SanitizeHelper and TextHelper so including TextHelper correctly include SanitizeHelper and extends its ClassMethods --- actionpack/lib/action_view/helpers/sanitize_helper.rb | 1 + actionpack/lib/action_view/helpers/text_helper.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 63f6154ec4..d5638a69e5 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -7,6 +7,7 @@ module ActionView # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. # These helper methods extend Action View making them callable within your template files. module SanitizeHelper + extend ActiveSupport::Concern # This +sanitize+ helper will html encode all tags and strip all attributes that # aren't specifically allowed. # diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 52a016c9c1..c1de5c8cb3 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -10,6 +10,9 @@ module ActionView # your views. These helper methods extend Action View making them callable # within your template files. module TextHelper + extend ActiveSupport::Concern + + include SanitizeHelper # The preferred method of outputting text in your views is to use the # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods # do not operate as expected in an eRuby code block. If you absolutely must -- cgit v1.2.3 From c25c3879ce27f5e834defd71409ea3cb2378aac6 Mon Sep 17 00:00:00 2001 From: wycats Date: Wed, 4 Aug 2010 04:05:28 -0700 Subject: I'm unsure how cloning was working in Rails 3 before --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 12736d3d5b..7710aa7c54 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1400,7 +1400,7 @@ MSG # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is # application specific and is therefore left to the application to implement according to its need. def initialize_copy(other) - callback(:after_initialize) if respond_to_without_attributes?(:after_initialize) + _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks) cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) cloned_attributes.delete(self.class.primary_key) -- cgit v1.2.3 From 7745f716a175b680f140c7b735261e5a3064e7e9 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Mon, 21 Jun 2010 12:07:13 +0200 Subject: Fixed Railtie Rdoc examples not properly formatted [#4918 state:resolved] Signed-off-by: Simone Carletti --- railties/lib/rails/railtie.rb | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index f0d9d95fc4..8a6e2716dc 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -6,53 +6,53 @@ require 'active_support/deprecation' module Rails # Railtie is the core of the Rails Framework and provides several hooks to extend # Rails and/or modify the initialization process. - # + # # Every major component of Rails (Action Mailer, Action Controller, # Action View, Active Record and Active Resource) are all Railties, so each of # them is responsible to set their own initialization. This makes, for example, # Rails absent of any Active Record hook, allowing any other ORM framework to hook in. - # + # # Developing a Rails extension does _not_ require any implementation of # Railtie, but if you need to interact with the Rails framework during # or after boot, then Railtie is what you need to do that interaction. - # + # # For example, the following would need you to implement Railtie in your # plugin: - # + # # * creating initializers # * configuring a Rails framework or the Application, like setting a generator # * adding Rails config.* keys to the environment # * setting up a subscriber to the Rails +ActiveSupport::Notifications+ # * adding rake tasks into rails - # + # # == Creating your Railtie # # Implementing Railtie in your Rails extension is done by creating a class # Railtie that has your extension name and making sure that this gets loaded # during boot time of the Rails stack. - # + # # You can do this however you wish, but here is an example if you want to provide # it for a gem that can be used with or without Rails: - # + # # * Create a file (say, lib/my_gem/railtie.rb) which contains class Railtie inheriting from # Rails::Railtie and is namespaced to your gem: # - # # lib/my_gem/railtie.rb - # module MyGem - # class Railtie < Rails::Railtie + # # lib/my_gem/railtie.rb + # module MyGem + # class Railtie < Rails::Railtie + # end # end - # end - # + # # * Require your own gem as well as rails in this file: - # - # # lib/my_gem/railtie.rb - # require 'my_gem' - # require 'rails' - # - # module MyGem - # class Railtie < Rails::Railtie + # + # # lib/my_gem/railtie.rb + # require 'my_gem' + # require 'rails' + # + # module MyGem + # class Railtie < Rails::Railtie + # end # end - # end # # == Initializers # @@ -65,7 +65,7 @@ module Rails # end # end # - # If specified, the block can also receive the application object, in case you + # If specified, the block can also receive the application object, in case you # need to access some application specific configuration, like middleware: # # class MyRailtie < Rails::Railtie -- cgit v1.2.3 From 7e2399a42feb47407ad0cb0688815812f68804de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 4 Aug 2010 14:13:50 +0200 Subject: Revert "Put lib back on the autoload path" This was causing engines/gems to eager load everything in lib. Another fix is comming soon. This reverts commit 02a5842cd09bd75de4c2fdb6b474c6c0ff163ebf. --- railties/lib/rails/engine/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index db0b20df33..521ed95447 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -20,7 +20,7 @@ module Rails paths.app.models "app/models", :eager_load => true paths.app.mailers "app/mailers", :eager_load => true paths.app.views "app/views" - paths.lib "lib", :eager_load => true + paths.lib "lib", :load_path => true paths.lib.tasks "lib/tasks", :glob => "**/*.rake" paths.config "config" paths.config.initializers "config/initializers", :glob => "**/*.rb" -- cgit v1.2.3 From cff7f53fbcce14f190cc2d993467ba77fe8fa03d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 4 Aug 2010 08:44:35 -0700 Subject: do not pass AR objects to ARel --- activerecord/lib/active_record/relation/finder_methods.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index bad9af250d..b34c11973b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -286,6 +286,8 @@ module ActiveRecord end def find_one(id) + id = id.id if ActiveRecord::Base === id + record = where(primary_key.eq(id)).first unless record -- cgit v1.2.3 From 462666b73717333d460684339c6f6ce07475f713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 4 Aug 2010 14:17:00 -0300 Subject: Revert "If a file is in the load path, require it without its full path (in more places)" Caused failures in ActionMailer test suite. This reverts commit 991cd59a225b90ab1ba31867810b8fc0942713eb. --- activesupport/lib/active_support/dependencies.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 198c124292..1b93eac7ee 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -388,13 +388,8 @@ module ActiveSupport #:nodoc: end # Search for a file in autoload_paths matching the provided suffix. - def search_for_file(file) - path_suffix = file.sub(/(\.rb)?$/, ".rb") - - $:.each do |root| - path = File.join(root, path_suffix) - return file if File.file?(path) - end + def search_for_file(path_suffix) + path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb") autoload_paths.each do |root| path = File.join(root, path_suffix) -- cgit v1.2.3 From 84f0a0bc30df58e1edfd09fdde2de891e4577321 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 18:58:18 +0200 Subject: Reload action_methods in AbstractController after defining new method. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionmailer/test/base_test.rb | 12 ++++++++++++ actionpack/lib/abstract_controller/base.rb | 12 ++++++++++++ actionpack/test/abstract/abstract_controller_test.rb | 14 ++++++++++++++ activesupport/lib/active_support/callbacks.rb | 5 ++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index e2b9df5d02..fec0ecf477 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -507,6 +507,18 @@ class BaseTest < ActiveSupport::TestCase assert_equal("Thanks for signing up this afternoon", mail.subject) end + test "action methods should be refreshed after defining new method" do + class FooMailer < ActionMailer::Base + # this triggers action_methods + self.respond_to?(:foo) + + def notify + end + end + + assert_equal ["notify"], FooMailer.action_methods + end + protected # Execute the block setting the given values and restoring old values after diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 8a8337858b..db0a6736e0 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -72,6 +72,13 @@ module AbstractController end end + # action_methods are cached and there is sometimes need to refresh + # them. clear_action_methods! allows you to do that, so next time + # you run action_methods, they will be recalculated + def clear_action_methods! + @action_methods = nil + end + # Returns the full controller name, underscored, without the ending Controller. # For instance, MyApp::MyPostsController would return "my_app/my_posts" for # controller_name. @@ -81,6 +88,11 @@ module AbstractController def controller_path @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? end + + def method_added(name) + super + clear_action_methods! + end end abstract! diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 3b5013a47a..19855490b4 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -250,5 +250,19 @@ module AbstractController end end + class Me6 < AbstractController::Base + self.action_methods + + def index + end + end + + class TestActionMethodsReloading < ActiveSupport::TestCase + + test "action_methods should be reloaded after defining a new method" do + assert_equal ["index"], Me6.action_methods + end + end + end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 1c7802f7de..0fafd56f33 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -419,7 +419,10 @@ module ActiveSupport @_keyed_callbacks ||= {} @_keyed_callbacks[name] ||= begin str = send("_#{kind}_callbacks").compile(name, object) - class_eval "def #{name}() #{str} end", __FILE__, __LINE__ + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{name}() #{str} end + protected :#{name} + RUBY_EVAL true end end -- cgit v1.2.3 From 589e6977d72b232b6e1af5ef508beddffbcd5f4c Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Wed, 4 Aug 2010 17:02:38 -0400 Subject: adding documentation to ActiveSupport::Concern ht:strictly typed for an awesome example some minor documentation changes --- activemodel/lib/active_model/serialization.rb | 2 ++ activesupport/lib/active_support/callbacks.rb | 4 ++- activesupport/lib/active_support/concern.rb | 35 +++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 5670ec74cb..e675937f4d 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -61,6 +61,8 @@ module ActiveModel # person.serializable_hash # => {"name"=>"Bob"} # person.to_json # => "{\"name\":\"Bob\"}" # person.to_xml # => "\n:only, :except and :methods . module Serialization def serializable_hash(options = nil) options ||= {} diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 1c7802f7de..adabdd3388 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -568,7 +568,9 @@ module ActiveSupport # # would trigger Audit#before_save instead. That's constructed by calling # "#{kind}_#{name}" on the given instance. In this case "kind" is "before" and - # "name" is "save". + # "name" is "save". In this context treat ":kind" and ":name" as special thing where + # ":kind" refers to "callback type(before/after)" and ":name" refers to the method on + # which callbacks are being defined. # # A declaration like # diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index eb31f7cad4..408d327dd7 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -1,3 +1,38 @@ +# A typical module looks like this +# +# module M +# def self.included(base) +# base.send(:extend, ClassMethods) +# base.send(:include, InstanceMethods) +# scope :foo, :conditions => {:created_at => nil} +# end +# +# module ClassMethods +# def cm; puts 'I am class method'; end +# end +# +# module InstanceMethods +# def im; puts 'I am instance method'; end +# end +# end +# +# By using ActiveSupport::Concern above module could be written as: +# +# module M +# extend ActiveSupport::Concern +# +# included do +# scope :foo, :conditions => {:created_at => nil} +# end +# +# module ClassMethods +# def cm; puts 'I am class method'; end +# end +# +# module InstanceMethods +# def im; puts 'I am instance method'; end +# end +# end module ActiveSupport module Concern def self.extended(base) -- cgit v1.2.3 From 30abb01d073a5cadf1e7f38e08ba20dfe468bda0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 4 Aug 2010 14:11:09 -0700 Subject: fisting indentation --- activerecord/lib/active_record/relation/batches.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 412be895c4..ecdf7c54b1 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -50,9 +50,9 @@ module ActiveRecord def find_in_batches(options = {}) relation = self - if orders.present? || taken.present? - ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") - end + if orders.present? || taken.present? + ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") + end if (finder_options = options.except(:start, :batch_size)).present? raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present? -- cgit v1.2.3 From ec736dff7b0a05d58d4c8780863afc47e2bb74a3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 4 Aug 2010 14:22:20 -0700 Subject: call to_a since we are not passing anything to all() --- activerecord/lib/active_record/relation/batches.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index ecdf7c54b1..d7494ebb5a 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -73,7 +73,7 @@ module ActiveRecord break if records.size < batch_size if primary_key_offset = records.last.id - records = relation.where(primary_key.gt(primary_key_offset)).all + records = relation.where(primary_key.gt(primary_key_offset)).to_a else raise "Primary key not included in the custom select clause" end -- cgit v1.2.3 From d191db76e04f065e1b0cff3766c818f9b8e2f43a Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 5 Aug 2010 01:09:09 +0200 Subject: standarizes the use of the article "an" for "SQL" and "SQLite" --- activerecord/lib/active_record/associations/has_many_association.rb | 2 +- .../lib/active_record/connection_adapters/abstract/database_limits.rb | 2 +- .../active_record/connection_adapters/abstract/schema_definitions.rb | 2 +- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- activerecord/lib/active_record/counter_cache.rb | 2 +- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/railties/databases.rake | 2 +- railties/guides/source/api_documentation_guidelines.textile | 2 +- railties/guides/source/getting_started.textile | 4 ++-- .../rails/generators/rails/app/templates/config/databases/oracle.yml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index d74fb7c702..c33bc6aa47 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -24,7 +24,7 @@ module ActiveRecord # If the association has a counter cache it gets that value. Otherwise # it will attempt to do a count via SQL, bounded to :limit if # there's one. Some configuration options like :group make it impossible - # to do a SQL count, in those cases the array count will be used. + # to do an SQL count, in those cases the array count will be used. # # That does not depend on whether the collection has already been loaded # or not. The +size+ method is the one that takes the loaded flag into diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 4118ea7b31..a130c330dd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -42,7 +42,7 @@ module ActiveRecord 65535 end - # the maximum length of a SQL query + # the maximum length of an SQL query def sql_query_length 1048575 end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 9fc0e6d403..9118ceb33c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -537,7 +537,7 @@ module ActiveRecord end end - # Represents a SQL table in an abstract way for updating a table. + # Represents an SQL table in an abstract way for updating a table. # Also see TableDefinition and SchemaStatements#create_table # # Available transformations are: diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index cc7c07dc35..ba0051de05 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -278,7 +278,7 @@ module ActiveRecord rows end - # Executes a SQL query and returns a MySQL::Result object. Note that you have to free + # Executes an SQL query and returns a MySQL::Result object. Note that you have to free # the Result object after you're done using it. def execute(sql, name = nil) #:nodoc: if name == :skip_logging diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index b0e0b45e16..237cd56683 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -1,7 +1,7 @@ module ActiveRecord # = Active Record Counter Cache module CounterCache - # Resets one or more counter caches to their correct value using a SQL + # Resets one or more counter caches to their correct value using an SQL # count query. This is useful when adding new counter caches, or if the # counter has been corrupted or modified directly by SQL. # diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 8f44f03d56..71b46beaef 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -60,7 +60,7 @@ module ActiveRecord # reflect that no changes should be made (since they can't be # persisted). Returns the frozen instance. # - # The row is simply removed with a SQL +DELETE+ statement on the + # The row is simply removed with an SQL +DELETE+ statement on the # record's primary key, and no callbacks are executed. # # To enforce the object's +before_destroy+ and +after_destroy+ diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 2c17c74ab4..ae605d3e7a 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -339,7 +339,7 @@ namespace :db do end namespace :structure do - desc "Dump the database structure to a SQL file" + desc "Dump the database structure to an SQL file" task :dump => :environment do abcs = ActiveRecord::Base.configurations case abcs[Rails.env]["adapter"] diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index d9a0d39d9d..9f201de49b 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -29,7 +29,7 @@ Documentation has to be concise but comprehensive. Explore and document edge cas The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be fenomenal :). -Spell names correctly: HTML, MySQL, JavaScript, ERb. +Spell names correctly: HTML, MySQL, JavaScript, ERb. Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database". h3. Example Code diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 12f2bb146b..ffb0310816 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -213,9 +213,9 @@ If you open this file in a new Rails application, you'll see a default database * The +test+ environment is used to run automated tests * The +production+ environment is used when you deploy your application for the world to use. -h5. Configuring a SQLite3 Database +h5. Configuring an SQLite3 Database -Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using a SQLite database when creating a new project, but you can always change it later. +Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using an SQLite database when creating a new project, but you can always change it later. Here's the section of the default configuration file (config/database.yml) with connection information for the development environment: diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml index f99ee937f3..fddf8b8144 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml @@ -4,7 +4,7 @@ # http://rubyforge.org/projects/ruby-oci8/ # # Specify your database using any valid connection syntax, such as a -# tnsnames.ora service name, or a SQL connect url string of the form: +# tnsnames.ora service name, or an SQL connect string of the form: # # //host:[port][/service name] # -- cgit v1.2.3 From 117b096d0a91ff7a8fb704b359a6fdf1ccc20e7c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 4 Aug 2010 16:22:16 -0700 Subject: avoid passing lists of lists to the group clause --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5ef00206dd..e71f1cca72 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -31,7 +31,7 @@ module ActiveRecord end def group(*args) - clone.tap {|r| r.group_values += args if args.present? } + clone.tap {|r| r.group_values += args.flatten if args.present? } end def order(*args) -- cgit v1.2.3 From f544c0a32dd86d4fe2c11e9111d3403fbbab2776 Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Thu, 5 Aug 2010 09:02:30 +0100 Subject: Fix ActiveSupport::Callbacks' define_callbacks and ActiveSupport::Concern documentation to look like native English --- activesupport/lib/active_support/callbacks.rb | 6 +++--- activesupport/lib/active_support/concern.rb | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index adabdd3388..4950d009d1 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -568,9 +568,9 @@ module ActiveSupport # # would trigger Audit#before_save instead. That's constructed by calling # "#{kind}_#{name}" on the given instance. In this case "kind" is "before" and - # "name" is "save". In this context treat ":kind" and ":name" as special thing where - # ":kind" refers to "callback type(before/after)" and ":name" refers to the method on - # which callbacks are being defined. + # "name" is "save". In this context ":kind" and ":name" have special meanings: ":kind" + # refers to the kind of callback (before/after/around) and ":name" refers to the + # method on which callbacks are being defined. # # A declaration like # diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 408d327dd7..2d87e8d0e5 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -4,33 +4,33 @@ # def self.included(base) # base.send(:extend, ClassMethods) # base.send(:include, InstanceMethods) -# scope :foo, :conditions => {:created_at => nil} +# scope :foo, :conditions => { :created_at => nil } # end # # module ClassMethods -# def cm; puts 'I am class method'; end +# def cm; puts 'I am a class method'; end # end # # module InstanceMethods -# def im; puts 'I am instance method'; end +# def im; puts 'I am an instance method'; end # end # end # -# By using ActiveSupport::Concern above module could be written as: +# By using ActiveSupport::Concern the above module could instead be written as: # # module M # extend ActiveSupport::Concern # -# included do -# scope :foo, :conditions => {:created_at => nil} +# included do +# scope :foo, :conditions => { :created_at => nil } # end # # module ClassMethods -# def cm; puts 'I am class method'; end +# def cm; puts 'I am a class method'; end # end # # module InstanceMethods -# def im; puts 'I am instance method'; end +# def im; puts 'I am an instance method'; end # end # end module ActiveSupport -- cgit v1.2.3 From 02572399a588110709c2988fab66e2d65d735bfc Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 5 Aug 2010 13:51:45 +0200 Subject: AS guide: documents DateTime#advance --- .../source/active_support_core_extensions.textile | 37 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 136fcde82a..d7d14236f6 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -2979,11 +2979,11 @@ Note in the previous example that increments may be negative. To perform the computation the method first increments years, then months, then weeks, and finally days. This order is important towards the end of months. Say for example we are at the end of February of 2010, and we want to move one month and one day forward. -The method +advance+ advances first one month, and the one day, the result is: +The method +advance+ advances first one month, and then one day, the result is: -Date.new(2010, 2, 28).advance(:months => 1, :day => 1) -# => Sun, 28 Mar 2010 +Date.new(2010, 2, 28).advance(:months => 1, :days => 1) +# => Sun, 29 Mar 2010 While if it did it the other way around the result would be different: @@ -3132,6 +3132,37 @@ now.utc? # => false now.utc.utc? # => true +h6(#datetime-advance). +advance+ + +The most generic way to jump to another datetime is +advance+. This method receives a hash with keys +:years+, +:months+, +:weeks+, +:days+, +:hours+, +:minutes+, and +:seconds+, and returns a datetime advanced as much as the present keys indicate. + + +d = DateTime.current +# => Thu, 05 Aug 2010 11:33:31 +0000 +d.advance(:years => 1, :months => 1, :days => 1, :hours => 1, :minutes => 1, :seconds => 1) +# => Tue, 06 Sep 2011 12:34:32 +0000 + + +This method first computes the destination date passing +:years+, +:months+, +:weeks+, and +:days+ to +Date#advance+ documented above. After that, it adjusts the time calling +since+ with the number of seconds to advance. This order is relevant, a different ordering would give different datetimes in some edge-cases. The example in +Date#advance+ applies, and we can extend it to show order relevance related to the time bits. + +If we first move the date bits (that have also a relative order of processing, as documented before), and then the time bits we get for example the following computation: + + +d = DateTime.new(2010, 2, 28, 23, 59, 59) +# => Sun, 28 Feb 2010 23:59:59 +0000 +d.advance(:months => 1, :seconds => 1) +# => Mon, 29 Mar 2010 00:00:00 +0000 + + +but if we computed them the other way around, the result would be different: + + +d.advance(:seconds => 1).advance(:months => 1) +# => Thu, 01 Apr 2010 00:00:00 +0000 + + +WARNING: Since +DateTime+ is not DST-aware you can end up in a non-existing point in time with no warning or error telling you so. + h5(#datetime-changing-components). Changing Components The method +change+ allows you to get a new datetime which is the same as the receiver except for the given options, which may include +:year+, +:month+, +:day+, +:hour+, +:min+, +:sec+, +:offset+, +:start+: -- cgit v1.2.3 From 8a657c0dc7e914111521eab1b0e69c20e57a8b3d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 5 Aug 2010 16:11:11 +0200 Subject: adds test coverage to ensure DateTime#advance processes first the date deltas, and then the time deltas --- activesupport/test/core_ext/date_time_ext_test.rb | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 19d7935211..e8506f5222 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -171,22 +171,29 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase end def test_advance - assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1) - assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4) - assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5) - assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7) - assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) + assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1) + assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4) + assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3) + assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5) + assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7) + assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) + assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year + assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5) + assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7) + assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9) + assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) + assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + end + def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas + # If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead. + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59).advance(:months => 1, :minutes => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23).advance(:months => 1, :hours => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(:months => 1, :hours => 1, :minutes => 1, :seconds => 1) end def test_next_week -- cgit v1.2.3 From 1ca18a6f858a257cf495e9dec7573374b1f3b01e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 5 Aug 2010 08:15:07 -0700 Subject: fixing whitespace errors --- activerecord/lib/active_record/schema.rb | 2 +- activerecord/lib/active_record/schema_dumper.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index e2783087ec..c1bc3214ea 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank' module ActiveRecord # = Active Record Schema - # + # # Allows programmers to programmatically define a schema in a portable # DSL. This means you can define tables, indexes, etc. without using SQL # directly, so your applications can more easily support multiple diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index a4757773d8..e9af20e1b6 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -8,13 +8,13 @@ module ActiveRecord # output format (i.e., ActiveRecord::Schema). class SchemaDumper #:nodoc: private_class_method :new - + ## # :singleton-method: - # A list of tables which should not be dumped to the schema. + # A list of tables which should not be dumped to the schema. # Acceptable values are strings as well as regexp. # This setting is only used if ActiveRecord::Base.schema_format == :ruby - cattr_accessor :ignore_tables + cattr_accessor :ignore_tables @@ignore_tables = [] def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT) @@ -71,7 +71,7 @@ HEADER else raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.' end - end + end table(tbl, stream) end end @@ -87,7 +87,7 @@ HEADER elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end - + tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } if pk != 'id' @@ -105,7 +105,7 @@ HEADER next if column.name == pk spec = {} spec[:name] = column.name.inspect - + # AR has an optimisation which handles zero-scale decimals as integers. This # code ensures that the dumper still dumps the column as a decimal. spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) } @@ -148,7 +148,7 @@ HEADER tbl.puts " end" tbl.puts - + indexes(table, tbl) tbl.rewind @@ -158,7 +158,7 @@ HEADER stream.puts "# #{e.message}" stream.puts end - + stream end @@ -172,7 +172,7 @@ HEADER value.inspect end end - + def indexes(table, stream) if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| -- cgit v1.2.3 From cd87cf771acb90c43cd56e5f038fc345a69790f9 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 5 Aug 2010 11:54:46 -0400 Subject: correcting the documentation that default to false. it does not default to false. returing either false or nil will not halt the chain unless :terminator is explicitly mentioned --- activesupport/lib/active_support/callbacks.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 4950d009d1..e179287b24 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -522,14 +522,13 @@ module ActiveSupport # # This macro accepts the following options: # - # * :terminator - Indicates when a before filter is considered - # to be halted. + # * :terminator - Indicates when a before filter is considered to be halted. # # define_callbacks :validate, :terminator => "result == false" # - # In the example above, if any before validate callbacks returns +false+, - # other callbacks are not executed. Defaults to "false", meaning no value - # halts the chain. + # In the example above, if any before validate callback returns +false+, + # other callbacks are not executed. Note that in this case if the callback + # returns +nil+ then other callbacks are still executed. # # * :rescuable - By default, after filters are not executed if # the given block or a before filter raises an error. Set this option to -- cgit v1.2.3 From 5130b0cf45e5efa93f60d34ae8bae02f67a4fec2 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 5 Aug 2010 11:55:20 -0400 Subject: more documentation for class_inheritable_* --- .../core_ext/class/inheritable_attributes.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index 92d6dbadd4..a33c772482 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -5,6 +5,10 @@ require 'active_support/core_ext/array/extract_options' module ClassInheritableAttributes # :nodoc: end +# It is recommend to use class_attribute over methods defined in this file. Please +# refer to documentation for class_attribute for more infor. Officially it is not +# deprected but class_attribute is faster. +# # Allows attributes to be shared within an inheritance hierarchy. Each descendant gets a copy of # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements # to, for example, an array without those additions being shared with either their parent, siblings, or @@ -12,6 +16,24 @@ end # # The copies of inheritable parent attributes are added to subclasses when they are created, via the # +inherited+ hook. +# +# class Person +# class_inheritable_accessor :hair_colors +# end +# +# Person.hair_colors = [:brown, :black, :blonde, :red] +# Person.hair_colors #=> [:brown, :black, :blonde, :red] +# Person.new.hair_colors #=> [:brown, :black, :blonde, :red] +# +# To opt out of the instance writer method, pass :instance_writer => false. +# To opt out of the instance reader method, pass :instance_reader => false. +# +# class Person +# cattr_accessor :hair_colors :instance_writer => false, :instance_reader => false +# end +# +# Person.new.hair_colors = [:brown] # => NoMethodError +# Person.new.hair_colors # => NoMethodError class Class # :nodoc: def class_inheritable_reader(*syms) options = syms.extract_options! -- cgit v1.2.3 From 86842fd1ce2dd4dac75f1a05ece646eab80c9347 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 5 Aug 2010 16:35:40 -0400 Subject: fixing typo --- .../lib/active_support/core_ext/class/inheritable_attributes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index a33c772482..d7a30cf123 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -6,7 +6,7 @@ module ClassInheritableAttributes # :nodoc: end # It is recommend to use class_attribute over methods defined in this file. Please -# refer to documentation for class_attribute for more infor. Officially it is not +# refer to documentation for class_attribute for more information. Officially it is not # deprected but class_attribute is faster. # # Allows attributes to be shared within an inheritance hierarchy. Each descendant gets a copy of -- cgit v1.2.3 From 232218f46533a4f2512d522dc7c730eaf4cedd82 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 5 Aug 2010 22:46:04 +0200 Subject: Revert "correcting the documentation that default to false. it does not default to false. returing either false or nil will not halt the chain unless :terminator is explicitly mentioned" This reverts commit cd87cf771acb90c43cd56e5f038fc345a69790f9. Reason: it does default to "false". --- activesupport/lib/active_support/callbacks.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e179287b24..4950d009d1 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -522,13 +522,14 @@ module ActiveSupport # # This macro accepts the following options: # - # * :terminator - Indicates when a before filter is considered to be halted. + # * :terminator - Indicates when a before filter is considered + # to be halted. # # define_callbacks :validate, :terminator => "result == false" # - # In the example above, if any before validate callback returns +false+, - # other callbacks are not executed. Note that in this case if the callback - # returns +nil+ then other callbacks are still executed. + # In the example above, if any before validate callbacks returns +false+, + # other callbacks are not executed. Defaults to "false", meaning no value + # halts the chain. # # * :rescuable - By default, after filters are not executed if # the given block or a before filter raises an error. Set this option to -- cgit v1.2.3 From 02e711bf1c2afe13a84ded3167bb633dcb4f8cb7 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 5 Aug 2010 22:54:43 +0200 Subject: documents that :terminator is a string to be eval'ed, and that it sees the result variable --- activesupport/lib/active_support/callbacks.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 4950d009d1..5e0c5b6e98 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -523,7 +523,8 @@ module ActiveSupport # This macro accepts the following options: # # * :terminator - Indicates when a before filter is considered - # to be halted. + # to halted. This is a string to be eval'ed and has the result of the + # very filter available in the result variable: # # define_callbacks :validate, :terminator => "result == false" # -- cgit v1.2.3 From d0ac56b5b4194bdf36bbd3b49ad64649b0675c66 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 5 Aug 2010 16:57:20 -0400 Subject: adding an example of skipping a callback --- activesupport/lib/active_support/callbacks.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 7290c971f9..24e407c253 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -486,7 +486,11 @@ module ActiveSupport end end - # Skip a previously defined callback for a given type. + # Skip a previously defined callback. + # + # class Writer < Person + # skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 } + # end # def skip_callback(name, *filter_list, &block) __update_callbacks(name, filter_list, block) do |chain, type, filters, options| -- cgit v1.2.3 From cdbc880055fba8bfa63b2c3c5fda94c48f87b037 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 6 Aug 2010 01:50:54 -0400 Subject: adding documentation for OrderedHash and OrderedOptions --- activesupport/lib/active_support/ordered_hash.rb | 9 ++++++++- activesupport/lib/active_support/ordered_options.rb | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 6b563b9063..e19d9d7dc1 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -4,7 +4,14 @@ YAML.add_builtin_type("omap") do |type, val| ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)] end -# OrderedHash is namespaced to prevent conflicts with other implementations +# Hash is not ordered in ruby 1.8.x. What it means is there is no guarantee of order of keys when +# method Hash#keys in invoked. Similarly Hash#values and Hash#each can't guarantee that each time +# the output will contain exactly same value in the same order. OrderedHash solves that +# problem. +# +# ActiveSupport::OrderedHash[:boy, 'John', :girl, 'Mary'] +# +# OrderedHash is namespaced to prevent conflicts with other implementations. module ActiveSupport class OrderedHash < ::Hash #:nodoc: def to_yaml_type diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 61ccb79211..b0584072c1 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,5 +1,21 @@ require 'active_support/ordered_hash' +# Usually key value pairs are handled something like this: +# +# h = ActiveSupport::OrderedOptions.new +# h[:boy] = 'John' +# h[:girl] = 'Mary' +# h[:boy] #=> 'John' +# h[:girl] #=> 'Mary' +# +# Using OrderedOptions above code could be reduced to: +# +# h = ActiveSupport::OrderedOptions.new +# h.boy = 'John' +# h.girl = 'Mary' +# h.boy #=> 'John' +# h.girl #=> 'Mary' +# module ActiveSupport #:nodoc: class OrderedOptions < OrderedHash def []=(key, value) -- cgit v1.2.3 From e0a0638094192061b5537f776a6a250b85cb09b8 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 6 Aug 2010 13:11:44 +0200 Subject: commit review: say clearly that AS::OrderedHash is about insertion order, be more neutral in wording, do not imply lack of ordering is a problem --- activesupport/lib/active_support/ordered_hash.rb | 18 ++++++++++-------- activesupport/lib/active_support/ordered_options.rb | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index e19d9d7dc1..2e8d538d0b 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -4,15 +4,17 @@ YAML.add_builtin_type("omap") do |type, val| ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)] end -# Hash is not ordered in ruby 1.8.x. What it means is there is no guarantee of order of keys when -# method Hash#keys in invoked. Similarly Hash#values and Hash#each can't guarantee that each time -# the output will contain exactly same value in the same order. OrderedHash solves that -# problem. -# -# ActiveSupport::OrderedHash[:boy, 'John', :girl, 'Mary'] -# -# OrderedHash is namespaced to prevent conflicts with other implementations. module ActiveSupport + # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the + # order in which +keys+ will return keys, or +each+ yield pairs. ActiveSupport::OrderedHash + # implements a hash that preserves insertion order, as in Ruby 1.9: + # + # oh = ActiveSupport::OrderedHash.new + # oh[:a] = 1 + # oh[:b] = 2 + # oh.keys # => [:a, :b], this order is guaranteed + # + # ActiveSupport::OrderedHash is namespaced to prevent conflicts with other implementations. class OrderedHash < ::Hash #:nodoc: def to_yaml_type "!tag:yaml.org,2002:omap" diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index b0584072c1..7fc2b45b51 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -5,16 +5,16 @@ require 'active_support/ordered_hash' # h = ActiveSupport::OrderedOptions.new # h[:boy] = 'John' # h[:girl] = 'Mary' -# h[:boy] #=> 'John' -# h[:girl] #=> 'Mary' +# h[:boy] # => 'John' +# h[:girl] # => 'Mary' # # Using OrderedOptions above code could be reduced to: # # h = ActiveSupport::OrderedOptions.new # h.boy = 'John' # h.girl = 'Mary' -# h.boy #=> 'John' -# h.girl #=> 'Mary' +# h.boy # => 'John' +# h.girl # => 'Mary' # module ActiveSupport #:nodoc: class OrderedOptions < OrderedHash -- cgit v1.2.3 From 23abf46112289d379ad539528ab739f74b1e23aa Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 6 Aug 2010 13:33:02 +0200 Subject: AR guide: fixes a query --- railties/guides/source/active_record_querying.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 5c4ed3a803..53095a2bd3 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -764,7 +764,7 @@ This loads all the posts and the associated category and comments for each post. h5. Nested Associations Hash -Category.find(1).includes(:posts => [{:comments => :guest}, :tags]) +Category.includes(:posts => [{:comments => :guest}, :tags]).find(1) This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association. -- cgit v1.2.3 From 3e678240f402fbf68cd25cc5daf1856ccb55f7cd Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 6 Aug 2010 13:56:38 +0200 Subject: edit pass to AMo::Serializers::JSON --- activemodel/lib/active_model/serializers/json.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 500b2399a3..e1dbc522de 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -19,8 +19,8 @@ module ActiveModel # passed through +options+. # # The option ActiveModel::Base.include_root_in_json controls the - # top-level behavior of to_json. It is true by default. When it is true, - # to_json will emit a single root node named after the object's type. For example: + # top-level behavior of +to_json+. If true (the default) +to_json+ will + # emit a single root node named after the object's type. For example: # # konata = User.find(1) # konata.to_json @@ -32,11 +32,11 @@ module ActiveModel # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} # - # The remainder of the examples in this section assume include_root_in_json is set to - # false. + # The remainder of the examples in this section assume +include_root_in_json+ + # is false. # - # Without any +options+, the returned JSON string will include all - # the model's attributes. For example: + # Without any +options+, the returned JSON string will include all the model's + # attributes. For example: # # konata = User.find(1) # konata.to_json @@ -52,14 +52,14 @@ module ActiveModel # konata.to_json(:except => [ :id, :created_at, :age ]) # # => {"name": "Konata Izumi", "awesome": true} # - # To include any methods on the model, use :methods. + # To include the result of some method calls on the model use :methods: # # konata.to_json(:methods => :permalink) # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true, # "permalink": "1-konata-izumi"} # - # To include associations, use :include. + # To include associations use :include: # # konata.to_json(:include => :posts) # # => {"id": 1, "name": "Konata Izumi", "age": 16, @@ -67,7 +67,7 @@ module ActiveModel # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, # {"id": 2, author_id: 1, "title": "So I was thinking"}]} # - # 2nd level and higher order associations work as well: + # Second level and higher order associations work as well: # # konata.to_json(:include => { :posts => { # :include => { :comments => { -- cgit v1.2.3 From 62bb83d0a27e83b0800e67676cfa0d0c47453f8e Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 6 Aug 2010 17:29:52 +0200 Subject: AS guide: documents calculations with Time objects --- .../source/active_support_core_extensions.textile | 112 ++++++++++++++++++--- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index d7d14236f6..9d9053d856 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -3053,30 +3053,30 @@ h4(#date-conversions). Conversions h3. Extensions to +DateTime+ -NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+. - WARNING: +DateTime+ is not aware of DST rules and so some of these methods have edge cases when a DST change is going on. For example +seconds_since_midnight+ might not return the real amount in such a day. h4(#calculations-datetime). Calculations +NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+. + The class +DateTime+ is a subclass of +Date+ so by loading +active_support/core_ext/date/calculations.rb+ you inherit these methods and their aliases, except that they will always return datetimes: yesterday tomorrow -beginning_of_week -end_on_week +beginning_of_week (monday, at_beginning_of_week) +end_on_week (at_end_of_week) next_week months_ago months_since -beginning_of_month -end_of_month +beginning_of_month (at_beginning_of_month) +end_of_month (at_end_of_month) prev_month next_month -beginning_of_quarter -end_of_quarter -beginning_of_year -end_of_year +beginning_of_quarter (at_beginning_of_quarter) +end_of_quarter (at_end_of_quarter) +beginning_of_year (at_beginning_of_year) +end_of_year (at_end_of_year) years_ago years_since prev_year @@ -3086,10 +3086,10 @@ next_year The following methods are reimplemented so you do *not* need to load +active_support/core_ext/date/calculations.rb+ for these ones: -beginning_of_day +beginning_of_day (midnight, at_midnight, at_beginning_of_day) end_of_day ago -since +since (in) On the other hand, +advance+ and +change+ are also defined and support more options, they are documented below. @@ -3199,7 +3199,93 @@ h4(#datetime-conversions). Conversions h3. Extensions to +Time+ -... +h4(#time-calculations). Calculations + +NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+. + +Active Support adds to +Time+ many of the methods available for +DateTime+: + + +past? +today? +future? +yesterday +tomorrow +seconds_since_midnight +change +advance +ago +since (in) +beginning_of_day (midnight, at_midnight, at_beginning_of_day) +end_of_day +beginning_of_week (monday, at_beginning_of_week) +end_on_week (at_end_of_week) +next_week +months_ago +months_since +beginning_of_month (at_beginning_of_month) +end_of_month (at_end_of_month) +prev_month +next_month +beginning_of_quarter (at_beginning_of_quarter) +end_of_quarter (at_end_of_quarter) +beginning_of_year (at_beginning_of_year) +end_of_year (at_end_of_year) +years_ago +years_since +prev_year +next_year + + +They are analogous. Please refer to their documentation above and take into account the following differences: + +* +change+ accepts an additional +:usec+ option. +* +Time+ understands DST, so you get correct DST calculations as in + + +# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST. +t = Time.local_time(2010, 3, 28, 1, 59, 59) +# => Sun Mar 28 01:59:59 +0100 2010 +t.advance(:seconds => 1) +# => Sun Mar 28 03:00:00 +0200 2010 + + +* If +since+ or +ago+ jump to a time that can't be expressed with +Time+ a +DateTime+ object is returned instead. + +h4. Time Constructors + +Active Support defines +Time.current+ to be +Time.zone.now+ if there's a user time zone defined, with fallback to +Time.now+: + + +Time.zone_default +# => # +Time.current +# => Fri, 06 Aug 2010 17:11:58 CEST +02:00 + + +Analogously to +DateTime+, the predicates +past?+, and +future?+ are relative to +Time.current+. + +Use the +local_time+ class method to create time objects honoring the user time zone: + + +Time.zone_default +# => # +Time.local_time(2010, 8, 15) +# => Sun Aug 15 00:00:00 +0200 2010 + + +The +utc_time+ class method returns a time in UTC: + + +Time.zone_default +# => # +Time.utc_time(2010, 8, 15) +# => Sun Aug 15 00:00:00 UTC 2010 + + +Both +local_time+ and +utc_time+ accept up to seven positional arguments: year, month, day, hour, min, sec, usec. Year is mandatory, month and day default to 1, and the rest default to 0. + +If the time to be constructed lies beyond the range supported by +Time+ in the runtime platform, usecs are discarded and a +DateTime+ object is returned instead. h3. Extensions to +Process+ -- cgit v1.2.3 From 12b3eca420c2de46d34a363c91b2263d366f4d6c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 6 Aug 2010 11:31:05 -0700 Subject: do not rely on arel class structure --- activerecord/lib/active_record/relation.rb | 4 +++- activerecord/test/cases/method_scoping_test.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index deacced627..30be723291 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -319,7 +319,9 @@ module ActiveRecord def scope_for_create @scope_for_create ||= begin @create_with_value || Hash[ - @where_values.grep(Arel::Predicates::Equality).map { |where| + @where_values.find_all { |w| + w.respond_to?(:operator) && w.operator == :== + }.map { |where| [where.operand1.name, where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2] diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 774b50e2e4..5256ab8d11 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -208,6 +208,13 @@ class MethodScopingTest < ActiveRecord::TestCase end end + def test_scope_for_create_only_uses_equal + table = VerySpecialComment.arel_table + relation = VerySpecialComment.scoped + relation.where_values << table[:id].not_eq(1) + assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create)) + end + def test_scoped_create new_comment = nil -- cgit v1.2.3 From 74dde5951af9f7cbea080087c49f2654e619a0fd Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 6 Aug 2010 15:24:38 -0400 Subject: updating documentation for method configure_dependency_for_has_many --- activerecord/lib/active_record/associations.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2556d243f6..73c0900c8b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1593,15 +1593,12 @@ module ActiveRecord # Creates before_destroy callback methods that nullify, delete or destroy # has_many associated objects, according to the defined :dependent rule. - # If the association is marked as :dependent => :restrict, create a callback - # that prevents deleting entirely. # - # See HasManyAssociation#delete_records. Dependent associations - # delete children, otherwise foreign key is set to NULL. - # See HasManyAssociation#delete_records. Dependent associations - # delete children if the option is set to :destroy or :delete_all, set the - # foreign key to NULL if the option is set to :nullify, and do not touch the - # child records if the option is set to :restrict. + # See HasManyAssociation#delete_records for more information. In general + # - delete children if the option is set to :destroy or :delete_all + # - set the foreign key to NULL if the option is set to :nullify + # - do not delete the parent record if there is any child record if the + # option is set to :restrict # # The +extra_conditions+ parameter, which is not used within the main # Active Record codebase, is meant to allow plugins to define extra -- cgit v1.2.3 From fc01adee79cd38069d89c2e457be99911e2a0ea3 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Fri, 6 Aug 2010 15:51:52 -0400 Subject: correcting wrong example --- .../lib/active_support/core_ext/class/inheritable_attributes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index d7a30cf123..e844cf50d1 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -29,7 +29,7 @@ end # To opt out of the instance reader method, pass :instance_reader => false. # # class Person -# cattr_accessor :hair_colors :instance_writer => false, :instance_reader => false +# class_inheritable_accessor :hair_colors :instance_writer => false, :instance_reader => false # end # # Person.new.hair_colors = [:brown] # => NoMethodError -- cgit v1.2.3 From 672c796ac4fe857fde817e7603f74790d4aca0b8 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 6 Aug 2010 22:06:13 +0200 Subject: fix reference to time/calculations.rb in AS guide --- railties/guides/source/active_support_core_extensions.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 9d9053d856..9a449debae 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -3201,7 +3201,7 @@ h3. Extensions to +Time+ h4(#time-calculations). Calculations -NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+. +NOTE: All the following methods are defined in +active_support/core_ext/time/calculations.rb+. Active Support adds to +Time+ many of the methods available for +DateTime+: -- cgit v1.2.3 From d082a9a2b803c2cf530c6b6bdb8a5c4f19f36982 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 6 Aug 2010 14:52:33 -0700 Subject: sorry AR, my privates are none of your business --- activerecord/test/cases/relations_test.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index c9313fe7b6..02c8c60873 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -192,13 +192,6 @@ class RelationTest < ActiveRecord::TestCase end end - def test_respond_to_private_arel_methods - relation = Topic.scoped - - assert ! relation.respond_to?(:matching_attributes) - assert relation.respond_to?(:matching_attributes, true) - end - def test_respond_to_dynamic_finders relation = Topic.scoped -- cgit v1.2.3 From e1596be32363122f12777ce09e654ae58f262eb4 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 6 Aug 2010 15:23:11 -0700 Subject: test to ensure that respond_to? delegates to arel --- activerecord/test/cases/relations_test.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 02c8c60873..ac7b501bb7 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -192,6 +192,25 @@ class RelationTest < ActiveRecord::TestCase end end + def test_respond_to_delegates_to_relation + relation = Topic.scoped + fake_arel = Struct.new(:responds) { + def respond_to? method, access = false + responds << [method, access] + end + }.new [] + + relation.extend(Module.new { attr_accessor :arel }) + relation.arel = fake_arel + + relation.respond_to?(:matching_attributes) + assert_equal [:matching_attributes, false], fake_arel.responds.first + + fake_arel.responds = [] + relation.respond_to?(:matching_attributes, true) + assert_equal [:matching_attributes, true], fake_arel.responds.first + end + def test_respond_to_dynamic_finders relation = Topic.scoped -- cgit v1.2.3 From 334452098e593bb514a9ea2bd74435f2c7b0628e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 6 Aug 2010 16:38:53 -0700 Subject: reduce the number of times current_connection_id is called in with_connection() --- .../connection_adapters/abstract/connection_pool.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 9d0251dda3..02a8f4e214 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -103,8 +103,8 @@ module ActiveRecord # Signal that the thread is finished with the current connection. # #release_connection releases the connection-thread association # and returns the connection to the pool. - def release_connection - conn = @reserved_connections.delete(current_connection_id) + def release_connection(with_id = current_connection_id) + conn = @reserved_connections.delete(with_id) checkin conn if conn end @@ -112,10 +112,11 @@ module ActiveRecord # exists checkout a connection, yield it to the block, and checkin the # connection when finished. def with_connection - fresh_connection = true unless @reserved_connections[current_connection_id] + connection_id = current_connection_id + fresh_connection = true unless @reserved_connections[connection_id] yield connection ensure - release_connection if fresh_connection + release_connection(connection_id) if fresh_connection end # Returns true if a connection has already been opened. -- cgit v1.2.3 From aed698a7c8bdafe3a7596a15d320fc5b1175600f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 7 Aug 2010 01:42:09 +0200 Subject: adds Abstract Controller to the API --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 4e56da5ab3..0e6acb5ac2 100644 --- a/Rakefile +++ b/Rakefile @@ -90,6 +90,7 @@ RDoc::Task.new do |rdoc| rdoc.rdoc_files.include('actionpack/README.rdoc') rdoc.rdoc_files.include('actionpack/CHANGELOG') + rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb') -- cgit v1.2.3 From 0953c04cf57cfc281e6972df450ebe517cfe3a00 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 7 Aug 2010 15:18:22 +0200 Subject: quick hack: hijacks the predicate RDoc::Parser.binary? so that it does not consider a handful of ordinary Ruby files in the Rails tree as binary (and thus excluded from the API) --- Rakefile | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Rakefile b/Rakefile index 0e6acb5ac2..aab4f5f2f5 100644 --- a/Rakefile +++ b/Rakefile @@ -5,6 +5,31 @@ require 'rake' require 'rdoc/task' require 'rake/gempackagetask' +# RDoc skips some files in the Rails tree due to its binary? predicate. This is a quick +# hack for edge docs, until we decide which is the correct way to address this issue. +# If not fixed in RDoc itself, via an option or something, we should probably move this +# to railties and use it also in doc:rails. +def hijack_rdoc! + require "rdoc/parser" + class << RDoc::Parser + def binary?(file) + s = File.read(file, 1024) or return false + + if s[0, 2] == Marshal.dump('')[0, 2] then + true + elsif file =~ /\.erb\.rb$/ then # ORIGINAL is file =~ /erb\.rb$/ + false + elsif s.index("\x00") then # ORIGINAL is s.scan(/<%|%>/).length >= 4 || s.index("\x00") + true + elsif 0.respond_to? :fdiv then + s.count("^ -~\t\r\n").fdiv(s.size) > 0.3 + else # HACK 1.8.6 + (s.count("^ -~\t\r\n").to_f / s.size) > 0.3 + end + end + end +end + PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties) desc 'Run all tests by default' @@ -63,6 +88,8 @@ end desc "Generate documentation for the Rails framework" RDoc::Task.new do |rdoc| + hijack_rdoc! + rdoc.rdoc_dir = 'doc/rdoc' rdoc.title = "Ruby on Rails Documentation" -- cgit v1.2.3 From 997021eb29869953936434dbaf6fb79fd34f5015 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 7 Aug 2010 20:10:01 +0200 Subject: undoes one of the modifications to RDoc::Parser.binary? --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index aab4f5f2f5..ceb0e832b3 100644 --- a/Rakefile +++ b/Rakefile @@ -17,7 +17,7 @@ def hijack_rdoc! if s[0, 2] == Marshal.dump('')[0, 2] then true - elsif file =~ /\.erb\.rb$/ then # ORIGINAL is file =~ /erb\.rb$/ + elsif file =~ /erb\.rb$/ then false elsif s.index("\x00") then # ORIGINAL is s.scan(/<%|%>/).length >= 4 || s.index("\x00") true -- cgit v1.2.3 From 5598d3ac2ee8d1d5da79a73c91bcf93c9270a124 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 8 Aug 2010 11:28:50 +0200 Subject: updates horo dependency to 1.0.1 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c8dbcc0507..71dc1f3e99 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ gem "rails", :path => File.dirname(__FILE__) gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" gem "rdoc", ">= 2.5.9" -gem "horo" +gem "horo", ">= 1.0.1" # AS gem "memcache-client", ">= 1.8.5" -- cgit v1.2.3 From ddeaf6c8877599b18b371232e72ed7150f5bb688 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 8 Aug 2010 18:29:58 +0200 Subject: routing guide: documents the CONTROLLER environment variable understood by the routes task --- railties/guides/source/routing.textile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 7b665d81e7..625941ba31 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -762,6 +762,12 @@ formatted_users GET /users.:format {:controller=>"users", :action=>"index"} POST /users.:format {:controller=>"users", :action=>"create"}
+You may restrict the listing to the routes that map to a particular controller setting the +CONTROLLER+ environment variable: + + +$ CONTROLLER=users rake routes + + TIP: You'll find that the output from +rake routes+ is much more readable if you widen your terminal window until the output lines don't wrap. h4. Testing Routes -- cgit v1.2.3 From 678aeb7e48fef9cd3107f22fb4ae9d1ecec27a59 Mon Sep 17 00:00:00 2001 From: Daniel McNevin Date: Sun, 8 Aug 2010 19:57:42 -0400 Subject: updated the action_controller guide with the new session configuration options --- .../source/action_controller_overview.textile | 41 +++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 038ca903c1..ff112608ff 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -159,23 +159,38 @@ Read more about session storage in the "Security Guide":security.html. If you need a different session storage mechanism, you can change it in the +config/initializers/session_store.rb+ file: -# Use the database for sessions instead of the cookie-based default, -# which shouldn't be used to store highly confidential information -# (create the session table with "rake db:sessions:create") -# ActionController::Base.session_store = :active_record_store + # Use the database for sessions instead of the cookie-based default, + # which shouldn't be used to store highly confidential information + # (create the session table with "rake db:sessions:create") + # YourApp::Application.config.session_store :active_record_store -Rails sets up a session key (the name of the cookie) and (for the CookieStore) a secret key used when signing the session data. These can also be changed in +config/initializers/session_store.rb+: +Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in +config/initializers/session_store.rb+: -# Your secret key for verifying cookie session data integrity. -# If you change this key, all old sessions will become invalid! -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.session = { - :key => '_yourappname_session', - :secret => '4f50711b8f0f49572...' -} + # Be sure to restart your server when you modify this file. + + YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session' + + +You can also pass a +:domain+ key and specify the domain name for the cookie: + + + # Be sure to restart your server when you modify this file. + + YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session', :domain => ".test.com" + + +Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in +config/initializers/secret_token.rb+ + + + # Be sure to restart your server when you modify this file. + + # Your secret key for verifying the integrity of signed cookies. + # If you change this key, all old signed cookies will become invalid! + # Make sure the secret is at least 30 characters and all random, + # no regular words or you'll be exposed to dictionary attacks. + YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...' NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions. -- cgit v1.2.3 From 9a43640ed1de6f65f152df2b0ab61f5bc740d805 Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Mon, 9 Aug 2010 13:44:04 +1000 Subject: typo in AM --- activemodel/lib/active_model/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 817640b178..a43436e008 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -283,7 +283,7 @@ module ActiveModel @attribute_methods_generated = true end - # Removes all the preiously dynamically defined methods from the class + # Removes all the previously dynamically defined methods from the class def undefine_attribute_methods generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } -- cgit v1.2.3 From d87c57bf3e5718c6995ecc73cf8d7396e3ba4b19 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 9 Aug 2010 12:21:21 +0200 Subject: AC guide: commit review, block examples go at column 0, use .example.com as example domain --- .../source/action_controller_overview.textile | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index ff112608ff..ec2d5b2787 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -159,41 +159,41 @@ Read more about session storage in the "Security Guide":security.html. If you need a different session storage mechanism, you can change it in the +config/initializers/session_store.rb+ file: - # Use the database for sessions instead of the cookie-based default, - # which shouldn't be used to store highly confidential information - # (create the session table with "rake db:sessions:create") - # YourApp::Application.config.session_store :active_record_store +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rake db:sessions:create") +# YourApp::Application.config.session_store :active_record_store Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in +config/initializers/session_store.rb+: - # Be sure to restart your server when you modify this file. +# Be sure to restart your server when you modify this file. - YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session' +YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session' You can also pass a +:domain+ key and specify the domain name for the cookie: - # Be sure to restart your server when you modify this file. +# Be sure to restart your server when you modify this file. - YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session', :domain => ".test.com" +YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session', :domain => ".example.com" Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in +config/initializers/secret_token.rb+ - # Be sure to restart your server when you modify this file. +# Be sure to restart your server when you modify this file. - # Your secret key for verifying the integrity of signed cookies. - # If you change this key, all old signed cookies will become invalid! - # Make sure the secret is at least 30 characters and all random, - # no regular words or you'll be exposed to dictionary attacks. - YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...' +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...' -NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions. +NOTE: Changing the secret when using the +CookieStore+ will invalidate all existing sessions. h4. Accessing the Session -- cgit v1.2.3 From 4434e407e93001409605e5f02650b591a0cede32 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 9 Aug 2010 13:31:42 +0200 Subject: adds URL to the body generated by the redirect macro in the routes mapper as per the RFC, extracts common test pattern into a test macro, adds a test to cover the :status option --- actionpack/lib/action_dispatch/routing/mapper.rb | 5 ++- actionpack/test/dispatch/routing_test.rb | 55 +++++++++++------------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 526c97ff8e..c118c72440 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,3 +1,4 @@ +require 'erb' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/blank' @@ -277,7 +278,6 @@ module ActionDispatch path = args.shift || block path_proc = path.is_a?(Proc) ? path : proc { |params| path % params } status = options[:status] || 301 - body = 'Moved Permanently' lambda do |env| req = Request.new(env) @@ -290,11 +290,14 @@ module ActionDispatch uri.host ||= req.host uri.port ||= req.port unless req.port == 80 + body = %(You are being redirected.) + headers = { 'Location' => uri.to_s, 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s } + [ status, headers, [body] ] end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 4808663aa9..3f090b7254 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1,3 +1,4 @@ +require 'erb' require 'abstract_unit' require 'controller/fake_controllers' @@ -56,7 +57,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" } match 'account/proc_req' => redirect {|params, req| "/#{req.method}" } - match 'account/google' => redirect('http://www.google.com/') + match 'account/google' => redirect('http://www.google.com/', :status => 302) match 'openid/login', :via => [:get, :post], :to => "openid#login" @@ -501,9 +502,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_login_redirect with_test_routes do get '/account/login' - assert_equal 301, @response.status - assert_equal 'http://www.example.com/login', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com/login' end end @@ -511,18 +510,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do assert_equal '/account/logout', logout_redirect_path get '/account/logout' - assert_equal 301, @response.status - assert_equal 'http://www.example.com/logout', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com/logout' end end def test_namespace_redirect with_test_routes do get '/private' - assert_equal 301, @response.status - assert_equal 'http://www.example.com/private/index', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com/private/index' end end @@ -586,27 +581,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_redirect_modulo with_test_routes do get '/account/modulo/name' - assert_equal 301, @response.status - assert_equal 'http://www.example.com/names', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com/names' end end def test_redirect_proc with_test_routes do get '/account/proc/person' - assert_equal 301, @response.status - assert_equal 'http://www.example.com/people', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com/people' end end def test_redirect_proc_with_request with_test_routes do get '/account/proc_req' - assert_equal 301, @response.status - assert_equal 'http://www.example.com/GET', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com/GET' end end @@ -1203,12 +1192,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - def test_redirect_with_complete_url + def test_redirect_with_complete_url_and_status with_test_routes do get '/account/google' - assert_equal 301, @response.status - assert_equal 'http://www.google.com/', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.google.com/', 302 end end @@ -1216,9 +1203,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest previous_host, self.host = self.host, 'www.example.com:3000' with_test_routes do get '/account/login' - assert_equal 301, @response.status - assert_equal 'http://www.example.com:3000/login', @response.headers['Location'] - assert_equal 'Moved Permanently', @response.body + verify_redirect 'http://www.example.com:3000/login' end ensure self.host = previous_host @@ -1899,8 +1884,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - private - def with_test_routes - yield - end +private + def with_test_routes + yield + end + + def verify_redirect(url, status=301) + assert_equal status, @response.status + assert_equal url, @response.headers['Location'] + assert_equal expected_redirect_body(url), @response.body + end + + def expected_redirect_body(url) + %(You are being redirected.) + end end -- cgit v1.2.3 From 505546af709082f25444e323ee0f2141a86bbc63 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 9 Aug 2010 14:11:05 +0200 Subject: form helpers guide: fixes an example --- railties/guides/source/form_helpers.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 1f1b7d076e..146b75da3f 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -647,7 +647,7 @@ the +params+ hash will contain {'person' => {'name' => 'Henry'}} -and +params["name"]+ will retrieve the submitted value in the controller. +and +params[:person][:name]+ will retrieve the submitted value in the controller. Hashes can be nested as many levels as required, for example -- cgit v1.2.3 From 4f7565c4de1f877547a6b901a8416b18c613acc9 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 9 Aug 2010 15:14:00 +0200 Subject: adds missing requires for Object#try --- actionmailer/lib/action_mailer/deprecated_api.rb | 2 ++ actionpack/lib/action_dispatch/routing/deprecated_mapper.rb | 1 + actionpack/lib/action_dispatch/testing/integration.rb | 1 + actionpack/lib/action_view/helpers/sanitize_helper.rb | 1 + actionpack/test/controller/resources_test.rb | 1 + activerecord/lib/active_record/relation/calculations.rb | 1 + activeresource/lib/active_resource/http_mock.rb | 6 +++++- 7 files changed, 12 insertions(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/deprecated_api.rb b/actionmailer/lib/action_mailer/deprecated_api.rb index 0070d8e016..7d57feba04 100644 --- a/actionmailer/lib/action_mailer/deprecated_api.rb +++ b/actionmailer/lib/action_mailer/deprecated_api.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/try' + module ActionMailer # This is the API which is deprecated and is going to be removed on Rails 3.1 release. # Part of the old API will be deprecated after 3.1, for a smoother deprecation process. diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index 4904f0633d..e04062ce8b 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/with_options' +require 'active_support/core_ext/object/try' module ActionDispatch module Routing diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 8e58adaf59..b52795c575 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,6 +1,7 @@ require 'stringio' require 'uri' require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/core_ext/object/try' require 'rack/test' require 'test/unit/assertions' diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index d5638a69e5..d82005fa24 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/object/try' require 'action_controller/vendor/html-scanner' require 'action_view/helpers/tag_helper' diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index a9d1c55c05..6c8f470fba 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/object/try' require 'abstract_unit' class ResourcesController < ActionController::Base diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index f8412bc604..a679c444cf 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/object/try' module ActiveRecord module Calculations diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb index f192c53b4f..75425c01c0 100644 --- a/activeresource/lib/active_resource/http_mock.rb +++ b/activeresource/lib/active_resource/http_mock.rb @@ -123,7 +123,11 @@ module ActiveResource # def post(path, body, headers) # request = ActiveResource::Request.new(:post, path, body, headers) # self.class.requests << request - # self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for #{request}")) + # if response = self.class.responses.assoc(request) + # response[1] + # else + # raise InvalidRequestError.new("No response recorded for #{request}") + # end # end module_eval <<-EOE, __FILE__, __LINE__ + 1 def #{method}(path, #{'body, ' if has_body}headers) -- cgit v1.2.3 From 6767946374353f90ce05e68d38bcb93dcb8bae56 Mon Sep 17 00:00:00 2001 From: wycats Date: Mon, 9 Aug 2010 11:48:31 -0700 Subject: Improve best_standards_support to use only IE=Edge in development mode --- .../middleware/best_standards_support.rb | 13 ++- railties/lib/rails/application.rb | 2 +- .../config/environments/development.rb.tt | 4 + railties/test/application/routing_test.rb | 96 +++++++++++++++------- 4 files changed, 81 insertions(+), 34 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb index df8f7766bb..69adcc419f 100644 --- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb +++ b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb @@ -1,12 +1,21 @@ module ActionDispatch class BestStandardsSupport - def initialize(app) + def initialize(app, type = true) @app = app + + @header = case type + when true + "IE=Edge,chrome=1" + when :builtin + "IE=Edge" + when false + nil + end end def call(env) status, headers, body = @app.call(env) - headers["X-UA-Compatible"] = "IE=Edge,chrome=1" + headers["X-UA-Compatible"] = @header [status, headers, body] end end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 6622cfdd2f..5b26333486 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -205,7 +205,7 @@ module Rails middleware.use ::ActionDispatch::ParamsParser middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::Head - middleware.use ::ActionDispatch::BestStandardsSupport if config.action_dispatch.best_standards_support + middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support if config.action_dispatch.best_standards_support end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 99758dfcf7..7616614aff 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -19,4 +19,8 @@ # Print deprecation notices to the Rails logger config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin end + diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index a10a39ef40..febc53bac9 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -11,19 +11,19 @@ module ApplicationTests extend Rack::Test::Methods end - def app + def app(env = "production") + old_env = ENV["RAILS_ENV"] + @app ||= begin + ENV["RAILS_ENV"] = env require "#{app_path}/config/environment" Rails.application end + ensure + ENV["RAILS_ENV"] = old_env end - test "rails/info/properties" do - get "/rails/info/properties" - assert_equal 200, last_response.status - end - - test "simple controller" do + def simple_controller controller :foo, <<-RUBY class FooController < ApplicationController def index @@ -37,12 +37,42 @@ module ApplicationTests match ':controller(/:action)' end RUBY + end + + test "rails/info/properties in development" do + app("development") + get "/rails/info/properties" + assert_equal 200, last_response.status + end + + test "rails/info/properties in production" do + app("production") + get "/rails/info/properties" + assert_equal 404, last_response.status + end + + test "simple controller" do + simple_controller get '/foo' assert_equal 'foo', last_response.body + end + + test "simple controller in production mode returns best standards" do + simple_controller + + get '/foo' assert_equal "IE=Edge,chrome=1", last_response.headers["X-UA-Compatible"] end + test "simple controller in development mode leaves out Chrome" do + simple_controller + app("development") + + get "/foo" + assert_equal "IE=Edge", last_response.headers["X-UA-Compatible"] + end + test "simple controller with helper" do controller :foo, <<-RUBY class FooController < ApplicationController @@ -147,38 +177,42 @@ module ApplicationTests assert_equal 'admin::foo', last_response.body end - test "reloads routes when configuration is changed" do - controller :foo, <<-RUBY - class FooController < ApplicationController - def bar - render :text => "bar" + {"development" => "baz", "production" => "bar"}.each do |mode, expected| + test "reloads routes when configuration is changed in #{mode}" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def bar + render :text => "bar" + end + + def baz + render :text => "baz" + end end + RUBY - def baz - render :text => "baz" + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'foo', :to => 'foo#bar' end - end - RUBY + RUBY - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do |map| - match 'foo', :to => 'foo#bar' - end - RUBY + app(mode) - get '/foo' - assert_equal 'bar', last_response.body + get '/foo' + assert_equal 'bar', last_response.body - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do |map| - match 'foo', :to => 'foo#baz' - end - RUBY + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'foo', :to => 'foo#baz' + end + RUBY - sleep 0.1 + sleep 0.1 - get '/foo' - assert_equal 'baz', last_response.body + get '/foo' + assert_equal expected, last_response.body + end end test 'resource routing with irrigular inflection' do -- cgit v1.2.3 From 7171161124a2852ee7b40c5c632ba8e092c97409 Mon Sep 17 00:00:00 2001 From: wycats Date: Mon, 9 Aug 2010 12:06:25 -0700 Subject: rename _snowman to _e --- actionpack/lib/action_view/helpers/form_tag_helper.rb | 2 +- actionpack/test/template/form_helper_test.rb | 2 +- actionpack/test/template/form_tag_helper_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 9dac2d4538..1ea870426a 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -538,7 +538,7 @@ module ActionView def extra_tags_for_form(html_options) snowman_tag = tag(:input, :type => "hidden", - :name => "_snowman", :value => "☃".html_safe) + :name => "_e", :value => "☃".html_safe) method = html_options.delete("method").to_s diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 9086a23345..be66710ae5 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1513,7 +1513,7 @@ class FormHelperTest < ActionView::TestCase def snowman(method = nil) txt = %{
} - txt << %{} + txt << %{} txt << %{} if method txt << %{
} end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 8a0f352bc0..6c85952d40 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -12,7 +12,7 @@ class FormTagHelperTest < ActionView::TestCase method = options[:method] txt = %{
} - txt << %{} + txt << %{} txt << %{} if method txt << %{
} end -- cgit v1.2.3 From 94cff67bd1125fb98abe8b642ce2dc0ea26bb6dd Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 9 Aug 2010 12:50:09 -0700 Subject: ignore this test for mysql2 --- activerecord/test/cases/attribute_methods_test.rb | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index d20b762853..2c069cd8a5 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -106,21 +106,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_read_attributes_before_type_cast_on_datetime - developer = Developer.find(:first) - # Oracle adapter returns Time before type cast - unless current_adapter?(:OracleAdapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] - else - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + unless current_adapter?(:Mysql2Adapter) + def test_read_attributes_before_type_cast_on_datetime + developer = Developer.find(:first) + # Oracle adapter returns Time before type cast + unless current_adapter?(:OracleAdapter) + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] + else + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) - developer.created_at = "345643456" - assert_equal developer.created_at_before_type_cast, "345643456" - assert_equal developer.created_at, nil + developer.created_at = "345643456" + assert_equal developer.created_at_before_type_cast, "345643456" + assert_equal developer.created_at, nil - developer.created_at = "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") + developer.created_at = "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") + end end end -- cgit v1.2.3 From 188855501b454d6732fb1fd1d76cc26d6119f2eb Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Mon, 9 Aug 2010 14:52:00 -0700 Subject: move mysql2 adapter into core --- .../connection_adapters/mysql2_adapter.rb | 639 +++++++++++++++++++++ 1 file changed, 639 insertions(+) create mode 100644 activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb new file mode 100644 index 0000000000..568759775b --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -0,0 +1,639 @@ +# encoding: utf-8 + +require 'mysql2' unless defined? Mysql2 + +module ActiveRecord + class Base + def self.mysql2_connection(config) + config[:username] = 'root' if config[:username].nil? + client = Mysql2::Client.new(config.symbolize_keys) + options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0] + ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) + end + end + + module ConnectionAdapters + class Mysql2Column < Column + BOOL = "tinyint(1)" + def extract_default(default) + if sql_type =~ /blob/i || type == :text + if default.blank? + return null ? nil : '' + else + raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" + end + elsif missing_default_forged_as_empty_string?(default) + nil + else + super + end + end + + def has_default? + return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns + super + end + + # Returns the Ruby class that corresponds to the abstract data type. + def klass + case type + when :integer then Fixnum + when :float then Float + when :decimal then BigDecimal + when :datetime then Time + when :date then Date + when :timestamp then Time + when :time then Time + when :text, :string then String + when :binary then String + when :boolean then Object + end + end + + def type_cast(value) + return nil if value.nil? + case type + when :string then value + when :text then value + when :integer then value.to_i rescue value ? 1 : 0 + when :float then value.to_f # returns self if it's already a Float + when :decimal then self.class.value_to_decimal(value) + when :datetime, :timestamp then value.class == Time ? value : self.class.string_to_time(value) + when :time then value.class == Time ? value : self.class.string_to_dummy_time(value) + when :date then value.class == Date ? value : self.class.string_to_date(value) + when :binary then value + when :boolean then self.class.value_to_boolean(value) + else value + end + end + + def type_cast_code(var_name) + case type + when :string then nil + when :text then nil + when :integer then "#{var_name}.to_i rescue #{var_name} ? 1 : 0" + when :float then "#{var_name}.to_f" + when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})" + when :datetime, :timestamp then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_time(#{var_name})" + when :time then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_dummy_time(#{var_name})" + when :date then "#{var_name}.class == Date ? #{var_name} : #{self.class.name}.string_to_date(#{var_name})" + when :binary then nil + when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})" + else nil + end + end + + private + def simplified_type(field_type) + return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL) + return :string if field_type =~ /enum/i or field_type =~ /set/i + return :integer if field_type =~ /year/i + return :binary if field_type =~ /bit/i + super + end + + def extract_limit(sql_type) + case sql_type + when /blob|text/i + case sql_type + when /tiny/i + 255 + when /medium/i + 16777215 + when /long/i + 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases + else + super # we could return 65535 here, but we leave it undecorated by default + end + when /^bigint/i; 8 + when /^int/i; 4 + when /^mediumint/i; 3 + when /^smallint/i; 2 + when /^tinyint/i; 1 + else + super + end + end + + # MySQL misreports NOT NULL column default when none is given. + # We can't detect this for columns which may have a legitimate '' + # default (string) but we can for others (integer, datetime, boolean, + # and the rest). + # + # Test whether the column has default '', is not null, and is not + # a type allowing default ''. + def missing_default_forged_as_empty_string?(default) + type != :string && !null && default == '' + end + end + + class Mysql2Adapter < AbstractAdapter + cattr_accessor :emulate_booleans + self.emulate_booleans = true + + ADAPTER_NAME = 'Mysql2' + PRIMARY = "PRIMARY" + + LOST_CONNECTION_ERROR_MESSAGES = [ + "Server shutdown in progress", + "Broken pipe", + "Lost connection to MySQL server during query", + "MySQL server has gone away" ] + + QUOTED_TRUE, QUOTED_FALSE = '1', '0' + + NATIVE_DATABASE_TYPES = { + :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", + :string => { :name => "varchar", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "int", :limit => 4 }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "datetime" }, + :timestamp => { :name => "datetime" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "blob" }, + :boolean => { :name => "tinyint", :limit => 1 } + } + + def initialize(connection, logger, connection_options, config) + super(connection, logger) + @connection_options, @config = connection_options, config + @quoted_column_names, @quoted_table_names = {}, {} + configure_connection + end + + def adapter_name + ADAPTER_NAME + end + + def supports_migrations? + true + end + + def supports_primary_key? + true + end + + def supports_savepoints? + true + end + + def native_database_types + NATIVE_DATABASE_TYPES + end + + # QUOTING ================================================== + + def quote(value, column = nil) + if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) + s = column.class.string_to_binary(value).unpack("H*")[0] + "x'#{s}'" + elsif value.kind_of?(BigDecimal) + value.to_s("F") + else + super + end + end + + def quote_column_name(name) #:nodoc: + @quoted_column_names[name] ||= "`#{name}`" + end + + def quote_table_name(name) #:nodoc: + @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`') + end + + def quote_string(string) + @connection.escape(string) + end + + def quoted_true + QUOTED_TRUE + end + + def quoted_false + QUOTED_FALSE + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity(&block) #:nodoc: + old = select_value("SELECT @@FOREIGN_KEY_CHECKS") + + begin + update("SET FOREIGN_KEY_CHECKS = 0") + yield + ensure + update("SET FOREIGN_KEY_CHECKS = #{old}") + end + end + + # CONNECTION MANAGEMENT ==================================== + + def active? + return false unless @connection + @connection.query 'select 1' + true + rescue Mysql2::Error + false + end + + def reconnect! + disconnect! + connect + end + + # this is set to true in 2.3, but we don't want it to be + def requires_reloading? + false + end + + def disconnect! + unless @connection.nil? + @connection.close + @connection = nil + end + end + + def reset! + disconnect! + connect + end + + # DATABASE STATEMENTS ====================================== + + # FIXME: re-enable the following once a "better" query_cache solution is in core + # + # The overrides below perform much better than the originals in AbstractAdapter + # because we're able to take advantage of mysql2's lazy-loading capabilities + # + # # Returns a record hash with the column names as keys and column values + # # as values. + # def select_one(sql, name = nil) + # result = execute(sql, name) + # result.each(:as => :hash) do |r| + # return r + # end + # end + # + # # Returns a single value from a record + # def select_value(sql, name = nil) + # result = execute(sql, name) + # if first = result.first + # first.first + # end + # end + # + # # Returns an array of the values of the first column in a select: + # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] + # def select_values(sql, name = nil) + # execute(sql, name).map { |row| row.first } + # end + + # Returns an array of arrays containing the field values. + # Order is the same as that returned by +columns+. + def select_rows(sql, name = nil) + execute(sql, name).to_a + end + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + if name == :skip_logging + @connection.query(sql) + else + log(sql, name) { @connection.query(sql) } + end + rescue ActiveRecord::StatementInvalid => exception + if exception.message.split(":").first =~ /Packets out of order/ + 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." + else + raise + end + end + + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + super + id_value || @connection.last_id + end + alias :create :insert_sql + + def update_sql(sql, name = nil) + super + @connection.affected_rows + end + + def begin_db_transaction + execute "BEGIN" + rescue Exception + # Transactions aren't supported + end + + def commit_db_transaction + execute "COMMIT" + rescue Exception + # Transactions aren't supported + end + + def rollback_db_transaction + execute "ROLLBACK" + rescue Exception + # Transactions aren't supported + end + + def create_savepoint + execute("SAVEPOINT #{current_savepoint_name}") + end + + def rollback_to_savepoint + execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") + end + + def release_savepoint + execute("RELEASE SAVEPOINT #{current_savepoint_name}") + end + + def add_limit_offset!(sql, options) + limit, offset = options[:limit], options[:offset] + if limit && offset + sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}" + elsif limit + sql << " LIMIT #{sanitize_limit(limit)}" + elsif offset + sql << " OFFSET #{offset.to_i}" + end + sql + end + + # SCHEMA STATEMENTS ======================================== + + def structure_dump + if supports_views? + sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'" + else + sql = "SHOW TABLES" + end + + select_all(sql).inject("") do |structure, table| + table.delete('Table_type') + structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n" + end + end + + def recreate_database(name, options = {}) + drop_database(name) + create_database(name, options) + end + + # Create a new MySQL database with optional :charset and :collation. + # Charset defaults to utf8. + # + # Example: + # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin' + # create_database 'matt_development' + # create_database 'matt_development', :charset => :big5 + def create_database(name, options = {}) + if options[:collation] + execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" + else + execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" + end + end + + def drop_database(name) #:nodoc: + execute "DROP DATABASE IF EXISTS `#{name}`" + end + + def current_database + select_value 'SELECT DATABASE() as db' + end + + # Returns the database character set. + def charset + show_variable 'character_set_database' + end + + # Returns the database collation strategy. + def collation + show_variable 'collation_database' + end + + def tables(name = nil) + tables = [] + execute("SHOW TABLES", name).each do |field| + tables << field.first + end + tables + end + + def drop_table(table_name, options = {}) + super(table_name, options) + end + + def indexes(table_name, name = nil) + indexes = [] + current_index = nil + result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name) + result.each(:symbolize_keys => true, :as => :hash) do |row| + if current_index != row[:Key_name] + next if row[:Key_name] == PRIMARY # skip the primary key + current_index = row[:Key_name] + indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, []) + end + + indexes.last.columns << row[:Column_name] + end + indexes + end + + def columns(table_name, name = nil) + sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" + columns = [] + result = execute(sql, :skip_logging) + result.each(:symbolize_keys => true, :as => :hash) { |field| + columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") + } + columns + end + + def create_table(table_name, options = {}) + super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) + end + + def rename_table(table_name, new_name) + execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" + end + + def add_column(table_name, column_name, type, options = {}) + add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + add_column_options!(add_column_sql, options) + add_column_position!(add_column_sql, options) + execute(add_column_sql) + end + + def change_column_default(table_name, column_name, default) + column = column_for(table_name, column_name) + change_column table_name, column_name, column.sql_type, :default => default + end + + def change_column_null(table_name, column_name, null, default = nil) + column = column_for(table_name, column_name) + + unless null || default.nil? + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + + change_column table_name, column_name, column.sql_type, :null => null + end + + def change_column(table_name, column_name, type, options = {}) + column = column_for(table_name, column_name) + + unless options_include_default?(options) + options[:default] = column.default + end + + unless options.has_key?(:null) + options[:null] = column.null + end + + change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + add_column_options!(change_column_sql, options) + add_column_position!(change_column_sql, options) + execute(change_column_sql) + end + + def rename_column(table_name, column_name, new_column_name) + options = {} + if column = columns(table_name).find { |c| c.name == column_name.to_s } + options[:default] = column.default + options[:null] = column.null + else + raise ActiveRecordError, "No such column: #{table_name}.#{column_name}" + end + current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"] + rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" + add_column_options!(rename_column_sql, options) + execute(rename_column_sql) + end + + # Maps logical Rails types to MySQL-specific data types. + def type_to_sql(type, limit = nil, precision = nil, scale = nil) + return super unless type.to_s == 'integer' + + case limit + when 1; 'tinyint' + when 2; 'smallint' + when 3; 'mediumint' + when nil, 4, 11; 'int(11)' # compatibility with MySQL default + when 5..8; 'bigint' + else raise(ActiveRecordError, "No integer type has byte size #{limit}") + end + end + + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end + end + + def show_variable(name) + variables = select_all("SHOW VARIABLES LIKE '#{name}'") + variables.first['Value'] unless variables.empty? + end + + def pk_and_sequence_for(table) + keys = [] + result = execute("describe #{quote_table_name(table)}") + result.each(:symbolize_keys => true, :as => :hash) do |row| + keys << row[:Field] if row[:Key] == "PRI" + end + keys.length == 1 ? [keys.first, nil] : nil + end + + # Returns just a table's primary key + def primary_key(table) + pk_and_sequence = pk_and_sequence_for(table) + pk_and_sequence && pk_and_sequence.first + end + + def case_sensitive_equality_operator + "= BINARY" + end + + def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) + where_sql + end + + protected + def quoted_columns_for_index(column_names, options = {}) + length = options[:length] if options.is_a?(Hash) + + quoted_column_names = case length + when Hash + column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } + when Fixnum + column_names.map {|name| "#{quote_column_name(name)}(#{length})"} + else + column_names.map {|name| quote_column_name(name) } + end + end + + def translate_exception(exception, message) + return super unless exception.respond_to?(:error_number) + + case exception.error_number + when 1062 + RecordNotUnique.new(message, exception) + when 1452 + InvalidForeignKey.new(message, exception) + else + super + end + end + + private + def connect + @connection = Mysql2::Client.new(@config) + configure_connection + end + + def configure_connection + @connection.query_options.merge!(:as => :array) + encoding = @config[:encoding] + execute("SET NAMES '#{encoding}'", :skip_logging) if encoding + + # By default, MySQL 'where id is null' selects the last inserted id. + # Turn this off. http://dev.rubyonrails.org/ticket/6778 + execute("SET SQL_AUTO_IS_NULL=0", :skip_logging) + end + + # Returns an array of record hashes with the column names as keys and + # column values as values. + def select(sql, name = nil) + execute(sql, name).each(:as => :hash) + end + + def supports_views? + version[0] >= 5 + end + + def version + @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } + end + + def column_for(table_name, column_name) + unless column = columns(table_name).find { |c| c.name == column_name.to_s } + raise "No such column: #{table_name}.#{column_name}" + end + column + end + end + end +end -- cgit v1.2.3 From 898bd38d7a89c39a5e89f0e6be208e6622b07e3d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 01:30:37 +0200 Subject: AS guide: documents date/datetime/time arithmetic with durations --- .../source/active_support_core_extensions.textile | 63 +++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 9a449debae..fbb617715f 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -3009,6 +3009,26 @@ Date.new(2010, 1, 31).change(:month => 2) # => ArgumentError: invalid date +h5. Durations + +Durations can be added and substracted to dates: + + +d = Date.current +# => Mon, 09 Aug 2010 +d + 1.year +# => Tue, 09 Aug 2011 +d - 3.hours +# => Sun, 08 Aug 2010 21:00:00 UTC +00:00 + + +They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform: + + +Date.new(1582, 10, 4) + 1.day +# => Fri, 15 Oct 1582 + + h5. Timestamps INFO: The following methods return a +Time+ object if possible, otherwise a +DateTime+. If set, they honor the user time zone. @@ -3195,7 +3215,25 @@ DateTime.current.change(:month => 2, :day => 30) # => ArgumentError: invalid date -h4(#datetime-conversions). Conversions +h5. Durations + +Durations can be added and substracted to datetimes: + + +now = DateTime.current +# => Mon, 09 Aug 2010 23:15:17 +0000 +now + 1.year +# => Tue, 09 Aug 2011 23:15:17 +0000 +now - 1.week +# => Mon, 02 Aug 2010 23:15:17 +0000 + + +They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform: + + +DateTime.new(1582, 10, 4, 23) + 1.hour +# => Fri, 15 Oct 1582 00:00:00 +0000 + h3. Extensions to +Time+ @@ -3243,6 +3281,9 @@ They are analogous. Please refer to their documentation above and take into acco * +Time+ understands DST, so you get correct DST calculations as in +Time.zone_default +# => # + # In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST. t = Time.local_time(2010, 3, 28, 1, 59, 59) # => Sun Mar 28 01:59:59 +0100 2010 @@ -3287,6 +3328,26 @@ Both +local_time+ and +utc_time+ accept up to seven positional arguments: year, If the time to be constructed lies beyond the range supported by +Time+ in the runtime platform, usecs are discarded and a +DateTime+ object is returned instead. +h5. Durations + +Durations can be added and substracted to time objects: + + +now = Time.current +# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 +now + 1.year +# => Tue, 09 Aug 2011 23:21:11 UTC +00:00 +now - 1.week +# => Mon, 02 Aug 2010 23:21:11 UTC +00:00 + + +They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform: + + +Time.utc_time(1582, 10, 3) + 5.days +# => Mon Oct 18 00:00:00 UTC 1582 + + h3. Extensions to +Process+ ... -- cgit v1.2.3 From 41328815c06b6fb5e0747604389f7f4c8bd60483 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 01:53:53 +0200 Subject: AS guide: documents Process.daemon --- railties/guides/source/active_support_core_extensions.textile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index fbb617715f..01eb09f3d3 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -3350,7 +3350,9 @@ Time.utc_time(1582, 10, 3) + 5.days h3. Extensions to +Process+ -... +h4. +daemon+ + +Ruby 1.9 provides +Process.daemon+, and Active Support defines it for previous versions. It accepts the same two arguments, whether it should chdir to the root directory (default, true), and whether it should inherit the standard file descriptors from the parent (default, false). h3. Extensions to +File+ -- cgit v1.2.3 From a5d401aa998c085670fb94387304cc4e1b098412 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 03:28:59 +0200 Subject: AS guide: documents Module#delegate --- .../source/active_support_core_extensions.textile | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 01eb09f3d3..672bf750bf 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -984,6 +984,88 @@ though an anonymous module is unreachable by definition. NOTE: Defined in +active_support/core_ext/module/anonymous.rb+. +h4. Delegation + +The +delegate+ macro declares that some instance method has to be forwarded to some object. + +Let's imagine that users in some application have login information in the +User+ model but name and other data in a separate +Profile+ model: + + +class User < ActiveRecord::Base + has_one :profile +end + + +With that configuration you get a user's name via his profile, +user.profile.name+, but you could write a shortcut so that client code can read it directly: + + +class User < ActiveRecord::Base + has_one :profile + + def name + profile.name + end +end + + +That is what +delegate+ does for you: + + +class User < ActiveRecord::Base + has_one :profile + + delegate :name, :to => :profile +end + + +When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to: + + +delegate :logger, :to => :Rails +delegate :table_name, :to => 'self.class' + + +WARNING: If the +:prefix+ option is +true+ this is less generic, see below. + +By default, if the delegation raises +NoMethodError+ and the target is +nil+ the exception is propagated. You can ask that +nil+ is returned instead with the +:allow_nil+ option: + + +class User < ActiveRecord::Base + has_one :profile + + delegate :name, :to => :profile, :allow_nil => true +end + + +With +:allow_nil+ the call +user.name+ returns +nil+ if the user has no profile instead of raising an exception. + +The option +:prefix+ adds a prefix to the name of the generated method. This may be handy for example to get a better name: + + +class Account < ActiveRecord::Base + has_one :address + + delegate :street, :to => :address, :prefix => true +end + + +The previous example generates +Account#address_street+ rather than +Account#street+. + +WARNING: Since in this case the name of the generated method is composed of the target object and target method names, the +:to+ option must be a method name. + +A custom prefix may also be configured: + + +class User < ActiveRecord::Base + has_one :attachment + + delegate :size, :to => :attachment, :prefix => :avatar + + +In the previous example the macro generates +User#avatar_size+ rather than +User#size+. + +NOTE: Defined in +active_support/core_ext/module/delegation.rb+ + h3. Extensions to +Class+ h4. Class Attributes -- cgit v1.2.3 From cb9295c8a1c97f07a7a41af420831794fe9c1b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20L=C3=BCtke?= Date: Mon, 9 Aug 2010 21:33:02 -0400 Subject: Added test case to verify that transaction callbacks are correctly propagated to class observers --- .../test/cases/transaction_callbacks_test.rb | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index ffc2cd638f..d72c4bf7c4 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -245,3 +245,44 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [:after_rollback], @second.history end end + + +class TransactionObserverCallbacksTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + fixtures :topics + + class TopicWithObserverAttached < ActiveRecord::Base + set_table_name :topics + def history + @history ||= [] + end + end + + class TopicWithObserverAttachedObserver < ActiveRecord::Observer + def after_commit(record) + record.history.push :"TopicWithObserverAttachedObserver#after_commit" + end + + def after_rollback(record) + record.history.push :"TopicWithObserverAttachedObserver#after_rollback" + end + end + + def test_after_commit_called + topic = TopicWithObserverAttached.new + topic.save! + + assert topic.history, [:"TopicWithObserverAttachedObserver#after_commit"] + end + + def test_after_rollback_called + topic = TopicWithObserverAttached.new + + Topic.transaction do + topic.save! + raise ActiveRecord::Rollback + end + + assert topic.history, [:"TopicWithObserverObserver#after_rollback"] + end +end -- cgit v1.2.3 From ae147b45bb4aa2168e104ffc989e42f5c9dbfd80 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 03:46:21 +0200 Subject: AS guide: documents Module#(instance_)method_names --- .../guides/source/active_support_core_extensions.textile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 672bf750bf..e0c69516cf 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1066,6 +1066,18 @@ In the previous example the macro generates +User#avatar_size+ rather than +User NOTE: Defined in +active_support/core_ext/module/delegation.rb+ +h4. Method Names + +The builtin methods +instance_methods+ and +methods+ return method names as strings or symbols depending on the Ruby version. Active Support defines +instance_method_names+ and +method_names+ to be equivalent to them, respectively, but always getting strings back. + +For example, +ActionView::Helpers::FormBuilder+ knows this array difference is going to work no matter the Ruby version: + + +self.field_helpers = (FormHelper.instance_method_names - ['form_for']) + + +NOTE: Defined in +active_support/core_ext/module/method_names.rb+ + h3. Extensions to +Class+ h4. Class Attributes -- cgit v1.2.3 From 5fc8af4712ea5b5f7a2ee8bedb3b431901de814d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 03:59:26 +0200 Subject: AS guide: documents Module#redefine_method --- .../source/active_support_core_extensions.textile | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index e0c69516cf..31433d50eb 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1078,6 +1078,27 @@ self.field_helpers = (FormHelper.instance_method_names - ['form_for']) NOTE: Defined in +active_support/core_ext/module/method_names.rb+ +h4. Redefining Methods + +There are cases where you need to define a method with +define_method+, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either. + +The method +redefine_method+ prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API: + + +redefine_method("#{reflection.name}=") do |new_value| + association = association_instance_get(reflection.name) + + if association.nil? || association.target != new_value + association = association_proxy_class.new(self, reflection) + end + + association.replace(new_value) + association_instance_set(reflection.name, new_value.nil? ? nil : association) +end + + +NOTE: Defined in +active_support/core_ext/module/remove_method.rb+ + h3. Extensions to +Class+ h4. Class Attributes -- cgit v1.2.3 From 347d604792fb70873b6c1a9eb13fce11e96005fb Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 04:09:52 +0200 Subject: adds the AS guide to the guides index --- railties/guides/source/active_support_core_extensions.textile | 1 + railties/guides/source/index.html.erb | 4 ++++ railties/guides/source/layout.html.erb | 1 + 3 files changed, 6 insertions(+) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 31433d50eb..c1e3b97f81 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -3543,4 +3543,5 @@ h3. Changelog "Lighthouse ticket":https://rails.lighthouseapp.com/projects/16213/tickets/67 +* August 10, 2010: Starts to take shape, added to the index. * April 18, 2009: Initial version by "Xavier Noria":credits.html#fxn diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb index 254ee91ab4..a0db87c188 100644 --- a/railties/guides/source/index.html.erb +++ b/railties/guides/source/index.html.erb @@ -88,6 +88,10 @@ Ruby on Rails Guides
+<%= guide("Active Support Core Extensions", 'active_support_core_extensions.html') do %> +

This guide documents the Ruby core extensions defined in Active Support.

+<% end %> + <%= guide("Rails Internationalization API", 'i18n.html') do %>

This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.

<% end %> diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index c4758316ea..cc7d54c256 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -62,6 +62,7 @@
Digging Deeper
+
Active Support Core Extensions
Rails Internationalization API
Action Mailer Basics
Testing Rails Applications
-- cgit v1.2.3 From 2a984806ca89ac5fb64256d82fdfe318dd8c543d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 11:52:29 +0200 Subject: AS guide: removes some duplication, and makes a second pass on method delegation --- .../source/active_support_core_extensions.textile | 165 +++------------------ 1 file changed, 21 insertions(+), 144 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index c1e3b97f81..54f766fffd 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -667,129 +667,6 @@ end NOTE: Defined in +active_support/core_ext/module/attribute_accessors.rb+. -h4. Method Delegation - -The class method +delegate+ offers an easy way to forward methods. - -For example, if +User+ has some details like the age factored out to +Profile+, it could be handy to still be able to access such attributes directly, user.age, instead of having to explicit the chain user.profile.age. - -That can be accomplished by hand: - - -class User - has_one :profile - - def age - profile.age - end -end - - -But with +delegate+ you can make that shorter and the intention even more obvious: - - -class User - has_one :profile - - delegate :age, to => :profile -end - - -The macro accepts more than one method: - - -class User - has_one :profile - - delegate :age, :avatar, :twitter_username, to => :profile -end - - -Methods can be delegated to objects returned by methods, as in the examples above, but also to instance variables, class variables, and constants. Just pass their names as symbols or strings, including the at signs in the last cases. - -For example, +ActionView::Base+ delegates +erb_trim_mode=+: - - -module ActionView - class Base - delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' - end -end - - -In fact, you can delegate to any expression passed as a string. It will be evaluated in the context of the receiver. Controllers for example delegate alerts and notices to the current flash: - - -delegate :alert, :notice, :to => "request.flash" - - -If the target is +nil+ calling any delegated method will raise an exception even if +nil+ responds to such method. You can override this behavior setting the option +:allow_nil+ to true, in which case the forwarded call will simply return +nil+. - -If the target is a method, the name of delegated methods can also be prefixed. If the +:prefix+ option is set to (exactly) the +true+ object, the value of the +:to+ option is prefixed: - - -class Invoice - belongs_to :customer - - # defines a method called customer_name - delegate :name, :to => :customer, :prefix => true -end - - -And a custom prefix can be set as well, in that case it does not matter wheter the target is a method or not: - - -class Account - belongs_to :user - - # defines a method called admin_email - delegate :email, :to => :user, :prefix => 'admin' -end - - -NOTE: Defined in +active_support/core_ext/module/delegation.rb+. - -h4. Method Removal - -h5. +remove_possible_method+ - -The method +remove_possible_method+ is like the standard +remove_method+, except it silently returns on failure: - - -class A; end - -A.class_eval do - remove_method(:nonexistent) # raises NameError - remove_possible_method(:nonexistent) # no problem, continue -end - - -This may come in handy if you need to define a method that may already exist, since redefining a method issues a warning "method redefined; discarding old redefined_method_name". - -h5. +redefine_method(method_name, &block)+ - -The method first removes method with given name (using +remove_possible_method+) and then defines new one. - - -class A; end - -A.class_eval do - redefine_method(:foobar) do |foo| - #do something here - end - - #Code above does the same as this: - - method_name = :foobar - remove_possible_method(method_name) - define_method(method_name) do |foo| - #do something here - end -end - - -NOTE: Defined in +active_support/core_ext/module/remove_method.rb+. - h4. Parents h5. +parent+ @@ -984,9 +861,9 @@ though an anonymous module is unreachable by definition. NOTE: Defined in +active_support/core_ext/module/anonymous.rb+. -h4. Delegation +h4. Method Delegation -The +delegate+ macro declares that some instance method has to be forwarded to some object. +The macro +delegate+ offers an easy way to forward methods. Let's imagine that users in some application have login information in the +User+ model but name and other data in a separate +Profile+ model: @@ -996,7 +873,7 @@ class User < ActiveRecord::Base end -With that configuration you get a user's name via his profile, +user.profile.name+, but you could write a shortcut so that client code can read it directly: +With that configuration you get a user's name via his profile, +user.profile.name+, but it could be handy to still be able to access such attribute directly: class User < ActiveRecord::Base @@ -1018,10 +895,21 @@ class User < ActiveRecord::Base end -When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to: +It is shorter, and the intention more obvious. + +The macro accepts several methods: + + +delegate :name, :age, :address, :twitter, :to => :profile + + +When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such a expression is evaluated in the context of the receiver: +# delegates to the Rails constant delegate :logger, :to => :Rails + +# delegates to the receiver's class delegate :table_name, :to => 'self.class' @@ -1030,39 +918,28 @@ WARNING: If the +:prefix+ option is +true+ this is less generic, see below. By default, if the delegation raises +NoMethodError+ and the target is +nil+ the exception is propagated. You can ask that +nil+ is returned instead with the +:allow_nil+ option: -class User < ActiveRecord::Base - has_one :profile - - delegate :name, :to => :profile, :allow_nil => true -end +delegate :name, :to => :profile, :allow_nil => true -With +:allow_nil+ the call +user.name+ returns +nil+ if the user has no profile instead of raising an exception. +With +:allow_nil+ the call +user.name+ returns +nil+ if the user has no profile. The option +:prefix+ adds a prefix to the name of the generated method. This may be handy for example to get a better name: -class Account < ActiveRecord::Base - has_one :address - - delegate :street, :to => :address, :prefix => true -end +delegate :street, :to => :address, :prefix => true -The previous example generates +Account#address_street+ rather than +Account#street+. +The previous example generates +address_street+ rather than +street+. WARNING: Since in this case the name of the generated method is composed of the target object and target method names, the +:to+ option must be a method name. A custom prefix may also be configured: -class User < ActiveRecord::Base - has_one :attachment - - delegate :size, :to => :attachment, :prefix => :avatar +delegate :size, :to => :attachment, :prefix => :avatar -In the previous example the macro generates +User#avatar_size+ rather than +User#size+. +In the previous example the macro generates +avatar_size+ rather than +size+. NOTE: Defined in +active_support/core_ext/module/delegation.rb+ -- cgit v1.2.3 From fff917e37d979fcbb4844671acbba7977c26d22a Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 10 Aug 2010 15:34:54 +0200 Subject: fixes a typo reported by rymai --- activemodel/lib/active_model/validations/validates.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 0674640925..3260e6bc5a 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -40,7 +40,7 @@ module ActiveModel # validates :email, :presence => true, :email => true # end # - # Validator classes my also exist within the class being validated + # Validator classes may also exist within the class being validated # allowing custom modules of validators to be included as needed e.g. # # class Film -- cgit v1.2.3 From e86cced311539932420f9cda49d736606d106c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 10 Aug 2010 11:18:58 -0300 Subject: Revert "require_dependency should require using the normal mechanism if possible to avoid double-requires" This was causing double requires since 991cd59a225b90ab1ba3 was reverted. This reverts commit 8bf79739b4219eb1d6464e6eb4853e92e81d7621. --- activesupport/lib/active_support/dependencies.rb | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 1b93eac7ee..2b80bd214f 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -276,22 +276,14 @@ module ActiveSupport #:nodoc: end def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb") - #path = search_for_file(file_name) - require_or_load(file_name) - rescue LoadError - begin - if path = search_for_file(file_name) - require_or_load(path) - else - raise - end - rescue LoadError => load_error - unless swallow_load_errors - if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] - raise LoadError.new(message % file_name).copy_blame!(load_error) - end - raise + path = search_for_file(file_name) + require_or_load(path || file_name) + rescue LoadError => load_error + unless swallow_load_errors + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + raise LoadError.new(message % file_name).copy_blame!(load_error) end + raise end end -- cgit v1.2.3