aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG16
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb56
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb24
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--railties/guides/source/action_view_overview.textile77
-rw-r--r--railties/guides/source/generators.textile9
-rw-r--r--railties/guides/source/rails_application_templates.textile12
-rw-r--r--railties/lib/rails/generators/actions.rb27
-rw-r--r--railties/test/generators/actions_test.rb14
10 files changed, 231 insertions, 15 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 31a03ea5d8..285ab05103 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,21 @@
*Rails 3.2.0 (unreleased)*
+* content_tag_for and div_for can now take the collection of records. It will also yield the record as the first argument if you set a receiving argument in your block [Prem Sichanugrist]
+
+ So instead of having to do this:
+
+ @items.each do |item|
+ content_tag_for(:li, item) do
+ Title: <%= item.title %>
+ end
+ end
+
+ You can now do this:
+
+ content_tag_for(:li, @items) do |item|
+ Title: <%= item.title %>
+ end
+
* send_file now guess the mime type [Esad Hajdarevic]
* Mime type entries for PDF, ZIP and other formats were added [Esad Hajdarevic]
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 142a25f118..ad8896a2fd 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -17,6 +17,19 @@ module ActionView
#
# <div id="person_123" class="person foo"> Joe Bloggs </div>
#
+ # You can also pass an array of Active Record objects, which will then
+ # get iterates over and yield each record as an argument for the block.
+ # For example:
+ #
+ # <%= div_for(@people, :class => "foo") do |person| %>
+ # <%= person.name %>
+ # <% end %>
+ #
+ # produces:
+ #
+ # <div id="person_123" class="person foo"> Joe Bloggs </div>
+ # <div id="person_124" class="person foo"> Jane Bloggs </div>
+ #
def div_for(record, *args, &block)
content_tag_for(:div, record, *args, &block)
end
@@ -42,6 +55,21 @@ module ActionView
#
# <tr id="foo_person_123" class="person">...
#
+ # You can also pass an array of objects which this method will loop through
+ # and yield the current object to the supplied block, reduce the need for
+ # having to iterate through the object (using <tt>each</tt>) beforehand.
+ # For example (assuming @people is an array of Person objects):
+ #
+ # <%= content_tag_for(:tr, @people) do |person| %>
+ # <td><%= person.first_name %></td>
+ # <td><%= person.last_name %></td>
+ # <% end %>
+ #
+ # produces:
+ #
+ # <tr id="person_123" class="person">...</tr>
+ # <tr id="person_124" class="person">...</tr>
+ #
# content_tag_for also accepts a hash of options, which will be converted to
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
# with the default class name for your object. For example:
@@ -52,12 +80,30 @@ module ActionView
#
# <li id="person_123" class="person bar">...
#
- def content_tag_for(tag_name, record, prefix = nil, options = nil, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
- options ||= {}
- options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
- content_tag(tag_name, options, &block)
+ def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
+ if single_or_multiple_records.respond_to?(:to_ary)
+ single_or_multiple_records.to_ary.map do |single_record|
+ capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) }
+ end.join("\n")
+ else
+ content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block)
+ end
end
+
+ private
+
+ # Called by <tt>content_tag_for</tt> internally to render a content tag
+ # for each record.
+ def content_tag_for_single_record(tag_name, record, prefix, options, &block)
+ options, prefix = prefix, nil if prefix.is_a?(Hash)
+ options ||= {}
+ options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
+ if block.arity == 0
+ content_tag(tag_name, capture(&block), options)
+ else
+ content_tag(tag_name, capture(record, &block), options)
+ end
+ end
end
end
end
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index 1ba14e8bc9..edc2689896 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -4,11 +4,12 @@ require 'controller/fake_models'
class Post
extend ActiveModel::Naming
include ActiveModel::Conversion
+ attr_writer :id, :body
def id
- 45
+ @id || 45
end
def body
- super || "What a wonderful world!"
+ super || @body || "What a wonderful world!"
end
end
@@ -58,4 +59,23 @@ class RecordTagHelperTest < ActionView::TestCase
actual = div_for(@post, :class => "bar") { concat @post.body }
assert_dom_equal expected, actual
end
+
+ def test_content_tag_for_collection
+ post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
+ post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
+ expected = %(<li class="post" id="post_101">Hello!</li>\n<li class="post" id="post_102">World!</li>)
+ actual = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ assert_dom_equal expected, actual
+ end
+
+ def test_content_tag_for_collection_is_html_safe
+ end
+
+ def test_div_for_collection
+ post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
+ post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
+ expected = %(<div class="post" id="post_101">Hello!</div>\n<div class="post" id="post_102">World!</div>)
+ actual = div_for([post_1, post_2]) { |post| concat post.body }
+ assert_dom_equal expected, actual
+ end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 355540782f..7eda9ad8e8 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -305,8 +305,10 @@ module ActiveRecord
when Arel::Nodes::Ordering
o.reverse
when String, Symbol
- s = o.to_s.gsub(/\s((desc)|(asc))\s*(,|\Z)/i) { |m| " #{$2 ? 'ASC' : 'DESC'}#{$4}" }
- s.match(/\s(de|a)sc\Z/i) ? s : s.concat(" DESC")
+ o.to_s.split(',').collect do |s|
+ s.strip!
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
+ end
else
o
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index c3bad58174..da96afd718 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -934,11 +934,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 'zyke', FastCar.order_using_old_style.limit(1).first.name
end
- def test_order_with_function_and_last
- authors = Author.scoped
- assert_equal authors(:bob), authors.order( "id asc, COALESCE( organization_id, owned_essay_id)" ).last
- end
-
def test_order_using_scoping
car1 = CoolCar.order('id DESC').scoping do
CoolCar.find(:first, :order => 'id asc')
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index 5a1e8b1247..d1827c649b 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -454,6 +454,83 @@ input("post", "title") # =>
<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
</ruby>
+h4. RecordTagHelper
+
+This module provides methods for generating a container tag, such as a +<div>+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
+
+h5. content_tag_for
+
+Renders a container tag that relates to your Active Record Object.
+
+For example, given +@post+ is the object of +Post+ class, you can do:
+
+<ruby>
+<%= content_tag_for(:tr, @post) do %>
+ <td><%= @post.title %></td>
+<% end %>
+</ruby>
+
+This will generate this HTML output:
+
+<html>
+<tr id="post_1234" class="post">
+ <td>Hello World!</td>
+</tr>
+</html>
+
+You can also supply HTML attributes as an additional option hash. For example:
+
+<ruby>
+<%= content_tag_for(:tr, @post, :class => "frontpage") do %>
+ <td><%= @post.title %></td>
+<% end %>
+</ruby>
+
+Will generate this HTML output:
+
+<html>
+<tr id="post_1234" class="post frontpage">
+ <td>Hello World!</td>
+</tr>
+</html>
+
+You can pass a collection of Active Record objects. This method will loops through your objects and create a container for each of them. For example, given +@posts+ is an array of two +Post+ objects:
+
+<ruby>
+<%= content_tag_for(:tr, @posts) do |post| %>
+ <td><%= post.title %></td>
+<% end %>
+</ruby>
+
+Will generate this HTML output:
+
+<html>
+<tr id="post_1234" class="post">
+ <td>Hello World!</td>
+</tr>
+<tr id="post_1235" class="post">
+ <td>Ruby on Rails Rocks!</td>
+</tr>
+</html>
+
+h5. div_for
+
+This is actually a convenient method which calls +content_tag_for+ internally with +:div+ as the tag name. You can pass either an Active Record object or a collection of objects. For example:
+
+<ruby>
+<%= div_for(@post, :class => "frontpage") do %>
+ <td><%= @post.title %></td>
+<% end %>
+</ruby>
+
+Will generate this HTML output:
+
+<html>
+<div id="post_1234" class="post frontpage">
+ <td>Hello World!</td>
+</div>
+</html>
+
h4. AssetTagHelper
This module provides methods for generating HTML that links views to assets such as images, JavaScript files, stylesheets, and feeds.
diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile
index 2fa1d6e21d..3f990ef54b 100644
--- a/railties/guides/source/generators.textile
+++ b/railties/guides/source/generators.textile
@@ -449,6 +449,15 @@ The above code will put the following line into +Gemfile+:
gem "devise", :git => "git://github.com/plataformatec/devise", :branch => "master"
</ruby>
+h4. +gem_group+
+
+Wraps gem entries inside a group:
+
+<ruby>
+gem_group :development, :test do
+ gem "rspec-rails"
+end
+</ruby>
h4. +add_source+
diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile
index 566f8a0bdd..c3c8af4d3a 100644
--- a/railties/guides/source/rails_application_templates.textile
+++ b/railties/guides/source/rails_application_templates.textile
@@ -60,6 +60,18 @@ Please note that this will NOT install the gems for you and you will have to run
bundle install
</ruby>
+h4. gem_group(*names, &block)
+
+Wraps gem entries inside a group.
+
+For example, if you want to load +rspec-rails+ only in +development+ and +test+ group:
+
+<ruby>
+gem_group :development, :test do
+ gem "rspec-rails"
+end
+</ruby>
+
h4. add_source(source, options = {})
Adds the given source to the generated application's +Gemfile+.
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index c43a99e85c..575f4bb106 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -68,7 +68,32 @@ module Rails
end
in_root do
- append_file "Gemfile", "gem #{parts.join(", ")}\n", :verbose => false
+ str = "gem #{parts.join(", ")}\n"
+ str = " " + str if @in_group
+ append_file "Gemfile", str, :verbose => false
+ end
+ end
+
+ # Wraps gem entries inside a group.
+ #
+ # ==== Example
+ #
+ # gem_group :development, :test do
+ # gem "rspec-rails"
+ # end
+ #
+ def gem_group(*names, &block)
+ name = names.map(&:inspect).join(", ")
+ log :gemfile, "group #{name}"
+
+ in_root do
+ append_file "Gemfile", "\ngroup #{name} do\n", :force => true
+
+ @in_group = true
+ instance_eval &block
+ @in_group = false
+
+ append_file "Gemfile", "end\n", :force => true
end
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 56cb53c1ad..94e9abb3cc 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -102,6 +102,20 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'Gemfile', /gem "rspec-rails"$/
end
+ def test_gem_group_should_wrap_gems_in_a_group
+ run_generator
+
+ action :gem_group, :development, :test do
+ gem 'rspec-rails'
+ end
+
+ action :gem_group, :test do
+ gem 'fakeweb'
+ end
+
+ assert_file 'Gemfile', /\ngroup :development, :test do\n gem "rspec-rails"\nend\n\ngroup :test do\n gem "fakeweb"\nend/
+ end
+
def test_environment_should_include_data_in_environment_initializer_block
run_generator
autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]'