提交 6acd1304 编写于 作者: V vijay

Merge branch 'master' of github.com:lifo/docrails

......@@ -4,25 +4,26 @@ module ActionController #:nodoc:
module Caching
# Action caching is similar to page caching by the fact that the entire
# output of the response is cached, but unlike page caching, every
# request still goes through the Action Pack. The key benefit
# of this is that filters are run before the cache is served, which
# allows for authentication and other restrictions on whether someone
# is allowed to see the cache. Example:
# request still goes through Action Pack. The key benefit of this is
# that filters run before the cache is served, which allows for
# authentication and other restrictions on whether someone is allowed
# to execute such action. Example:
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
#
# caches_page :public
# caches_action :index, :show, :feed
# caches_action :index, :show
# end
#
# In this example, the public action doesn't require authentication,
# so it's possible to use the faster page caching method. But both
# the show and feed action are to be shielded behind the authenticate
# filter, so we need to implement those as action caches.
# In this example, the +public+ action doesn't require authentication
# so it's possible to use the faster page caching. On the other hand
# +index+ and +show+ require authentication. They can still be cached,
# but we need action caching for them.
#
# Action caching internally uses the fragment caching and an around
# filter to do the job. The fragment cache is named according to both
# the current host and the path. So a page that is accessed at
# Action caching uses fragment caching internally and an around
# filter to do the job. The fragment cache is named according to
# the host and path of the request. A page that is accessed at
# <tt>http://david.example.com/lists/show/1</tt> will result in a fragment named
# <tt>david.example.com/lists/show/1</tt>. This allows the cacher to
# differentiate between <tt>david.example.com/lists/</tt> and
......@@ -38,19 +39,23 @@ module Caching
# <tt>:action => 'list', :format => :xml</tt>.
#
# You can set modify the default action cache path by passing a
# :cache_path option. This will be passed directly to
# ActionCachePath.path_for. This is handy for actions with multiple
# possible routes that should be cached differently. If a block is
# given, it is called with the current controller instance.
# <tt>:cache_path</tt> option. This will be passed directly to
# <tt>ActionCachePath.path_for</tt>. This is handy for actions with
# multiple possible routes that should be cached differently. If a
# block is given, it is called with the current controller instance.
#
# And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
# proc that specifies when the action should be cached.
#
# And you can also use :if (or :unless) to pass a Proc that
# specifies when the action should be cached.
# Finally, if you are using memcached, you can also pass <tt>:expires_in</tt>.
#
# Finally, if you are using memcached, you can also pass :expires_in.
# The following example depicts some of the points made above:
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
#
# caches_page :public
#
# caches_action :index, :if => proc do |c|
# !c.request.format.json? # cache if is not a JSON request
# end
......@@ -58,19 +63,28 @@ module Caching
# caches_action :show, :cache_path => { :project => 1 },
# :expires_in => 1.hour
#
# caches_action :feed, :cache_path => proc do |controller|
# if controller.params[:user_id]
# controller.send(:user_list_url,
# controller.params[:user_id], controller.params[:id])
# caches_action :feed, :cache_path => proc do |c|
# if c.params[:user_id]
# c.send(:user_list_url,
# c.params[:user_id], c.params[:id])
# else
# controller.send(:list_url, controller.params[:id])
# c.send(:list_url, c.params[:id])
# end
# end
# end
#
# If you pass :layout => false, it will only cache your action
# content. It is useful when your layout has dynamic information.
# If you pass <tt>:layout => false</tt>, it will only cache your action
# content. That's useful when your layout has dynamic information.
#
# Warning: If the format of the request is determined by the Accept HTTP
# header the Content-Type of the cached response could be wrong because
# no information about the MIME type is stored in the cache key. So, if
# you first ask for MIME type M in the Accept header, a cache entry is
# created, and then perform a second resquest to the same resource asking
# for a different MIME type, you'd get the content cached for M.
#
# The <tt>:format</tt> parameter is taken into account though. The safest
# way to cache by MIME type is to pass the format in the route.
module Actions
extend ActiveSupport::Concern
......
......@@ -247,7 +247,11 @@ def initialize(set) #:nodoc:
#
# root :to => 'pages#main'
#
# You should put the root route at the end of <tt>config/routes.rb</tt>.
# For options, see the +match+ method's documentation, as +root+ uses it internally.
#
# You should put the root route at the top of <tt>config/routes.rb</tt>,
# because this means it will be matched first. As this is the most popular route
# of most Rails applications, this is beneficial.
def root(options = {})
match '/', options.reverse_merge(:as => :root)
end
......@@ -269,18 +273,18 @@ def match(path, options=nil)
# Mount a Rack-based application to be used within the application.
#
# mount SomeRackApp, :at => "some_route"
# mount SomeRackApp, :at => "some_route"
#
# Alternatively:
#
# mount(SomeRackApp => "some_route")
# mount(SomeRackApp => "some_route")
#
# All mounted applications come with routing helpers to access them.
# These are named after the class specified, so for the above example
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
# To customize this helper's name, use the +:as+ option:
#
# mount(SomeRackApp => "some_route", :as => "exciting")
# mount(SomeRackApp => "some_route", :as => "exciting")
#
# This will generate the +exciting_path+ and +exciting_url+ helpers
# which can be used to navigate to this mounted app.
......@@ -563,7 +567,7 @@ def controller(controller, options={})
# admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
# === Supported options
#
# The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ all default to the name of the namespace.
# The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ options all default to the name of the namespace.
#
# [:path]
# The path prefix for the routes.
......@@ -1085,6 +1089,7 @@ def nested
end
end
# See ActionDispatch::Routing::Mapper::Scoping#namespace
def namespace(path, options = {})
if resource_scope?
nested { super }
......
# == Active Model Lint Tests
#
# You can test whether an object is compliant with the Active Model API by
# including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include
# tests that tell you whether your object is fully compliant, or if not,
# which aspects of the API are not implemented.
#
# These tests do not attempt to determine the semantic correctness of the
# returned values. For instance, you could implement valid? to always
# return true, and the tests would pass. It is up to you to ensure that
# the values are semantically meaningful.
#
# Objects you pass in are expected to return a compliant object from a
# call to to_model. It is perfectly fine for to_model to return self.
module ActiveModel
module Lint
# == Active Model Lint Tests
#
# You can test whether an object is compliant with the Active Model API by
# including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include
# tests that tell you whether your object is fully compliant, or if not,
# which aspects of the API are not implemented.
#
# These tests do not attempt to determine the semantic correctness of the
# returned values. For instance, you could implement valid? to always
# return true, and the tests would pass. It is up to you to ensure that
# the values are semantically meaningful.
#
# Objects you pass in are expected to return a compliant object from a
# call to to_model. It is perfectly fine for to_model to return self.
module Tests
# == Responds to <tt>to_key</tt>
......
......@@ -17,6 +17,23 @@ def test_translated_model_attributes_with_default
assert_equal 'name default attribute', Person.human_attribute_name('name')
end
def test_translated_model_attributes_using_default_option
assert_equal 'name default attribute', Person.human_attribute_name('name', :default => "name default attribute")
end
def test_translated_model_attributes_using_default_option_as_symbol
I18n.backend.store_translations 'en', :default_name => 'name default attribute'
assert_equal 'name default attribute', Person.human_attribute_name('name', :default => :default_name)
end
def test_translated_model_attributes_falling_back_to_default
assert_equal 'Name', Person.human_attribute_name('name')
end
def test_translated_model_attributes_using_default_option_as_symbol_and_falling_back_to_default
assert_equal 'Name', Person.human_attribute_name('name', :default => :default_name)
end
def test_translated_model_attributes_with_symbols
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
assert_equal 'person name attribute', Person.human_attribute_name(:name)
......
......@@ -6,14 +6,17 @@ module ActiveRecord
module Associations
module ClassMethods
class JoinDependency # :nodoc:
attr_reader :join_parts, :reflections, :table_aliases
attr_reader :join_parts, :reflections, :table_aliases, :active_record
def initialize(base, associations, joins)
@table_joins = joins || ''
@active_record = base
@table_joins = joins
@join_parts = [JoinBase.new(base)]
@associations = {}
@reflections = []
@table_aliases = Hash.new(0)
@table_aliases = Hash.new do |h,name|
h[name] = count_aliases_from_table_joins(name)
end
@table_aliases[base.table_name] = 1
build(associations)
end
......@@ -44,12 +47,26 @@ def columns
end
def count_aliases_from_table_joins(name)
return 0 if !@table_joins || Arel::Table === @table_joins
@table_joins.grep(Arel::Nodes::Join).map { |join|
right = join.right
case right
when Arel::Table
right.name.downcase == name ? 1 : 0
when String
count_aliases_from_string(right.downcase, name)
else
0
end
}.sum
end
def count_aliases_from_string(join_sql, name)
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
join_sql = @table_joins.downcase
join_sql.blank? ? 0 :
# Table names
join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
quoted_name = active_record.connection.quote_table_name(name.downcase).downcase
# Table names
join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
# Table aliases
join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
end
......@@ -61,11 +78,11 @@ def instantiate(rows)
records = rows.map { |model|
primary_id = model[primary_key]
parent = parents[primary_id] ||= join_base.instantiate(model)
construct(parent, @associations, join_associations.dup, model)
construct(parent, @associations, join_associations, model)
parent
}.uniq
remove_duplicate_results!(join_base.active_record, records, @associations)
remove_duplicate_results!(active_record, records, @associations)
records
end
......
......@@ -67,8 +67,7 @@ def join_relation(joining_relation)
def table
@table ||= Arel::Table.new(
table_name, :as => aliased_table_name,
:engine => arel_engine, :columns => active_record.columns
table_name, :as => aliased_table_name, :engine => arel_engine
)
end
......@@ -78,23 +77,18 @@ def table
protected
def aliased_table_name_for(name, suffix = nil)
if @join_dependency.table_aliases[name].zero?
@join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
end
aliases = @join_dependency.table_aliases
if !@join_dependency.table_aliases[name].zero? # We need an alias
name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
@join_dependency.table_aliases[name] += 1
if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
# Also need to count the aliases from the table_aliases to avoid incorrect count
@join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
end
table_index = @join_dependency.table_aliases[name]
name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
else
@join_dependency.table_aliases[name] += 1
if aliases[name] != 0 # We need an alias
connection = active_record.connection
name = connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
table_index = aliases[name] + 1
name = name[0, connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
end
aliases[name] += 1
name
end
......
......@@ -13,7 +13,7 @@ def aliased_prefix
end
def table
Arel::Table.new(table_name, :engine => arel_engine, :columns => active_record.columns)
Arel::Table.new(table_name, arel_engine)
end
def aliased_table_name
......
......@@ -1500,9 +1500,7 @@ def attributes=(new_attributes, guard_protected_attributes = true)
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
def attributes
attrs = {}
attribute_names.each { |name| attrs[name] = read_attribute(name) }
attrs
Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
end
# Returns an <tt>#inspect</tt>-like string for the value of the
......
......@@ -196,7 +196,7 @@ def find_with_associations
def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.join_sql)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
end
......
......@@ -162,27 +162,6 @@ def arel
@arel ||= build_arel
end
def custom_join_sql(joins)
arel = table.select_manager
joins.each do |join|
next if join.blank?
@implicit_readonly = true
case join
when Array
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
when String
join = Arel.sql(join)
end
arel.join(join)
end
arel.join_sql
end
def build_arel
arel = table
......@@ -209,6 +188,32 @@ def build_arel
private
def custom_join_ast(table, joins)
joins = joins.reject { |join| join.blank? }
return if joins.empty?
@implicit_readonly = true
joins.map! do |join|
case join
when Array
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
when String
join = Arel.sql(join)
end
join
end
head = table.create_string_join(table, joins.shift)
joins.inject(head) do |ast, join|
ast.right = table.create_string_join(ast.right, join)
end
head
end
def collapse_wheres(arel, wheres)
equalities = wheres.grep(Arel::Nodes::Equality)
......@@ -252,19 +257,31 @@ def build_joins(relation, joins)
stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
non_association_joins = (joins - association_joins - stashed_association_joins)
custom_joins = custom_join_sql(non_association_joins)
join_ast = custom_join_ast(relation, non_association_joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, join_ast)
join_dependency.graft(*stashed_association_joins)
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
# FIXME: refactor this to build an AST
join_dependency.join_associations.each do |association|
relation = association.join_to(relation)
end
relation.join(custom_joins)
if Arel::Table === relation
relation.from(join_ast || relation)
else
if relation.froms.length > 0 && join_ast
join_ast.left = relation.froms.first
relation.from join_ast
elsif relation.froms.length == 0 && join_ast
relation.from(join_ast)
else
relation
end
end
end
def build_select(arel, selects)
......
......@@ -141,7 +141,8 @@ class TransactionError < ActiveRecordError # :nodoc:
# end
# end
#
# User.find(:all) # => empty
# User.find(:all) # => Return both Kotori and Nemu, because inner transaction do not rollback
# # without :requiers_new => true option, and Rollback exception do not reraise
#
# It is also possible to requires a sub-transaction by passing
# <tt>:requires_new => true</tt>. If anything goes wrong, the
......
......@@ -178,49 +178,48 @@ def start(key=nil, object=nil)
# options[0] is the compiled form of supplied conditions
# options[1] is the "end" for the conditional
#
if @kind == :before || @kind == :around
if @kind == :before
# if condition # before_save :filter_name, :if => :condition
# filter_name
# end
filter = <<-RUBY_EVAL
unless halted
result = #{@filter}
halted = (#{chain.config[:terminator]})
end
RUBY_EVAL
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
else
# Compile around filters with conditions into proxy methods
# that contain the conditions.
#
# For `around_save :filter_name, :if => :condition':
#
# def _conditional_callback_save_17
# if condition
# filter_name do
# yield self
# end
# else
# yield self
# end
# end
#
name = "_conditional_callback_#{@kind}_#{next_id}"
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}(halted)
#{@compiled_options[0] || "if true"} && !halted
#{@filter} do
yield self
end
else
case @kind
when :before
# if condition # before_save :filter_name, :if => :condition
# filter_name
# end
filter = <<-RUBY_EVAL
unless halted
result = #{@filter}
halted = (#{chain.config[:terminator]})
end
RUBY_EVAL
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
when :around
# Compile around filters with conditions into proxy methods
# that contain the conditions.
#
# For `around_save :filter_name, :if => :condition':
#
# def _conditional_callback_save_17
# if condition
# filter_name do
# yield self
# end
# else
# yield self
# end
# end
#
name = "_conditional_callback_#{@kind}_#{next_id}"
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}(halted)
#{@compiled_options[0] || "if true"} && !halted
#{@filter} do
yield self
end
else
yield self
end
RUBY_EVAL
"#{name}(halted) do"
end
end
RUBY_EVAL
"#{name}(halted) do"
end
end
......@@ -229,15 +228,14 @@ def #{name}(halted)
def end(key=nil, object=nil)
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
if @kind == :around || @kind == :after
case @kind
when :after
# if condition # after_save :filter_name, :if => :condition
# filter_name
# end
if @kind == :after
[@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
else
"end"
end
[@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
when :around
"end"
end
end
......@@ -388,7 +386,7 @@ module ClassMethods
# key. See #define_callbacks for more information.
#
def __define_runner(symbol) #:nodoc:
body = send("_#{symbol}_callbacks").compile(nil)
body = send("_#{symbol}_callbacks").compile
silence_warnings do
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
......
......@@ -478,8 +478,6 @@ end
Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method +filter+ which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same +filter+ method, which will get run in the same way. The method must +yield+ to execute the action. Alternatively, it can have both a +before+ and an +after+ method that are run before and after the action.
The Rails API documentation has "more information on using filters":http://ap.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html.
h3. Verification
Verifications make sure certain criteria are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the +params+, +session+ or +flash+ hashes or that a certain HTTP method was used or that the request was made using +XMLHttpRequest+ (Ajax). The default action taken when these criteria are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response. It is described in the "API documentation":http://ap.rubyonrails.org/classes/ActionController/Verification/ClassMethods.html as "essentially a special kind of before_filter".
......
......@@ -197,6 +197,8 @@ Examples:
Creates a Post model with a string title, text body, and published flag.
</shell>
NOTE: For a list of available field types, refer to the "API documentation":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column for the column method for the +TableDefinition+ class.
But instead of generating a model directly (which we'll be doing later), let's set up a scaffold. A *scaffold* in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above.
We will set up a simple resource called "HighScore" that will keep track of our highest score on video games we play.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册