提交 99f2cb49 编写于 作者: J Jeremy Kemper

Merge branch 'master' of git@github.com:rails/rails

......@@ -420,12 +420,6 @@ def deliver(mail)
new.deliver!(mail)
end
def register_template_extension(extension)
ActiveSupport::Deprecation.warn(
"ActionMailer::Base.register_template_extension has been deprecated." +
"Use ActionView::Base.register_template_extension instead", caller)
end
def template_root
self.view_paths && self.view_paths.first
end
......
require 'abstract_unit'
class AssetHostMailer < ActionMailer::Base
def email_with_asset(recipient)
recipients recipient
subject "testing email containing asset path while asset_host is set"
from "tester@example.com"
end
end
class AssetHostTest < Test::Unit::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_asset_host_as_string
ActionController::Base.asset_host = "http://www.example.com"
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
end
def test_asset_host_as_one_arguement_proc
ActionController::Base.asset_host = Proc.new { |source|
if source.starts_with?('/images')
"http://images.example.com"
else
"http://assets.example.com"
end
}
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.strip
end
def test_asset_host_as_two_arguement_proc
ActionController::Base.asset_host = Proc.new {|source,request|
if request && request.ssl?
"https://www.example.com"
else
"http://www.example.com"
end
}
mail = nil
assert_nothing_raised { mail = AssetHostMailer.deliver_email_with_asset(@recipient) }
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
end
end
\ No newline at end of file
<%= image_tag "somelogo.png" %>
\ No newline at end of file
*2.3.0 [Edge]*
* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd]
* Deprecated formatted_polymorphic_url. [Jeremy Kemper]
* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [DHH]
......
......@@ -57,6 +57,7 @@ def self.load_all!
autoload :Integration, 'action_controller/integration'
autoload :IntegrationTest, 'action_controller/integration'
autoload :Layout, 'action_controller/layout'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RackRequest, 'action_controller/rack_process'
......
......@@ -327,6 +327,10 @@ class Base
# sets it to <tt>:authenticity_token</tt> by default.
cattr_accessor :request_forgery_protection_token
# Controls the IP Spoofing check when determining the remote IP.
@@ip_spoofing_check = true
cattr_accessor :ip_spoofing_check
# Indicates whether or not optimise the generated named
# route helper methods
cattr_accessor :optimise_named_routes
......
......@@ -85,6 +85,9 @@ def failsafe_logger
end
end
cattr_accessor :middleware
self.middleware = MiddlewareStack.new
cattr_accessor :error_file_path
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
......@@ -93,6 +96,7 @@ def failsafe_logger
def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
@app = @@middleware.build(lambda { |env| self._call(env) })
end
def dispatch_unlocked
......@@ -127,6 +131,10 @@ def dispatch_cgi(cgi, session_options)
end
def call(env)
@app.call(env)
end
def _call(env)
@request = RackRequest.new(env)
@response = RackResponse.new(@request)
dispatch
......
module ActionController
class MiddlewareStack < Array
class Middleware
attr_reader :klass, :args, :block
def initialize(klass, *args, &block)
@klass = klass.is_a?(Class) ? klass : klass.to_s.constantize
@args = args
@block = block
end
def ==(middleware)
case middleware
when Middleware
klass == middleware.klass
when Class
klass == middleware
else
klass == middleware.to_s.constantize
end
end
def inspect
str = @klass.to_s
@args.each { |arg| str += ", #{arg.inspect}" }
str
end
def build(app)
klass.new(app, *args, &block)
end
end
def use(*args, &block)
push(Middleware.new(*args, &block))
end
def build(app)
reverse.inject(app) { |a, e| e.build(a) }
end
end
end
......@@ -218,7 +218,7 @@ def remote_ip
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
if @env.include? 'HTTP_CLIENT_IP'
if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
# We don't know which came from the proxy, and which from the user
raise ActionControllerError.new(<<EOM)
IP spoofing attack?!
......
......@@ -574,7 +574,7 @@ def mtime
private
def request
@controller.request
request? && @controller.request
end
def request?
......
......@@ -370,8 +370,8 @@ def auto_link(text, *args, &block)#link = :all, href_options = {}, &block)
options.reverse_merge!(:link => :all, :html => {})
case options[:link].to_sym
when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), &block)
when :email_addresses then auto_link_email_addresses(text, &block)
when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), options[:html], &block)
when :email_addresses then auto_link_email_addresses(text, options[:html], &block)
when :urls then auto_link_urls(text, options[:html], &block)
end
end
......@@ -559,7 +559,7 @@ def auto_link_urls(text, html_options = {})
# Turns all email addresses into clickable links. If a block is given,
# each email is yielded and the result is used as the link text.
def auto_link_email_addresses(text)
def auto_link_email_addresses(text, html_options = {})
body = text.dup
text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
text = $1
......@@ -568,7 +568,7 @@ def auto_link_email_addresses(text)
text
else
display_text = (block_given?) ? yield(text) : text
%{<a href="mailto:#{text}">#{display_text}</a>}
mail_to text, display_text, html_options
end
end
end
......
......@@ -66,6 +66,15 @@ def test_remote_ip
assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message
assert_match /HTTP_CLIENT_IP="8.8.8.8"/, e.message
# turn IP Spoofing detection off.
# This is useful for sites that are aimed at non-IP clients. The typical
# example is WAP. Since the cellular network is not IP based, it's a
# leap of faith to assume that their proxies are ever going to set the
# HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
ActionController::Base.ip_spoofing_check = false
assert_equal('8.8.8.8', @request.remote_ip(true))
ActionController::Base.ip_spoofing_check = true
@request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9'
assert_equal '8.8.8.8', @request.remote_ip(true)
......
......@@ -262,6 +262,11 @@ def test_auto_linking
email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>}
assert_equal email2_result, auto_link(email2_raw)
email3_raw = '+david@loudthinking.com'
email3_result = %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;+%64%61%76%69%64@%6c%6f%75%64%74%68%69%6e%6b%69%6e%67.%63%6f%6d">#{email3_raw}</a>}
assert_equal email3_result, auto_link(email3_raw, :all, :encode => :hex)
assert_equal email3_result, auto_link(email3_raw, :email_addresses, :encode => :hex)
link2_raw = 'www.rubyonrails.com'
link2_result = generate_result(link2_raw, "http://#{link2_raw}")
assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls)
......@@ -362,7 +367,7 @@ def test_auto_link_with_block
end
def test_auto_link_with_options_hash
assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.',
assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com" class="menu" target="_blank">me@email.com</a>.',
auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.",
:link => :all, :html => { :class => "menu", :target => "_blank" })
end
......
*2.3.0/3.0*
* Add :having as a key to find and the relevant associations. [miloops]
* Added default_scope to Base #1381 [Paweł Kondzior]. Example:
class Person < ActiveRecord::Base
......
......@@ -185,7 +185,7 @@ def preload_has_and_belongs_to_many_association(records, reflection, preload_opt
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
:order => options[:order])
......
......@@ -724,6 +724,8 @@ module ClassMethods
# Specify second-order associations that should be eager loaded when the collection is loaded.
# [:group]
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# [:having]
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# [:limit]
# An integer determining the limit on the number of rows that should be returned.
# [:offset]
......@@ -1181,6 +1183,8 @@ def belongs_to(association_id, options = {})
# Specify second-order associations that should be eager loaded when the collection is loaded.
# [:group]
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# [:having]
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# [:limit]
# An integer determining the limit on the number of rows that should be returned.
# [:offset]
......@@ -1553,7 +1557,7 @@ def nullify_has_many_dependencies(record, reflection_name, association_class, pr
@@valid_keys_for_has_many_association = [
:class_name, :table_name, :foreign_key, :primary_key,
:dependent,
:select, :conditions, :include, :order, :group, :limit, :offset,
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
:as, :through, :source, :source_type,
:uniq,
:finder_sql, :counter_sql,
......@@ -1609,7 +1613,7 @@ def create_belongs_to_reflection(association_id, options)
mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
@@valid_keys_for_has_and_belongs_to_many_association = [
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
:select, :conditions, :include, :order, :group, :limit, :offset,
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
:uniq,
:finder_sql, :counter_sql, :delete_sql, :insert_sql,
:before_add, :after_add, :before_remove, :after_remove,
......@@ -1658,7 +1662,7 @@ def construct_finder_sql_with_included_associations(options, join_dependency)
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
add_group!(sql, options[:group], scope)
add_group!(sql, options[:group], options[:having], scope)
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
add_lock!(sql, options, scope)
......@@ -1714,7 +1718,7 @@ def construct_finder_sql_for_association_limiting(options, join_dependency)
end
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
add_group!(sql, options[:group], options[:having], scope)
if order && is_distinct
connection.add_order_by_for_association_limiting!(sql, :order => order)
......
......@@ -188,6 +188,7 @@ def set_belongs_to_association_for(record)
def merge_options_from_reflection!(options)
options.reverse_merge!(
:group => @reflection.options[:group],
:having => @reflection.options[:having],
:limit => @reflection.options[:limit],
:offset => @reflection.options[:offset],
:joins => @reflection.options[:joins],
......
......@@ -521,6 +521,7 @@ class << self # Class methods
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
......@@ -1632,7 +1633,7 @@ def construct_finder_sql(options)
add_joins!(sql, options[:joins], scope)
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
add_group!(sql, options[:group], options[:having], scope)
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
......@@ -1688,13 +1689,15 @@ def add_order!(sql, order, scope = :auto)
end
end
def add_group!(sql, group, scope = :auto)
def add_group!(sql, group, having, scope = :auto)
if group
sql << " GROUP BY #{group}"
sql << " HAVING #{having}" if having
else
scope = scope(:find) if :auto == scope
if scope && (scoped_group = scope[:group])
sql << " GROUP BY #{scoped_group}"
sql << " HAVING #{scoped_having}" if (scoped_having = scope[:having])
end
end
end
......@@ -2259,7 +2262,7 @@ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
end
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
:order, :select, :readonly, :group, :from, :lock ]
:order, :select, :readonly, :group, :having, :from, :lock ]
def validate_find_options(options) #:nodoc:
options.assert_valid_keys(VALID_FIND_OPTIONS)
......
......@@ -658,6 +658,11 @@ def test_find_scoped_grouped
assert_equal 1, categories(:technology).posts_gruoped_by_title.size
end
def test_find_scoped_grouped_having
assert_equal 2, projects(:active_record).well_payed_salary_groups.size
assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 }
end
def test_get_ids
assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort
assert_equal [projects(:active_record).id], developers(:jamis).project_ids
......
......@@ -255,6 +255,11 @@ def test_find_scoped_grouped
assert_equal 2, companies(:first_firm).clients_grouped_by_name.length
end
def test_find_scoped_grouped_having
assert_equal 1, authors(:david).popular_grouped_posts.length
assert_equal 0, authors(:mary).popular_grouped_posts.length
end
def test_adding
force_signal37_to_load_all_clients_of_firm
natural = Client.new("name" => "Natural Company")
......
......@@ -175,6 +175,13 @@ def test_find_with_group
assert_equal 4, developers.map(&:salary).uniq.size
end
def test_find_with_group_and_having
developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary")
assert_equal 3, developers.size
assert_equal 3, developers.map(&:salary).uniq.size
assert developers.all? { |developer| developer.salary > 10000 }
end
def test_find_with_entire_select_statement
topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
......
class Author < ActiveRecord::Base
has_many :posts
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type"
has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
has_many :posts_sorted_by_id_limited, :class_name => "Post", :order => 'posts.id', :limit => 1
has_many :posts_with_categories, :include => :categories, :class_name => "Post"
......
......@@ -14,6 +14,7 @@ class Category < ActiveRecord::Base
:class_name => 'Post',
:conditions => { :title => 'Yet Another Testing Title' }
has_and_belongs_to_many :popular_grouped_posts, :class_name => "Post", :group => "posts.type", :having => "sum(comments.post_id) > 2", :include => :comments
has_and_belongs_to_many :posts_gruoped_by_title, :class_name => "Post", :group => "title", :select => "title"
def self.what_are_you
......
......@@ -13,6 +13,7 @@ class Project < ActiveRecord::Base
:after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"},
:before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
:after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}
has_and_belongs_to_many :well_payed_salary_groups, :class_name => "Developer", :group => "salary", :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary"
attr_accessor :developers_log
......
......@@ -704,6 +704,7 @@ def clone
def new?
id.nil?
end
alias :new_record? :new?
# Gets the <tt>\id</tt> attribute of the resource.
def id
......
*2.3.0 [Edge]*
* Enhanced Rails.root to take parameters that'll be join with the root, like Rails.root('app', 'controllers') => File.join(Rails.root, 'app', 'controllers') #1482 [Damian Janowski]
* Add "-m/--template" option to Rails generator to apply a template to the generated application. [Jeremy McAnally]
This has been extracted from rg - http://github.com/jeremymcanally/rg
Example:
# template.rb
# Install plugins from git or svn
plugin "will-paginate", :git => "git://github.com/mislav/will_paginate.git"
plugin "old-restful-auth", :svn => "http://svn.techno-weenie.net/projects/plugins/restful_authentication/"
# Add gems to environment.rb
gem "jeremymcanally-context"
gem "bluecloth"
# Vendor file. Data in a string or...
vendor("borrowed.rb", <<CODE
def helpful_method
do_something_helpful_here
end
CODE
# ...file data from block return value.
# #initializer creates a new initializer file
initializer("crypto.rb") do
salt = "--#{Time.now}--#{rand}--#{srand(Time.now.to_i)}"
"SPECIAL_SALT = '#{salt}'"
end
Usage:
To use a template, provide a file path or URL:
1. Using a local file :
rails <application name> -m /path/to/my/template.rb
2. Or directly from a URL :
rails <application name> --template=http://gist.github.com/31208.txt
* Extracted the process scripts (inspector, reaper, spawner) into the plugin irs_process_scripts [DHH]
* Changed Rails.root to return a Pathname object (allows for Rails.root.join('app', 'controllers') => "#{RAILS_ROOT}/app/controllers") #1482 [Damian Janowski/?]
* Added view path support for engines [DHH]
......
......@@ -53,7 +53,6 @@ BASE_DIRS = %w(
public
script
script/performance
script/process
test
vendor
vendor/plugins
......@@ -71,7 +70,7 @@ LOG_FILES = %w( server.log development.log test.log production.log )
HTML_FILES = %w( 422.html 404.html 500.html index.html robots.txt favicon.ico images/rails.png
javascripts/prototype.js javascripts/application.js
javascripts/effects.js javascripts/dragdrop.js javascripts/controls.js )
BIN_FILES = %w( about console destroy generate performance/benchmarker performance/profiler process/reaper process/spawner process/inspector runner server plugin )
BIN_FILES = %w( about console destroy generate performance/benchmarker performance/profiler runner server plugin )
VENDOR_LIBS = %w( actionpack activerecord actionmailer activesupport activeresource railties )
......@@ -174,9 +173,6 @@ task :copy_dispatches do
copy_with_rewritten_ruby_path("dispatches/dispatch.fcgi", "#{PKG_DESTINATION}/public/dispatch.fcgi")
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.fcgi"
# copy_with_rewritten_ruby_path("dispatches/gateway.cgi", "#{PKG_DESTINATION}/public/gateway.cgi")
# chmod 0755, "#{PKG_DESTINATION}/public/gateway.cgi"
end
task :copy_html_files do
......
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/process/inspector'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/process/reaper'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/process/spawner'
......@@ -8,6 +8,7 @@ if %w(--version -v).include? ARGV.first
end
freeze = ARGV.any? { |option| %w(--freeze -f).include?(option) }
app_path = ARGV.first
require File.dirname(__FILE__) + '/../lib/rails_generator'
......
# Be sure to restart your server when you modify this file.
# These settings change the behavior of Rails 2 apps and will be defaults
# for Rails 3. You can remove this initializer when Rails 3 is released.
......
# Be sure to restart your server when you modify this file.
# 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 = {
:session_key => '_<%= app_name %>_session',
:secret => '<%= app_secret %>'
}
# 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
# Rackup Configuration
#
# Start Rails mongrel server with rackup
# $ rackup -p 3000 config.ru
#
# Start server with webrick (or any compatible Rack server) instead
# $ rackup -p 3000 -s webrick config.ru
# Rack Dispatcher
# Require your environment file to bootstrap Rails
require File.dirname(__FILE__) + '/config/environment'
# Static server middleware
# You can remove this extra check if you use an asset server
use Rails::Rack::Static
# Dispatch the request
run ActionController::Dispatcher.new
# Be sure to restart your server when you modify this file
# Uncomment below to force Rails into production mode when
# you don't control web/app server and can't set it the proper way
# ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
<%= '# ' if freeze %>RAILS_GEM_VERSION = '<%= Rails::VERSION::STRING %>' unless defined? RAILS_GEM_VERSION
......@@ -14,62 +10,32 @@
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# See Rails::Configuration for more options.
# Skip frameworks you're not going to use. To use Rails without a database
# you must remove the Active Record framework.
# config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{RAILS_ROOT}/extras )
# Specify gems that this application depends on.
# They can then be installed with "rake gems:install" on new installations.
# You have to specify the :lib option for libraries, where the Gem name (sqlite3-ruby) differs from the file itself (sqlite3)
# Specify gems that this application depends on and have them installed with rake gems:install
# config.gem "bj"
# config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
# config.gem "sqlite3-ruby", :lib => "sqlite3"
# config.gem "aws-s3", :lib => "aws/s3"
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
# 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
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{RAILS_ROOT}/extras )
# Skip frameworks you're not going to use. To use Rails without a database,
# you must remove the Active Record framework.
# config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
# Force all environments to use the same logger level
# (by default production uses :info, the others :debug)
# config.log_level = :debug
# Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Make Time.zone default to the specified zone, and make Active Record store time values
# in the database in UTC, and return them converted to the specified local zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Comment line to use default local time.
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names.
config.time_zone = 'UTC'
# The internationalization framework can be changed to have another default locale (standard is :en) or more load paths.
# All files from config/locales/*.rb,yml are added automatically.
# config.i18n.load_path << Dir[File.join(RAILS_ROOT, 'my', 'locales', '*.{rb,yml}')]
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
# config.i18n.default_locale = :de
# 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.
config.action_controller.session = {
:session_key => '_<%= app_name %>_session',
:secret => '<%= app_secret %>'
}
# 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")
# config.action_controller.session_store = :active_record_store
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# Activate observers that should always be running
# Please note that observers generated using script/generate observer need to have an _observer suffix
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
end
end
\ No newline at end of file
......@@ -4,21 +4,24 @@
# Code is not reloaded between requests
config.cache_classes = true
# Enable threaded mode
# config.threadsafe!
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
# Full error reports are disabled and caching is turned on
config.action_controller.consider_all_requests_local = false
config.action_controller.perform_caching = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and javascripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# config.action_controller.asset_host = "http://assets.example.com"
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!
\ No newline at end of file
......@@ -20,3 +20,8 @@
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
\ No newline at end of file
......@@ -3,12 +3,8 @@
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
# See ActionController::RequestForgeryProtection for details
protect_from_forgery
# See ActionController::Base for details
# Uncomment this to filter the contents of submitted sensitive data parameters
# from your application log (in this case, all fields with names like "password").
# Scrub sensitive parameters from your log
# filter_parameter_logging :password
end
require 'optparse'
if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Inspector is only for Unix") end
OPTIONS = {
:pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'),
:pattern => "dispatch.*.pid",
:ps => "ps -o pid,state,user,start,time,pcpu,vsz,majflt,command -p %s"
}
class Inspector
def self.inspect(pid_path, pattern)
new(pid_path, pattern).inspect
end
def initialize(pid_path, pattern)
@pid_path, @pattern = pid_path, pattern
end
def inspect
header = `#{OPTIONS[:ps] % 1}`.split("\n")[0] + "\n"
lines = pids.collect { |pid| `#{OPTIONS[:ps] % pid}`.split("\n")[1] }
puts(header + lines.join("\n"))
end
private
def pids
pid_files.collect do |pid_file|
File.read(pid_file).to_i
end
end
def pid_files
Dir.glob(@pid_path + "/" + @pattern)
end
end
ARGV.options do |opts|
opts.banner = "Usage: inspector [options]"
opts.separator ""
opts.on <<-EOF
Description:
Displays system information about Rails dispatchers (or other processes that use pid files) through
the ps command.
Examples:
inspector # default ps on all tmp/pids/dispatch.*.pid files
inspector -s 'ps -o user,start,majflt,pcpu,vsz -p %s' # custom ps, %s is where the pid is interleaved
EOF
opts.on(" Options:")
opts.on("-s", "--ps=command", "default: #{OPTIONS[:ps]}", String) { |v| OPTIONS[:ps] = v }
opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v }
opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v }
opts.separator ""
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
opts.parse!
end
Inspector.inspect(OPTIONS[:pid_path], OPTIONS[:pattern])
require 'optparse'
require 'net/http'
require 'uri'
if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Reaper is only for Unix") end
class Killer
class << self
# Searches for all processes matching the given keywords, and then invokes
# a specific action on each of them. This is useful for (e.g.) reloading a
# set of processes:
#
# Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid")
def process(action, pid_path, pattern, keyword)
new(pid_path, pattern, keyword).process(action)
end
# Forces the (rails) application to reload by sending a +HUP+ signal to the
# process.
def reload(pid)
`kill -s HUP #{pid}`
end
# Force the (rails) application to restart by sending a +USR2+ signal to the
# process.
def restart(pid)
`kill -s USR2 #{pid}`
end
# Forces the (rails) application to gracefully terminate by sending a
# +TERM+ signal to the process.
def graceful(pid)
`kill -s TERM #{pid}`
end
# Forces the (rails) application to terminate immediately by sending a -9
# signal to the process.
def kill(pid)
`kill -9 #{pid}`
end
# Send a +USR1+ signal to the process.
def usr1(pid)
`kill -s USR1 #{pid}`
end
end
def initialize(pid_path, pattern, keyword=nil)
@pid_path, @pattern, @keyword = pid_path, pattern, keyword
end
def process(action)
pids = find_processes
if pids.empty?
warn "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'"
warn "(also looked for processes matching #{@keyword.inspect})" if @keyword
else
pids.each do |pid|
puts "#{action.capitalize}ing #{pid}"
self.class.send(action, pid)
end
delete_pid_files if terminating?(action)
end
end
private
def terminating?(action)
[ "kill", "graceful" ].include?(action)
end
def find_processes
files = pid_files
if files.empty?
find_processes_via_grep
else
files.collect { |pid_file| File.read(pid_file).to_i }
end
end
def find_processes_via_grep
lines = `ps axww -o 'pid command' | grep #{@keyword}`.split(/\n/).
reject { |line| line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/ }
lines.map { |line| line[/^\s*(\d+)/, 1].to_i }
end
def delete_pid_files
pid_files.each { |pid_file| File.delete(pid_file) }
end
def pid_files
Dir.glob(@pid_path + "/" + @pattern)
end
end
OPTIONS = {
:action => "restart",
:pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'),
:pattern => "dispatch.[0-9]*.pid",
:dispatcher => File.expand_path("#{RAILS_ROOT}/public/dispatch.fcgi")
}
ARGV.options do |opts|
opts.banner = "Usage: reaper [options]"
opts.separator ""
opts.on <<-EOF
Description:
The reaper is used to restart, reload, gracefully exit, and forcefully exit processes
running a Rails Dispatcher (or any other process responding to the same signals). This
is commonly done when a new version of the application is available, so the existing
processes can be updated to use the latest code.
It uses pid files to work on the processes and by default assume them to be located
in RAILS_ROOT/tmp/pids.
The reaper actions are:
* restart : Restarts the application by reloading both application and framework code
* reload : Only reloads the application, but not the framework (like the development environment)
* graceful: Marks all of the processes for exit after the next request
* kill : Forcefully exists all processes regardless of whether they're currently serving a request
Restart is the most common and default action.
Examples:
reaper # restarts the default dispatchers
reaper -a reload # reload the default dispatchers
reaper -a kill -r *.pid # kill all processes that keep pids in tmp/pids
EOF
opts.on(" Options:")
opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String) { |v| OPTIONS[:action] = v }
opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v }
opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v }
opts.on("-d", "--dispatcher=path", "DEPRECATED. default: #{OPTIONS[:dispatcher]}", String) { |v| OPTIONS[:dispatcher] = v }
opts.separator ""
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
opts.parse!
end
Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern], OPTIONS[:dispatcher])
require 'active_support'
require 'optparse'
require 'socket'
require 'fileutils'
def daemonize #:nodoc:
exit if fork # Parent exits, child continues.
Process.setsid # Become session leader.
exit if fork # Zap session leader. See [1].
Dir.chdir "/" # Release old working directory.
File.umask 0000 # Ensure sensible umask. Adjust as needed.
STDIN.reopen "/dev/null" # Free file descriptors and
STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile.
end
class Spawner
def self.record_pid(name = "#{OPTIONS[:process]}.spawner", id = Process.pid)
FileUtils.mkdir_p(OPTIONS[:pids])
File.open(File.expand_path(OPTIONS[:pids] + "/#{name}.pid"), "w+") { |f| f.write(id) }
end
def self.spawn_all
OPTIONS[:instances].times do |i|
port = OPTIONS[:port] + i
print "Checking if something is already running on #{OPTIONS[:address]}:#{port}..."
begin
srv = TCPServer.new(OPTIONS[:address], port)
srv.close
srv = nil
puts "NO"
puts "Starting dispatcher on port: #{OPTIONS[:address]}:#{port}"
FileUtils.mkdir_p(OPTIONS[:pids])
spawn(port)
rescue
puts "YES"
end
end
end
end
class FcgiSpawner < Spawner
def self.spawn(port)
cmd = "#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port} -P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid"
cmd << " -a #{OPTIONS[:address]}" if can_bind_to_custom_address?
system(cmd)
end
def self.can_bind_to_custom_address?
@@can_bind_to_custom_address ||= /^\s-a\s/.match `#{OPTIONS[:spawner]} -h`
end
end
class MongrelSpawner < Spawner
def self.spawn(port)
cmd =
"mongrel_rails start -d " +
"-a #{OPTIONS[:address]} " +
"-p #{port} " +
"-P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid " +
"-e #{OPTIONS[:environment]} " +
"-c #{OPTIONS[:rails_root]} " +
"-l #{OPTIONS[:rails_root]}/log/mongrel.log"
# Add prefix functionality to spawner's call to mongrel_rails
# Digging through mongrel's project subversion server, the earliest
# Tag that has prefix implemented in the bin/mongrel_rails file
# is 0.3.15 which also happens to be the earliest tag listed.
# References: http://mongrel.rubyforge.org/svn/tags
if Mongrel::Const::MONGREL_VERSION.to_f >=0.3 && !OPTIONS[:prefix].nil?
cmd = cmd + " --prefix #{OPTIONS[:prefix]}"
end
system(cmd)
end
def self.can_bind_to_custom_address?
true
end
end
begin
require_library_or_gem 'fcgi'
rescue Exception
# FCGI not available
end
begin
require_library_or_gem 'mongrel'
rescue Exception
# Mongrel not available
end
server = case ARGV.first
when "fcgi", "mongrel"
ARGV.shift
else
if defined?(Mongrel)
"mongrel"
elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `spawn-fcgi -version` }.blank? && defined?(FCGI)
"fcgi"
end
end
case server
when "fcgi"
puts "=> Starting FCGI dispatchers"
spawner_class = FcgiSpawner
when "mongrel"
puts "=> Starting mongrel dispatchers"
spawner_class = MongrelSpawner
else
puts "Neither FCGI (spawn-fcgi) nor Mongrel was installed and available!"
exit(0)
end
OPTIONS = {
:environment => "production",
:spawner => '/usr/bin/env spawn-fcgi',
:dispatcher => File.expand_path(RELATIVE_RAILS_ROOT + '/public/dispatch.fcgi'),
:pids => File.expand_path(RELATIVE_RAILS_ROOT + "/tmp/pids"),
:rails_root => File.expand_path(RELATIVE_RAILS_ROOT),
:process => "dispatch",
:port => 8000,
:address => '0.0.0.0',
:instances => 3,
:repeat => nil,
:prefix => nil
}
ARGV.options do |opts|
opts.banner = "Usage: spawner [platform] [options]"
opts.separator ""
opts.on <<-EOF
Description:
The spawner is a wrapper for spawn-fcgi and mongrel that makes it
easier to start multiple processes running the Rails dispatcher. The
spawn-fcgi command is included with the lighttpd web server, but can
be used with both Apache and lighttpd (and any other web server
supporting externally managed FCGI processes). Mongrel automatically
ships with with mongrel_rails for starting dispatchers.
The first choice you need to make is whether to spawn the Rails
dispatchers as FCGI or Mongrel. By default, this spawner will prefer
Mongrel, so if that's installed, and no platform choice is made,
Mongrel is used.
Then decide a starting port (default is 8000) and the number of FCGI
process instances you'd like to run. So if you pick 9100 and 3
instances, you'll start processes on 9100, 9101, and 9102.
By setting the repeat option, you get a protection loop, which will
attempt to restart any FCGI processes that might have been exited or
outright crashed.
You can select bind address for started processes. By default these
listen on every interface. For single machine installations you would
probably want to use 127.0.0.1, hiding them form the outside world.
Examples:
spawner # starts instances on 8000, 8001, and 8002
# using Mongrel if available.
spawner fcgi # starts instances on 8000, 8001, and 8002
# using FCGI.
spawner mongrel -i 5 # starts instances on 8000, 8001, 8002,
# 8003, and 8004 using Mongrel.
spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to
# 9109 using Mongrel if available.
spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to
# 9102 and attempts start them every 5
# seconds.
spawner -a 127.0.0.1 # starts 3 instances binding to localhost
EOF
opts.on(" Options:")
opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v }
if spawner_class.can_bind_to_custom_address?
opts.on("-a", "--address=ip", String, "Bind to IP address (default: #{OPTIONS[:address]})") { |v| OPTIONS[:address] = v }
end
opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v }
opts.on("-i", "--instances=number", Integer, "Number of instances (default: #{OPTIONS[:instances]})") { |v| OPTIONS[:instances] = v }
opts.on("-r", "--repeat=seconds", Integer, "Repeat spawn attempts every n seconds (default: off)") { |v| OPTIONS[:repeat] = v }
opts.on("-e", "--environment=name", String, "test|development|production (default: #{OPTIONS[:environment]})") { |v| OPTIONS[:environment] = v }
opts.on("-P", "--prefix=path", String, "URL prefix for Rails app. [Used only with Mongrel > v0.3.15]: (default: #{OPTIONS[:prefix]})") { |v| OPTIONS[:prefix] = v }
opts.on("-n", "--process=name", String, "default: #{OPTIONS[:process]}") { |v| OPTIONS[:process] = v }
opts.on("-s", "--spawner=path", String, "default: #{OPTIONS[:spawner]}") { |v| OPTIONS[:spawner] = v }
opts.on("-d", "--dispatcher=path", String, "default: #{OPTIONS[:dispatcher]}") { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) }
opts.separator ""
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
opts.parse!
end
ENV["RAILS_ENV"] = OPTIONS[:environment]
if OPTIONS[:repeat]
daemonize
trap("TERM") { exit }
spawner_class.record_pid
loop do
spawner_class.spawn_all
sleep(OPTIONS[:repeat])
end
else
spawner_class.spawn_all
end
require 'optparse'
def daemonize #:nodoc:
exit if fork # Parent exits, child continues.
Process.setsid # Become session leader.
exit if fork # Zap session leader. See [1].
Dir.chdir "/" # Release old working directory.
File.umask 0000 # Ensure sensible umask. Adjust as needed.
STDIN.reopen "/dev/null" # Free file descriptors and
STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile.
end
OPTIONS = {
:interval => 5.0,
:command => File.expand_path(RAILS_ROOT + '/script/process/spawner'),
:daemon => false
}
ARGV.options do |opts|
opts.banner = "Usage: spinner [options]"
opts.separator ""
opts.on <<-EOF
Description:
The spinner is a protection loop for the spawner, which will attempt to restart any FCGI processes
that might have been exited or outright crashed. It's a brute-force attempt that'll just try
to run the spawner every X number of seconds, so it does pose a light load on the server.
Examples:
spinner # attempts to run the spawner with default settings every second with output on the terminal
spinner -i 3 -d # only run the spawner every 3 seconds and detach from the terminal to become a daemon
spinner -c '/path/to/app/script/process/spawner -p 9000 -i 10' -d # using custom spawner
EOF
opts.on(" Options:")
opts.on("-c", "--command=path", String) { |v| OPTIONS[:command] = v }
opts.on("-i", "--interval=seconds", Float) { |v| OPTIONS[:interval] = v }
opts.on("-d", "--daemon") { |v| OPTIONS[:daemon] = v }
opts.separator ""
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
opts.parse!
end
daemonize if OPTIONS[:daemon]
trap(OPTIONS[:daemon] ? "TERM" : "INT") { exit }
loop do
system(OPTIONS[:command])
sleep(OPTIONS[:interval])
end
\ No newline at end of file
......@@ -65,7 +65,6 @@
ENV["RAILS_ENV"] = options[:environment]
RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
require RAILS_ROOT + "/config/environment"
if File.exist?(options[:config])
config = options[:config]
......@@ -74,20 +73,23 @@
if cfgfile[/^#\\(.*)/]
opts.parse!($1.split(/\s+/))
end
app = eval("Rack::Builder.new {( " + cfgfile + "\n )}.to_app", nil, config)
inner_app = eval("Rack::Builder.new {( " + cfgfile + "\n )}.to_app", nil, config)
else
require config
app = Object.const_get(File.basename(config, '.rb').capitalize)
inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
end
else
app = Rack::Builder.new {
use Rails::Rack::Logger
use Rails::Rack::Static
use Rails::Rack::Debugger if options[:debugger]
run ActionController::Dispatcher.new
}.to_app
require RAILS_ROOT + "/config/environment"
inner_app = ActionController::Dispatcher.new
end
app = Rack::Builder.new {
use Rails::Rack::Logger
use Rails::Rack::Static
use Rails::Rack::Debugger if options[:debugger]
run inner_app
}.to_app
puts "=> Call with -d to detach"
trap(:INT) { exit }
......
......@@ -98,7 +98,7 @@ def process_request(cgi)
with_signal_handler 'USR1' do
begin
Dispatcher.dispatch(cgi)
::Rack::Handler::FastCGI.serve(cgi, Dispatcher.new)
rescue SignalException, SystemExit
raise
rescue Exception => error
......
......@@ -48,8 +48,8 @@ def backtrace_cleaner
end
end
def root(*args)
File.join(RAILS_ROOT, *args.compact) if defined?(RAILS_ROOT)
def root
Pathname.new(RAILS_ROOT) if defined?(RAILS_ROOT)
end
def env
......@@ -513,10 +513,15 @@ def initialize_temporary_session_directory
def initialize_time_zone
if configuration.time_zone
zone_default = Time.__send__(:get_zone, configuration.time_zone)
unless zone_default
raise %{Value assigned to config.time_zone not recognized. Run "rake -D time" for a list of tasks for finding appropriate time zone names.}
raise \
'Value assigned to config.time_zone not recognized.' +
'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
end
Time.zone_default = zone_default
if configuration.frameworks.include?(:active_record)
ActiveRecord::Base.time_zone_aware_attributes = true
ActiveRecord::Base.default_timezone = :utc
......@@ -876,6 +881,11 @@ def to_prepare(&callback)
end
end
def middleware
require 'action_controller'
ActionController::Dispatcher.middleware
end
def builtin_directories
# Include builtins only in the development environment.
(environment == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : []
......
......@@ -74,6 +74,7 @@ def add_load_paths
def dependencies
return [] if framework_gem?
return [] if specification.nil?
all_dependencies = specification.dependencies.map do |dependency|
GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
end
......
......@@ -154,6 +154,9 @@ def destination_path(relative_destination)
File.join(destination_root, relative_destination)
end
def after_generate
end
protected
# Convenience method for generator subclasses to record a manifest.
def record
......
......@@ -40,6 +40,7 @@ class Base < DelegateClass(Rails::Generator::Base)
# Replay action manifest. RewindBase subclass rewinds manifest.
def invoke!
manifest.replay(self)
after_generate
end
def dependency(generator_name, args, runtime_options = {})
......
require 'rbconfig'
require File.dirname(__FILE__) + '/template_runner'
require 'digest/md5'
require 'active_support/secure_random'
class AppGenerator < Rails::Generator::Base
DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
Config::CONFIG['ruby_install_name'])
DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
DATABASES = %w(mysql oracle postgresql sqlite2 sqlite3 frontbase ibm_db)
DATABASES = %w( mysql oracle postgresql sqlite2 sqlite3 frontbase ibm_db )
DEFAULT_DATABASE = 'sqlite3'
mandatory_options :source => "#{File.dirname(__FILE__)}/../../../../.."
default_options :db => (ENV["RAILS_DEFAULT_DATABASE"] || DEFAULT_DATABASE),
:shebang => DEFAULT_SHEBANG, :with_dispatchers => false, :freeze => false
mandatory_options :source => "#{File.dirname(__FILE__)}/../../../../.."
def initialize(runtime_args, runtime_options = {})
super
usage if args.empty?
usage("Databases supported for preconfiguration are: #{DATABASES.join(", ")}") if (options[:db] && !DATABASES.include?(options[:db]))
@destination_root = args.shift
@app_name = File.basename(File.expand_path(@destination_root))
end
def manifest
# Use /usr/bin/env if no special shebang was specified
script_options = { :chmod => 0755, :shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang] }
dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] }
# duplicate CGI::Session#generate_unique_id
md5 = Digest::MD5.new
now = Time.now
md5 << now.to_s
md5 << String(now.usec)
md5 << String(rand(0))
md5 << String($$)
md5 << @app_name
# Do our best to generate a secure secret key for CookieStore
secret = ActiveSupport::SecureRandom.hex(64)
record do |m|
# Root directory and all subdirectories.
m.directory ''
BASEDIRS.each { |path| m.directory path }
# Root
m.file "fresh_rakefile", "Rakefile"
m.file "README", "README"
# Application
m.template "helpers/application_controller.rb", "app/controllers/application_controller.rb", :assigns => {
:app_name => @app_name, :app_secret => md5.hexdigest }
m.template "helpers/application_helper.rb", "app/helpers/application_helper.rb"
m.template "helpers/test_helper.rb", "test/test_helper.rb"
m.template "helpers/performance_test.rb", "test/performance/browsing_test.rb"
# database.yml and routes.rb
m.template "configs/databases/#{options[:db]}.yml", "config/database.yml", :assigns => {
:app_name => @app_name,
:socket => options[:db] == "mysql" ? mysql_socket_location : nil
}
m.template "configs/routes.rb", "config/routes.rb"
# Initializers
m.template "configs/initializers/backtrace_silencers.rb", "config/initializers/backtrace_silencers.rb"
m.template "configs/initializers/inflections.rb", "config/initializers/inflections.rb"
m.template "configs/initializers/mime_types.rb", "config/initializers/mime_types.rb"
m.template "configs/initializers/new_rails_defaults.rb", "config/initializers/new_rails_defaults.rb"
# Locale
m.template "configs/locales/en.yml", "config/locales/en.yml"
# Environments
m.file "environments/boot.rb", "config/boot.rb"
m.template "environments/environment.rb", "config/environment.rb", :assigns => { :freeze => options[:freeze], :app_name => @app_name, :app_secret => secret }
m.file "environments/production.rb", "config/environments/production.rb"
m.file "environments/development.rb", "config/environments/development.rb"
m.file "environments/test.rb", "config/environments/test.rb"
# Scripts
%w( about console dbconsole destroy generate performance/benchmarker performance/profiler performance/request process/reaper process/spawner process/inspector runner server plugin ).each do |file|
m.file "bin/#{file}", "script/#{file}", script_options
end
# Dispatches
if options[:with_dispatchers]
m.file "dispatches/dispatch.rb", "public/dispatch.rb", dispatcher_options
m.file "dispatches/dispatch.rb", "public/dispatch.cgi", dispatcher_options
m.file "dispatches/dispatch.fcgi", "public/dispatch.fcgi", dispatcher_options
end
# HTML files
%w(404 422 500 index).each do |file|
m.template "html/#{file}.html", "public/#{file}.html"
end
m.template "html/favicon.ico", "public/favicon.ico"
m.template "html/robots.txt", "public/robots.txt"
m.file "html/images/rails.png", "public/images/rails.png"
# Javascripts
m.file "html/javascripts/prototype.js", "public/javascripts/prototype.js"
m.file "html/javascripts/effects.js", "public/javascripts/effects.js"
m.file "html/javascripts/dragdrop.js", "public/javascripts/dragdrop.js"
m.file "html/javascripts/controls.js", "public/javascripts/controls.js"
m.file "html/javascripts/application.js", "public/javascripts/application.js"
# Docs
m.file "doc/README_FOR_APP", "doc/README_FOR_APP"
create_directories(m)
create_root_files(m)
create_app_files(m)
create_config_files(m)
create_script_files(m)
create_test_files(m)
create_public_files(m)
create_documentation_file(m)
create_log_files(m)
end
end
# Logs
%w(server production development test).each { |file|
m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
}
def after_generate
if options[:template]
Rails::TemplateRunner.new(@destination_root, options[:template])
end
end
......@@ -138,55 +67,192 @@ def add_options!(opt)
opt.on("-f", "--freeze",
"Freeze Rails in vendor/rails from the gems generating the skeleton",
"Default: false") { |v| options[:freeze] = v }
opt.on("-m", "--template=path", String,
"Use an application template that lives at path (can be a filesystem path or URL).",
"Default: (none)") { |v| options[:template] = v }
end
private
def create_directories(m)
m.directory ''
# Intermediate directories are automatically created so don't sweat their absence here.
%w(
app/controllers
app/helpers
app/models
app/views/layouts
config/environments
config/initializers
config/locales
db
doc
lib
lib/tasks
log
public/images
public/javascripts
public/stylesheets
script/performance
test/fixtures
test/functional
test/integration
test/performance
test/unit
vendor
vendor/plugins
tmp/sessions
tmp/sockets
tmp/cache
tmp/pids
).each { |path| m.directory(path) }
end
def create_root_files(m)
m.file "fresh_rakefile", "Rakefile"
m.file "README", "README"
end
def create_app_files(m)
m.file "helpers/application_controller.rb", "app/controllers/application_controller.rb"
m.file "helpers/application_helper.rb", "app/helpers/application_helper.rb"
end
def create_config_files(m)
create_database_configuration_file(m)
create_routes_file(m)
create_locale_file(m)
create_initializer_files(m)
create_environment_files(m)
end
def create_documentation_file(m)
m.file "doc/README_FOR_APP", "doc/README_FOR_APP"
end
def create_log_files(m)
%w( server production development test ).each do |file|
m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
end
end
def create_public_files(m)
create_dispatch_files(m)
create_error_files(m)
create_welcome_file(m)
create_browser_convention_files(m)
create_rails_image(m)
create_javascript_files(m)
end
def create_script_files(m)
%w(
about console dbconsole destroy generate runner server plugin
performance/benchmarker performance/profiler performance/request
).each do |file|
m.file "bin/#{file}", "script/#{file}", {
:chmod => 0755,
:shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang]
}
end
end
def create_test_files(m)
m.file "helpers/test_helper.rb", "test/test_helper.rb"
m.file "helpers/performance_test.rb", "test/performance/browsing_test.rb"
end
def create_database_configuration_file(m)
m.template "configs/databases/#{options[:db]}.yml", "config/database.yml", :assigns => {
:app_name => @app_name,
:socket => options[:db] == "mysql" ? mysql_socket_location : nil }
end
def create_routes_file(m)
m.file "configs/routes.rb", "config/routes.rb"
end
def create_initializer_files(m)
%w(
backtrace_silencers
inflections
mime_types
new_rails_defaults
).each do |initializer|
m.file "configs/initializers/#{initializer}.rb", "config/initializers/#{initializer}.rb"
end
m.template "configs/initializers/session_store.rb", "config/initializers/session_store.rb",
:assigns => { :app_name => @app_name, :app_secret => ActiveSupport::SecureRandom.hex(64) }
end
def create_locale_file(m)
m.file "configs/locales/en.yml", "config/locales/en.yml"
end
def create_environment_files(m)
m.template "environments/environment.rb", "config/environment.rb",
:assigns => { :freeze => options[:freeze] }
m.file "environments/boot.rb", "config/boot.rb"
m.file "environments/production.rb", "config/environments/production.rb"
m.file "environments/development.rb", "config/environments/development.rb"
m.file "environments/test.rb", "config/environments/test.rb"
end
def create_dispatch_files(m)
if options[:with_dispatchers]
dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] }
m.file "dispatches/config.ru", "config.ru"
m.file "dispatches/dispatch.rb", "public/dispatch.rb", dispatcher_options
m.file "dispatches/dispatch.rb", "public/dispatch.cgi", dispatcher_options
m.file "dispatches/dispatch.fcgi", "public/dispatch.fcgi", dispatcher_options
end
end
def create_error_files(m)
%w( 404 422 500 ).each do |file|
m.file "html/#{file}.html", "public/#{file}.html"
end
end
def create_welcome_file(m)
m.file 'html/index.html', 'public/index.html'
end
def create_browser_convention_files(m)
m.file "html/favicon.ico", "public/favicon.ico"
m.file "html/robots.txt", "public/robots.txt"
end
def create_rails_image(m)
m.file "html/images/rails.png", "public/images/rails.png"
end
def create_javascript_files(m)
%w( prototype effects dragdrop controls application ).each do |javascript|
m.file "html/javascripts/#{javascript}.js", "public/javascripts/#{javascript}.js"
end
end
def mysql_socket_location
MYSQL_SOCKET_LOCATIONS.find { |f| File.exist?(f) } unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
end
# Installation skeleton. Intermediate directories are automatically
# created so don't sweat their absence here.
BASEDIRS = %w(
app/controllers
app/helpers
app/models
app/views/layouts
config/environments
config/initializers
config/locales
db
doc
lib
lib/tasks
log
public/images
public/javascripts
public/stylesheets
script/performance
script/process
test/fixtures
test/functional
test/integration
test/performance
test/unit
vendor
vendor/plugins
tmp/sessions
tmp/sockets
tmp/cache
tmp/pids
)
MYSQL_SOCKET_LOCATIONS = [
"/tmp/mysql.sock", # default
"/var/run/mysqld/mysqld.sock", # debian/gentoo
"/var/tmp/mysql.sock", # freebsd
"/var/lib/mysql/mysql.sock", # fedora
"/opt/local/lib/mysql/mysql.sock", # fedora
"/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql
"/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4
"/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
]
end
[
"/tmp/mysql.sock", # default
"/var/run/mysqld/mysqld.sock", # debian/gentoo
"/var/tmp/mysql.sock", # freebsd
"/var/lib/mysql/mysql.sock", # fedora
"/opt/local/lib/mysql/mysql.sock", # fedora
"/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql
"/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4
"/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
].find { |f| File.exist?(f) } unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
end
end
\ No newline at end of file
module Rails
class Git < Scm
def self.clone(repos, branch=nil)
`git clone #{repos}`
if branch
`cd #{repos.split('/').last}/`
`git checkout #{branch}`
end
end
def self.run(command)
`git #{command}`
end
end
end
\ No newline at end of file
module Rails
class Scm
private
def self.hash_to_parameters(hash)
hash.collect { |key, value| "--#{key} #{(value.kind_of?(String) ? value : "")}"}.join(" ")
end
end
end
\ No newline at end of file
module Rails
class Svn < Scm
def self.checkout(repos, branch = nil)
`svn checkout #{repos}/#{branch || "trunk"}`
end
end
end
\ No newline at end of file
require File.dirname(__FILE__) + '/scm/scm'
require File.dirname(__FILE__) + '/scm/git'
require File.dirname(__FILE__) + '/scm/svn'
require 'open-uri'
require 'fileutils'
module Rails
class TemplateRunner
attr_reader :behavior, :description, :root
def initialize(root, template) # :nodoc:
@root = Dir.pwd + "/" + root
puts "applying template: #{template}"
load_template(template)
puts "#{template} applied."
end
def load_template(template)
begin
code = open(template).read
in_root { self.instance_eval(code) }
rescue LoadError
raise "The template [#{template}] could not be loaded."
end
end
# Create a new file in the Rails project folder. Specify the
# relative path from RAILS_ROOT. Data is the return value of a block
# or a data string.
#
# ==== Examples
#
# file("lib/fun_party.rb") do
# hostname = ask("What is the virtual hostname I should use?")
# "vhost.name = #{hostname}"
# end
#
# file("config/apach.conf", "your apache config")
#
def file(filename, data = nil, &block)
puts "creating file #{filename}"
dir, file = [File.dirname(filename), File.basename(filename)]
inside(dir) do
File.open(file, "w") do |f|
if block_given?
f.write(block.call)
else
f.write(data)
end
end
end
end
# Install a plugin. You must provide either a Subversion url or Git url.
#
# ==== Examples
#
# plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git'
# plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
#
def plugin(name, options)
puts "installing plugin #{name}"
if options[:git] || options[:svn]
in_root do
`script/plugin install #{options[:svn] || options[:git]}`
end
else
puts "! no git or svn provided for #{name}. skipping..."
end
end
# Adds an entry into config/environment.rb for the supplied gem :
def gem(name, options = {})
puts "adding gem #{name}"
sentinel = 'Rails::Initializer.run do |config|'
gems_code = "config.gem '#{name}'"
if options.any?
opts = options.inject([]) {|result, h| result << [":#{h[0]} => '#{h[1]}'"] }.join(", ")
gems_code << ", #{opts}"
end
in_root do
gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
"#{match}\n #{gems_code}"
end
end
end
# Run a command in git.
#
# ==== Examples
#
# git :init
# git :add => "this.file that.rb"
# git :add => "onefile.rb", :rm => "badfile.cxx"
#
def git(command = {})
puts "running git #{command}"
in_root do
if command.is_a?(Symbol)
Git.run(command.to_s)
else
command.each do |command, options|
Git.run("#{command} #{options}")
end
end
end
end
# Create a new file in the vendor/ directory. Code can be specified
# in a block or a data string can be given.
#
# ==== Examples
#
# vendor("sekrit.rb") do
# sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
# "salt = '#{sekrit_salt}'"
# end
#
# vendor("foreign.rb", "# Foreign code is fun")
#
def vendor(filename, data = nil, &block)
puts "vendoring file #{filename}"
inside("vendor") do |folder|
File.open("#{folder}/#{filename}", "w") do |f|
if block_given?
f.write(block.call)
else
f.write(data)
end
end
end
end
# Create a new file in the lib/ directory. Code can be specified
# in a block or a data string can be given.
#
# ==== Examples
#
# lib("crypto.rb") do
# "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
# end
#
# lib("foreign.rb", "# Foreign code is fun")
#
def lib(filename, data = nil)
puts "add lib file #{filename}"
inside("lib") do |folder|
File.open("#{folder}/#{filename}", "w") do |f|
if block_given?
f.write(block.call)
else
f.write(data)
end
end
end
end
# Create a new Rakefile with the provided code (either in a block or a string).
#
# ==== Examples
#
# rakefile("bootstrap.rake") do
# project = ask("What is the UNIX name of your project?")
#
# <<-TASK
# namespace :#{project} do
# task :bootstrap do
# puts "i like boots!"
# end
# end
# TASK
# end
#
# rakefile("seed.rake", "puts 'im plantin ur seedz'")
#
def rakefile(filename, data = nil, &block)
puts "adding rakefile #{filename}"
inside("lib/tasks") do |folder|
File.open("#{folder}/#{filename}", "w") do |f|
if block_given?
f.write(block.call)
else
f.write(data)
end
end
end
end
# Create a new initializer with the provided code (either in a block or a string).
#
# ==== Examples
#
# initializer("globals.rb") do
# data = ""
#
# ['MY_WORK', 'ADMINS', 'BEST_COMPANY_EVAR'].each do
# data << "#{const} = :entp"
# end
#
# data
# end
#
# initializer("api.rb", "API_KEY = '123456'")
#
def initializer(filename, data = nil, &block)
puts "adding initializer #{filename}"
inside("config/initializers") do |folder|
File.open("#{folder}/#{filename}", "w") do |f|
if block_given?
f.write(block.call)
else
f.write(data)
end
end
end
end
# Generate something using a generator from Rails or a plugin.
# The second parameter is the argument string that is passed to
# the generator or an Array that is joined.
#
# ==== Example
#
# generate(:authenticated, "user session")
#
def generate(what, args = nil)
puts "generating #{what}"
args = args.join(" ") if args.class == Array
in_root { `#{root}/script/generate #{what} #{args}` }
end
# Executes a command
#
# ==== Example
#
# inside('vendor') do
# run('ln -s ~/edge rails)
# end
#
def run(command)
puts "executing #{command} from #{Dir.pwd}"
`#{command}`
end
# Runs the supplied rake task
#
# ==== Example
#
# rake("db:migrate")
# rake("db:migrate", "production")
#
def rake(command, env = 'development')
puts "running rake task #{command}"
in_root { `rake #{command} RAILS_ENV=#{env}` }
end
# Just run the capify command in root
#
# ==== Example
#
# capify!
#
def capify!
in_root { `capify .` }
end
# Add Rails to /vendor/rails
#
# ==== Example
#
# freeze!
#
def freeze!(args = {})
puts "vendoring rails edge"
in_root { `rake rails:freeze:edge` }
end
# Make an entry in Rails routing file conifg/routes.rb
#
# === Example
#
# route "map.root :controller => :welcome"
#
def route(routing_code)
sentinel = 'ActionController::Routing::Routes.draw do |map|'
in_root do
gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
"#{match}\n #{routing_code}\n"
end
end
end
protected
# Get a user's input
#
# ==== Example
#
# answer = ask("Should I freeze the latest Rails?")
# freeze! if ask("Should I freeze the latest Rails?") == "yes"
#
def ask(string)
puts string
gets.strip
end
# Do something in the root of the Rails application or
# a provided subfolder; the full path is yielded to the block you provide.
# The path is set back to the previous path when the method exits.
def inside(dir = '', &block)
folder = File.join(root, dir)
FileUtils.mkdir_p(folder) unless File.exist?(folder)
FileUtils.cd(folder) { block.arity == 1 ? yield(folder) : yield }
end
def in_root
FileUtils.cd(root) { yield }
end
# Helper to test if the user says yes(y)?
#
# ==== Example
#
# freeze! if yes?("Should I freeze the latest Rails?")
#
def yes?(question)
answer = ask(question).downcase
answer == "y" || answer == "yes"
end
# Helper to test if the user does NOT say yes(y)?
#
# ==== Example
#
# capify! if no?("Will you be using vlad to deploy your application?")
#
def no?(question)
!yes?(question)
end
def gsub_file(relative_destination, regexp, *args, &block)
path = destination_path(relative_destination)
content = File.read(path).gsub(regexp, *args, &block)
File.open(path, 'wb') { |file| file.write(content) }
end
def destination_path(relative_destination)
File.join(root, relative_destination)
end
end
end
\ No newline at end of file
namespace :db do
task :load_config => :rails_env do
require 'active_record'
ActiveRecord::Base.configurations = Rails::Configuration.new.database_configuration
end
namespace :create do
desc 'Create all the local databases defined in config/database.yml'
task :all => :environment do
task :all => :load_config do
ActiveRecord::Base.configurations.each_value do |config|
# Skip entries that don't have a database key, such as the first entry here:
#
......@@ -22,7 +27,7 @@ namespace :db do
end
desc 'Create the database defined in config/database.yml for the current RAILS_ENV'
task :create => :environment do
task :create => :load_config do
create_database(ActiveRecord::Base.configurations[RAILS_ENV])
end
......@@ -76,7 +81,7 @@ namespace :db do
namespace :drop do
desc 'Drops all the local databases defined in config/database.yml'
task :all => :environment do
task :all => :load_config do
ActiveRecord::Base.configurations.each_value do |config|
# Skip entries that don't have a database key
next unless config['database']
......@@ -87,7 +92,7 @@ namespace :db do
end
desc 'Drops the database for the current RAILS_ENV'
task :drop => :environment do
task :drop => :load_config do
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
begin
drop_database(config)
......@@ -393,6 +398,7 @@ end
def drop_database(config)
case config['adapter']
when 'mysql'
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/
FileUtils.rm(File.join(RAILS_ROOT, config['database']))
......
......@@ -128,6 +128,7 @@ namespace :rails do
desc "Generate dispatcher files in RAILS_ROOT/public"
task :generate_dispatchers do
require 'railties_path'
FileUtils.cp(RAILTIES_PATH + '/dispatches/config.ru', RAILS_ROOT + '/config.ru')
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.fcgi', RAILS_ROOT + '/public/dispatch.fcgi')
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.rb', RAILS_ROOT + '/public/dispatch.rb')
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.rb', RAILS_ROOT + '/public/dispatch.cgi')
......
desc 'Prints out your Rack middleware stack'
task :middleware => :environment do
ActionController::Dispatcher.middleware.each do |middleware|
puts "use #{middleware.inspect}"
end
puts "run ActionController::Dispatcher.new"
end
......@@ -3,6 +3,12 @@ task :environment do
require(File.join(RAILS_ROOT, 'config', 'environment'))
end
task :rails_env do
unless defined? RAILS_ENV
RAILS_ENV = ENV['RAILS_ENV'] ||= 'development'
end
end
desc 'Generate a crytographically secure secret key. This is typically used to generate a secret for cookie sessions.'
task :secret do
puts ActiveSupport::SecureRandom.hex(64)
......
......@@ -129,5 +129,19 @@ def test_gem_load_bad_specification
assert_equal '1.0.0', DUMMY_GEM_E_VERSION
end
def test_gem_handle_missing_dependencies
dummy_gem = Rails::GemDependency.new "dummy-gem-g"
dummy_gem.add_load_paths
dummy_gem.load
assert dummy_gem.loaded?
debugger
assert_equal 2, dummy_gem.dependencies.size
assert_nothing_raised do
dummy_gem.dependencies.each do |g|
g.dependencies
end
end
end
end
end
......@@ -314,10 +314,10 @@ def test_setting_another_default_locale
class RailsRootTest < Test::Unit::TestCase
def test_rails_dot_root_equals_rails_root
assert_equal RAILS_ROOT, Rails.root
assert_equal RAILS_ROOT, Rails.root.to_s
end
def test_rails_dot_root_accepts_arguments_for_file_dot_join
assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root('app', 'controllers')
def test_rails_dot_root_should_be_a_pathname
assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root.join('app', 'controllers').to_s
end
end
\ No newline at end of file
--- !ruby/object:Gem::Specification
name: dummy-gem-f
version: !ruby/object:Gem::Version
version: 1.3.0
platform: ruby
authors:
- "Nobody"
date: 2008-10-03 00:00:00 -04:00
dependencies:
- !ruby/object:Gem::Dependency
name: absolutely-no-such-gem
type: :runtime
version_requirement:
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.0.0
version:
files:
- lib
- lib/dummy-gem-f.rb
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
specification_version: 2
summary: Dummy Gem F
--- !ruby/object:Gem::Specification
name: dummy-gem-g
version: !ruby/object:Gem::Version
version: 1.3.0
platform: ruby
authors:
- "Nobody"
date: 2008-10-03 00:00:00 -04:00
dependencies:
- !ruby/object:Gem::Dependency
name: dummy-gem-f
type: :development
version_requirement:
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.0.0
version:
files:
- lib
- lib/dummy-gem-g.rb
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
specification_version: 2
summary: Dummy Gem G
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册