diff options
-rw-r--r-- | actionpack/lib/action_dispatch/testing/assertions/routing.rb | 11 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/query_methods.rb | 41 | ||||
-rw-r--r-- | activerecord/lib/active_record/session_store.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/time/zones.rb | 10 | ||||
-rw-r--r-- | guides/source/action_mailer_basics.textile | 4 | ||||
-rw-r--r-- | guides/source/active_record_querying.textile | 2 | ||||
-rw-r--r-- | guides/source/contributing_to_ruby_on_rails.textile | 2 | ||||
-rw-r--r-- | guides/source/engines.textile | 38 | ||||
-rw-r--r-- | guides/source/form_helpers.textile | 135 | ||||
-rw-r--r-- | guides/source/getting_started.textile | 1 | ||||
-rw-r--r-- | guides/source/testing.textile | 10 |
11 files changed, 218 insertions, 38 deletions
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 41fa3a4b95..9de545b3c5 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -127,16 +127,13 @@ module ActionDispatch # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block - # will create some routes using <tt>map.draw { map.connect ... }</tt>: + # will create some routes using <tt>set.draw { match ... }</tt>: # # with_routing do |set| - # set.draw do |map| - # map.connect ':controller/:action/:id' - # assert_equal( - # ['/content/10/show', {}], - # map.generate(:controller => 'content', :id => 10, :action => 'show') - # end + # set.draw do + # resources :users # end + # assert_equal "/users", users_path # end # def with_routing diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c958fdb5d5..1271b74ead 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -51,7 +51,18 @@ module ActiveRecord # # allows you to access the +address+ attribute of the +User+ model without # firing an additional query. This will often result in a - # performance improvement over a simple +join+ + # performance improvement over a simple +join+. + # + # === conditions + # + # If you want to add conditions to your included models you'll have + # to explicitly reference them. For example: + # + # User.includes(:posts).where('posts.name = ?', 'example') + # + # Will throw an error, but this will work: + # + # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) def includes(*args) args.empty? ? self : spawn.includes!(*args) end @@ -63,6 +74,12 @@ module ActiveRecord self end + # Forces eager loading by performing a LEFT OUTER JOIN on +args+: + # + # User.eager_load(:posts) + # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... + # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = + # "users"."id" def eager_load(*args) args.blank? ? self : spawn.eager_load!(*args) end @@ -72,6 +89,10 @@ module ActiveRecord self end + # Allows preloading of +args+, in the same way that +includes+ does: + # + # User.preload(:posts) + # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) def preload(*args) args.blank? ? self : spawn.preload!(*args) end @@ -147,7 +168,7 @@ module ActiveRecord # User.group(:name) # => SELECT "users".* FROM "users" GROUP BY name # - # Returns an array with distinct records based on the `group` attribute: + # Returns an array with distinct records based on the +group+ attribute: # # User.select([:id, :name]) # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo"> @@ -211,6 +232,10 @@ module ActiveRecord self end + # Performs a joins on +args+: + # + # User.joins(:posts) + # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" def joins(*args) args.compact.blank? ? self : spawn.joins!(*args) end @@ -334,6 +359,10 @@ module ActiveRecord self end + # Allows to specify a HAVING clause. Note that you can't use HAVING + # without also specifying a GROUP clause. + # + # Order.having('SUM(price) > 30').group('user_id') def having(opts, *rest) opts.blank? ? self : spawn.having!(opts, *rest) end @@ -375,6 +404,8 @@ module ActiveRecord self end + # Specifies locking settings (default to +true+). For more information + # on locking, please see +ActiveRecord::Locking+. def lock(locks = true) spawn.lock!(locks) end @@ -423,6 +454,12 @@ module ActiveRecord scoped.extending(NullRelation) end + # Sets readonly attributes for the returned relation. If value is + # true (default), attempting to update a record will result in an error. + # + # users = User.readonly + # users.first.save + # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord def readonly(value = true) spawn.readonly!(value) end diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 700621632e..d2d3106721 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -7,7 +7,7 @@ module ActiveRecord # # The default assumes a +sessions+ tables with columns: # +id+ (numeric primary key), - # +session_id+ (text, or longtext if your session data exceeds 65K), and + # +session_id+ (string, usually varchar; maximum length is 255), and # +data+ (text or longtext; careful if your session data exceeds 65KB). # # The +session_id+ column should always be indexed for speedy lookups. diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 5f2fec3782..37bc3fae24 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -26,11 +26,11 @@ class Time # around_filter :set_time_zone # # def set_time_zone - # old_time_zone = Time.zone - # Time.zone = current_user.time_zone if logged_in? - # yield - # ensure - # Time.zone = old_time_zone + # if logged_in? + # Time.use_zone(current_user.time_zone) { yield } + # else + # yield + # end # end # end def zone=(time_zone) diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile index 7c61cc4a8d..54e55d7260 100644 --- a/guides/source/action_mailer_basics.textile +++ b/guides/source/action_mailer_basics.textile @@ -508,8 +508,8 @@ class UserMailerTest < ActionMailer::TestCase # Test the body of the sent email contains what we expect it to assert_equal [user.email], email.to assert_equal "Welcome to My Awesome Site", email.subject - assert_match(/<h1>Welcome to example.com, #{user.name}<\/h1>/, email.encoded) - assert_match(/Welcome to example.com, #{user.name}/, email.encoded) + assert_match "<h1>Welcome to example.com, #{user.name}</h1>", email.body.to_s + assert_match "you have joined to example.com community", email.body.to_s end end </ruby> diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index 9863d16fda..f71a136b22 100644 --- a/guides/source/active_record_querying.textile +++ b/guides/source/active_record_querying.textile @@ -1043,7 +1043,7 @@ Even though Active Record lets you specify conditions on the eager loaded associ However if you must do this, you may use +where+ as you would normally. <ruby> -Post.includes(:comments).where("comments.visible", true) +Post.includes(:comments).where("comments.visible" => true) </ruby> This would generate a query which contains a +LEFT OUTER JOIN+ whereas the +joins+ method would generate one using the +INNER JOIN+ function instead. diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile index fd5e2b28c0..a8a097d156 100644 --- a/guides/source/contributing_to_ruby_on_rails.textile +++ b/guides/source/contributing_to_ruby_on_rails.textile @@ -34,6 +34,8 @@ h4. What about Feature Requests? Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed. +If you'd like feedback on an idea for a feature before doing the work for make a patch, please send an email to the "rails-core mailing list":https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core. You might get no response, which means that everyone is indifferent. You might find someone who's also interested in building that feature. You might get a "This won't be accepted." But it's the proper place to discuss new ideas. GitHub Issues are not a particularly good venue for the sometimes long and involved discussions new features require. + h3. Running the Test Suite To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer. diff --git a/guides/source/engines.textile b/guides/source/engines.textile index 86e7254201..53c2845731 100644 --- a/guides/source/engines.textile +++ b/guides/source/engines.textile @@ -347,7 +347,7 @@ The form will be making a +POST+ request to +/posts/:post_id/comments+, which wi <ruby> def create @post = Post.find(params[:post_id]) - @comment = @post.comments.build(params[:comment]) + @comment = @post.comments.create(params[:comment]) flash[:notice] = "Comment has been created!" redirect_to post_path end @@ -563,7 +563,7 @@ end By default, the engine's controllers inherit from <tt>Blorgh::ApplicationController</tt>. So, after making this change they will have access to the main applications +ApplicationController+ as though they were part of the main application. -This change does require that the engine is run from a Rails application that has an +ApplicationController+. +This change does require that the engine is run from a Rails application that has an +ApplicationController+. h4. Configuring an engine @@ -734,12 +734,14 @@ You can also specify these assets as dependencies of other assets using the Asse */ </plain> +INFO. Remember that in order to use languages like Sass or CoffeeScript, you should add the relevant library to your engine's +.gemspec+. + h4. Separate Assets & Precompiling There are some situations where your engine's assets not required by the host application. For example, say that you've created an admin functionality that only exists for your engine. In this case, the host application doesn't need to require +admin.css+ or +admin.js+. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include +"blorg/admin.css"+ in it's stylesheets. In this situation, you should explicitly define these assets for precompilation. -This tells sprockets to add you engine assets when +rake assets:precompile+ is ran. +This tells sprockets to add you engine assets when +rake assets:precompile+ is ran. You can define assets for precompilation in +engine.rb+ @@ -753,18 +755,40 @@ For more information, read the "Asset Pipeline guide":http://guides.rubyonrails. h4. Other gem dependencies -Gem dependencies inside an engine should be specified inside the +.gemspec+ file that's at the root of the engine. The reason for this is because the engine may be installed as a gem. If dependencies were to be specified inside the +Gemfile+, these would not be recognised by a traditional gem install and so they would not be installed, causing the engine to malfunction. +Gem dependencies inside an engine should be specified inside the +.gemspec+ file +that's at the root of the engine. The reason for this is because the engine may +be installed as a gem. If dependencies were to be specified inside the +Gemfile+, +these would not be recognised by a traditional gem install and so they would not +be installed, causing the engine to malfunction. -To specify a dependency that should be installed with the engine during a traditional +gem install+, specify it inside the +Gem::Specification+ block inside the +.gemspec+ file in the engine: +To specify a dependency that should be installed with the engine during a +traditional +gem install+, specify it inside the +Gem::Specification+ block +inside the +.gemspec+ file in the engine: <ruby> s.add_dependency "moo" </ruby> -To specify a dependency that should only be installed as a development dependency of the application, specify it like this: +To specify a dependency that should only be installed as a development +dependency of the application, specify it like this: <ruby> s.add_development_dependency "moo" </ruby> -Both kinds of dependencies will be installed when +bundle install+ is run inside the application. The development dependencies for the gem will only be used when the tests for the engine are running. +Both kinds of dependencies will be installed when +bundle install+ is run inside +the application. The development dependencies for the gem will only be used when +the tests for the engine are running. + +Note that if you want to immediately require dependencies when the engine is +required, you should require them before engine's initialization. For example: + +<ruby> +require 'other_engine/engine' +require 'yet_another_engine/engine' + +module MyEngine + class Engine < ::Rails::Engine + end +end +</ruby>
\ No newline at end of file diff --git a/guides/source/form_helpers.textile b/guides/source/form_helpers.textile index 1851aceff8..58338ce54b 100644 --- a/guides/source/form_helpers.textile +++ b/guides/source/form_helpers.textile @@ -10,7 +10,7 @@ In this guide you will: * Understand the date and time helpers Rails provides * Learn what makes a file upload form different * Learn some cases of building forms to external resources -* Find out where to look for complex forms +* Find out how to build complex forms endprologue. @@ -816,11 +816,130 @@ Or if you don't want to render an +authenticity_token+ field: h3. Building Complex Forms -Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include: +Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. -* As of Rails 2.3, Rails includes "Nested Attributes":./2_3_release_notes.html#nested-attributes and "Nested Object Forms":./2_3_release_notes.html#nested-object-forms -* Ryan Bates' series of Railscasts on "complex forms":http://railscasts.com/episodes/75 -* Handle Multiple Models in One Form from "Advanced Rails Recipes":http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf -* Eloy Duran's "complex-forms-examples":https://github.com/alloy/complex-form-examples/ application -* Lance Ivy's "nested_assignment":https://github.com/cainlevy/nested_assignment/tree/master plugin and "sample application":https://github.com/cainlevy/complex-form-examples/tree/cainlevy -* James Golick's "attribute_fu":https://github.com/jamesgolick/attribute_fu plugin +h4. Configuring the Model + +Active Record provides model level support via the +accepts_nested_attributes_for+ method: + +<ruby> +class Person < ActiveRecord::Base + has_many :addresses + accepts_nested_attributes_for :addresses + + attr_accessible :name, :addresses_attributes +end + +class Address < ActiveRecord::Base + belongs_to :person + attr_accessible :kind, :street +end +</ruby> + +This creates an +addresses_attributes=+ method on +Person+ that allows you to create, update and (optionally) destroy addresses. When using +attr_accessible+ or +attr_protected+ you must mark +addresses_attributes+ as accessible as well as the other attributes of +Person+ and +Address+ that should be mass assigned. + +h4. Building the Form + +The following form allows a user to create a +Person+ and its associated addresses. + +<erb> +<%= form_for @person do |f| %> + Addresses: + <ul> + <%= f.fields_for :addresses do |addresses_form| %> + <li> + <%= addresses_form.label :kind %> + <%= addresses_form.text_field :kind %> + + <%= addresses_form.label :street %> + <%= addresses_form.text_field :street %> + ... + </li> + <% end %> + </ul> +<% end %> +</erb> + + +When an association accepts nested attributes +fields_for+ renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 3 sets of address fields being rendered on the new person form. + +<ruby> +def new + @person = Person.new + 3.times { @person.addresses.build} +end +</ruby> + ++fields_for+ yields a form builder that names parameters in the format expected the accessor generated by +accepts_nested_attributes_for+. For example when creating a user with 2 addresses, the submitted parameters would look like + +<ruby> +{ + :person => { + :name => 'John Doe', + :addresses_attributes => { + '0' => { + :kind => 'Home', + :street => '221b Baker Street', + }, + '1' => { + :kind => 'Office', + :street => '31 Spooner Street' + } + } + } +} +</ruby> + +The keys of the +:addresses_attributes+ hash are unimportant, they need merely be different for each address. + +If the associated object is already saved, +fields_for+ autogenerates a hidden input with the +id+ of the saved record. You can disable this by passing +:include_id => false+ to +fields_for+. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an id. + +h4. The Controller + +You do not need to write any specific controller code to use nested attributes. Create and update records as you would with a simple form. + +h4. Removing Objects + +You can allow users to delete associated objects by passing +allow_destroy => true+ to +accepts_nested_attributes_for+ + +<ruby> +class Person < ActiveRecord::Base + has_many :addresses + accepts_nested_attributes_for :addresses, :allow_destroy => true +end +</ruby> + +If the hash of attributes for an object contains the key +_destroy+ with a value of '1' or 'true' then the object will be destroyed. This form allows users to remove addresses: + +<erb> +<%= form_for @person do |f| %> + Addresses: + <ul> + <%= f.fields_for :addresses do |addresses_form| %> + <li> + <%= check_box :_destroy%> + <%= addresses_form.label :kind %> + <%= addresses_form.text_field :kind %> + ... + </li> + <% end %> + </ul> +<% end %> +</erb> + +h4. Preventing Empty Records + +It is often useful to ignore sets of fields that the user has not filled in. You can control this by passing a +:reject_if+ proc to +accepts_nested_attributes_for+. This proc will be called with each hash of attributes submitted by the form. If the proc returns +false+ then Active Record will not build an associated object for that hash. The example below only tries to build an address if the +kind+ attribute is set. + +<ruby> +class Person < ActiveRecord::Base + has_many :addresses + accepts_nested_attributes_for :addresses, :reject_if => lambda {|attributes| attributes['kind'].blank?} +end +</ruby> + +As a convenience you can instead pass the symbol +:all_blank+ which will create a proc that will reject records where all the attributes are blank excluding any value for +_destroy+. + +h4. Adding Fields on the Fly + +Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new child' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the the key of the associated array is unique - the current javascript date (milliseconds after the epoch) is a common choice.
\ No newline at end of file diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile index 07419d11b4..b00d9af00c 100644 --- a/guides/source/getting_started.textile +++ b/guides/source/getting_started.textile @@ -1248,6 +1248,7 @@ First, take a look at +comment.rb+: <ruby> class Comment < ActiveRecord::Base belongs_to :post + attr_accessible :body, :commenter end </ruby> diff --git a/guides/source/testing.textile b/guides/source/testing.textile index 26bfc132d8..4faf59fad8 100644 --- a/guides/source/testing.textile +++ b/guides/source/testing.textile @@ -357,12 +357,12 @@ There are a bunch of different types of assertions you can use. Here's the compl |_.Assertion |_.Purpose| |+assert( boolean, [msg] )+ |Ensures that the object/expression is true.| -|+assert_equal( obj1, obj2, [msg] )+ |Ensures that +obj1 == obj2+ is true.| -|+assert_not_equal( obj1, obj2, [msg] )+ |Ensures that +obj1 == obj2+ is false.| -|+assert_same( obj1, obj2, [msg] )+ |Ensures that +obj1.equal?(obj2)+ is true.| -|+assert_not_same( obj1, obj2, [msg] )+ |Ensures that +obj1.equal?(obj2)+ is false.| +|+assert_equal( expected, actual, [msg] )+ |Ensures that +expected == actual+ is true.| +|+assert_not_equal( expected, actual, [msg] )+ |Ensures that +expected != actual+ is true.| +|+assert_same( expected, actual, [msg] )+ |Ensures that +expected.equal?(actual)+ is true.| +|+assert_not_same( expected, actual, [msg] )+ |Ensures that +!expected.equal?(actual)+ is true.| |+assert_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is true.| -|+assert_not_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is false.| +|+assert_not_nil( obj, [msg] )+ |Ensures that +!obj.nil?+ is true.| |+assert_match( regexp, string, [msg] )+ |Ensures that a string matches the regular expression.| |+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't match the regular expression.| |+assert_in_delta( expecting, actual, delta, [msg] )+ |Ensures that the numbers +expecting+ and +actual+ are within +delta+ of each other.| |