mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-17 15:50:13 +01:00
forgot to add these
This commit is contained in:
parent
86afd42148
commit
fde64e0b3d
145 changed files with 9044 additions and 0 deletions
2
backup.rails2.3/plugins/extra_validations/init.rb
Normal file
2
backup.rails2.3/plugins/extra_validations/init.rb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
require 'extra_validations'
|
||||
ActiveRecord::Base.extend ExtraValidations
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
module ExtraValidations
|
||||
|
||||
# Validates the value of the specified attribute by checking for a forbidden string
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_does_not_contain :first_name, :string => ','
|
||||
# end
|
||||
#
|
||||
# A string must be provided or else an exception will be raised.
|
||||
#
|
||||
# Configuration options:
|
||||
# * <tt>message</tt> - A custom error message (default is: "is invalid")
|
||||
# * <tt>string</tt> - The string to verify is not included (note: must be supplied!)
|
||||
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
||||
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
def validates_does_not_contain(*attr_names)
|
||||
configuration = { :message => I18n.translate('activerecord.errors.messages')[:invalid], :on => :save, :string => nil }
|
||||
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
||||
|
||||
raise(ArgumentError, "A string must be supplied as the :string option of the configuration hash") unless configuration[:string].is_a?(String)
|
||||
|
||||
validates_each(attr_names, configuration) do |record, attr_name, value|
|
||||
record.errors.add(attr_name, configuration[:message]) if value.to_s =~ Regexp.new(Regexp.escape(configuration[:string]))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
35
backup.rails2.3/plugins/open_id_authentication/CHANGELOG
Normal file
35
backup.rails2.3/plugins/open_id_authentication/CHANGELOG
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
* Fake HTTP method from OpenID server since they only support a GET. Eliminates the need to set an extra route to match the server's reply. [Josh Peek]
|
||||
|
||||
* OpenID 2.0 recommends that forms should use the field name "openid_identifier" rather than "openid_url" [Josh Peek]
|
||||
|
||||
* Return open_id_response.display_identifier to the application instead of .endpoints.claimed_id. [nbibler]
|
||||
|
||||
* Add Timeout protection [Rick]
|
||||
|
||||
* An invalid identity url passed through authenticate_with_open_id will no longer raise an InvalidOpenId exception. Instead it will return Result[:missing] to the completion block.
|
||||
|
||||
* Allow a return_to option to be used instead of the requested url [Josh Peek]
|
||||
|
||||
* Updated plugin to use Ruby OpenID 2.x.x [Josh Peek]
|
||||
|
||||
* Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH]
|
||||
|
||||
* Use URI instead of regexps to normalize the URL and gain free, better matching #8136 [dkubb]
|
||||
|
||||
* Allow -'s in #normalize_url [Rick]
|
||||
|
||||
* remove instance of mattr_accessor, it was breaking tests since they don't load ActiveSupport. Fix Timeout test [Rick]
|
||||
|
||||
* Throw a InvalidOpenId exception instead of just a RuntimeError when the URL can't be normalized [DHH]
|
||||
|
||||
* Just use the path for the return URL, so extra query parameters don't interfere [DHH]
|
||||
|
||||
* Added a new default database-backed store after experiencing trouble with the filestore on NFS. The file store is still available as an option [DHH]
|
||||
|
||||
* Added normalize_url and applied it to all operations going through the plugin [DHH]
|
||||
|
||||
* Removed open_id? as the idea of using the same input box for both OpenID and username has died -- use using_open_id? instead (which checks for the presence of params[:openid_url] by default) [DHH]
|
||||
|
||||
* Added OpenIdAuthentication::Result to make it easier to deal with default situations where you don't care to do something particular for each error state [DHH]
|
||||
|
||||
* Stop relying on root_url being defined, we can just grab the current url instead [DHH]
|
||||
231
backup.rails2.3/plugins/open_id_authentication/README
Normal file
231
backup.rails2.3/plugins/open_id_authentication/README
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
OpenIdAuthentication
|
||||
====================
|
||||
|
||||
Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first:
|
||||
|
||||
gem install ruby-openid
|
||||
|
||||
To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb
|
||||
from that gem.
|
||||
|
||||
The specification used is http://openid.net/specs/openid-authentication-2_0.html.
|
||||
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
OpenID authentication uses the session, so be sure that you haven't turned that off. It also relies on a number of
|
||||
database tables to store the authentication keys. So you'll have to run the migration to create these before you get started:
|
||||
|
||||
rake open_id_authentication:db:create
|
||||
|
||||
Or, use the included generators to install or upgrade:
|
||||
|
||||
./script/generate open_id_authentication_tables MigrationName
|
||||
./script/generate upgrade_open_id_authentication_tables MigrationName
|
||||
|
||||
Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb:
|
||||
|
||||
OpenIdAuthentication.store = :file
|
||||
|
||||
This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations.
|
||||
If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb.
|
||||
|
||||
The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb:
|
||||
|
||||
map.root :controller => 'articles'
|
||||
|
||||
This plugin relies on Rails Edge revision 6317 or newer.
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add
|
||||
salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point,
|
||||
not a destination.
|
||||
|
||||
Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever
|
||||
model you are using for authentication.
|
||||
|
||||
Also of note is the following code block used in the example below:
|
||||
|
||||
authenticate_with_open_id do |result, identity_url|
|
||||
...
|
||||
end
|
||||
|
||||
In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' -
|
||||
If you are storing just 'example.com' with your user, the lookup will fail.
|
||||
|
||||
There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs.
|
||||
|
||||
OpenIdAuthentication.normalize_url(user.identity_url)
|
||||
|
||||
The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/'
|
||||
It will also raise an InvalidOpenId exception if the URL is determined to not be valid.
|
||||
Use the above code in your User model and validate OpenID URLs before saving them.
|
||||
|
||||
config/routes.rb
|
||||
|
||||
map.root :controller => 'articles'
|
||||
map.resource :session
|
||||
|
||||
|
||||
app/views/sessions/new.erb
|
||||
|
||||
<% form_tag(session_url) do %>
|
||||
<p>
|
||||
<label for="name">Username:</label>
|
||||
<%= text_field_tag "name" %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="password">Password:</label>
|
||||
<%= password_field_tag %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
...or use:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="openid_identifier">OpenID:</label>
|
||||
<%= text_field_tag "openid_identifier" %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%= submit_tag 'Sign in', :disable_with => "Signing in…" %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
app/controllers/sessions_controller.rb
|
||||
class SessionsController < ApplicationController
|
||||
def create
|
||||
if using_open_id?
|
||||
open_id_authentication
|
||||
else
|
||||
password_authentication(params[:name], params[:password])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def password_authentication(name, password)
|
||||
if @current_user = @account.users.authenticate(params[:name], params[:password])
|
||||
successful_login
|
||||
else
|
||||
failed_login "Sorry, that username/password doesn't work"
|
||||
end
|
||||
end
|
||||
|
||||
def open_id_authentication
|
||||
authenticate_with_open_id do |result, identity_url|
|
||||
if result.successful?
|
||||
if @current_user = @account.users.find_by_identity_url(identity_url)
|
||||
successful_login
|
||||
else
|
||||
failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
|
||||
end
|
||||
else
|
||||
failed_login result.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def successful_login
|
||||
session[:user_id] = @current_user.id
|
||||
redirect_to(root_url)
|
||||
end
|
||||
|
||||
def failed_login(message)
|
||||
flash[:error] = message
|
||||
redirect_to(new_session_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
If you're fine with the result messages above and don't need individual logic on a per-failure basis,
|
||||
you can collapse the case into a mere boolean:
|
||||
|
||||
def open_id_authentication
|
||||
authenticate_with_open_id do |result, identity_url|
|
||||
if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url)
|
||||
successful_login
|
||||
else
|
||||
failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Simple Registration OpenID Extension
|
||||
====================================
|
||||
|
||||
Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension
|
||||
|
||||
You can support it in your app by changing #open_id_authentication
|
||||
|
||||
def open_id_authentication(identity_url)
|
||||
# Pass optional :required and :optional keys to specify what sreg fields you want.
|
||||
# Be sure to yield registration, a third argument in the #authenticate_with_open_id block.
|
||||
authenticate_with_open_id(identity_url,
|
||||
:required => [ :nickname, :email ],
|
||||
:optional => :fullname) do |result, identity_url, registration|
|
||||
case result.status
|
||||
when :missing
|
||||
failed_login "Sorry, the OpenID server couldn't be found"
|
||||
when :invalid
|
||||
failed_login "Sorry, but this does not appear to be a valid OpenID"
|
||||
when :canceled
|
||||
failed_login "OpenID verification was canceled"
|
||||
when :failed
|
||||
failed_login "Sorry, the OpenID verification failed"
|
||||
when :successful
|
||||
if @current_user = @account.users.find_by_identity_url(identity_url)
|
||||
assign_registration_attributes!(registration)
|
||||
|
||||
if current_user.save
|
||||
successful_login
|
||||
else
|
||||
failed_login "Your OpenID profile registration failed: " +
|
||||
@current_user.errors.full_messages.to_sentence
|
||||
end
|
||||
else
|
||||
failed_login "Sorry, no user by that identity URL exists"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# registration is a hash containing the valid sreg keys given above
|
||||
# use this to map them to fields of your user model
|
||||
def assign_registration_attributes!(registration)
|
||||
model_to_registration_mapping.each do |model_attribute, registration_attribute|
|
||||
unless registration[registration_attribute].blank?
|
||||
@current_user.send("#{model_attribute}=", registration[registration_attribute])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def model_to_registration_mapping
|
||||
{ :login => 'nickname', :email => 'email', :display_name => 'fullname' }
|
||||
end
|
||||
|
||||
Attribute Exchange OpenID Extension
|
||||
===================================
|
||||
|
||||
Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html
|
||||
|
||||
Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example:
|
||||
|
||||
authenticate_with_open_id(identity_url,
|
||||
:required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration|
|
||||
|
||||
This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate'
|
||||
|
||||
|
||||
|
||||
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
|
||||
22
backup.rails2.3/plugins/open_id_authentication/Rakefile
Normal file
22
backup.rails2.3/plugins/open_id_authentication/Rakefile
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the open_id_authentication plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the open_id_authentication plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'OpenIdAuthentication'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
class OpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
|
||||
def initialize(runtime_args, runtime_options = {})
|
||||
super
|
||||
end
|
||||
|
||||
def manifest
|
||||
record do |m|
|
||||
m.migration_template 'migration.rb', 'db/migrate'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
class <%= class_name %> < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :open_id_authentication_associations, :force => true do |t|
|
||||
t.integer :issued, :lifetime
|
||||
t.string :handle, :assoc_type
|
||||
t.binary :server_url, :secret
|
||||
end
|
||||
|
||||
create_table :open_id_authentication_nonces, :force => true do |t|
|
||||
t.integer :timestamp, :null => false
|
||||
t.string :server_url, :null => true
|
||||
t.string :salt, :null => false
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :open_id_authentication_associations
|
||||
drop_table :open_id_authentication_nonces
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
class <%= class_name %> < ActiveRecord::Migration
|
||||
def self.up
|
||||
drop_table :open_id_authentication_settings
|
||||
drop_table :open_id_authentication_nonces
|
||||
|
||||
create_table :open_id_authentication_nonces, :force => true do |t|
|
||||
t.integer :timestamp, :null => false
|
||||
t.string :server_url, :null => true
|
||||
t.string :salt, :null => false
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :open_id_authentication_nonces
|
||||
|
||||
create_table :open_id_authentication_nonces, :force => true do |t|
|
||||
t.integer :created
|
||||
t.string :nonce
|
||||
end
|
||||
|
||||
create_table :open_id_authentication_settings, :force => true do |t|
|
||||
t.string :setting
|
||||
t.binary :value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
class UpgradeOpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
|
||||
def initialize(runtime_args, runtime_options = {})
|
||||
super
|
||||
end
|
||||
|
||||
def manifest
|
||||
record do |m|
|
||||
m.migration_template 'migration.rb', 'db/migrate'
|
||||
end
|
||||
end
|
||||
end
|
||||
18
backup.rails2.3/plugins/open_id_authentication/init.rb
Normal file
18
backup.rails2.3/plugins/open_id_authentication/init.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
if config.respond_to?(:gems)
|
||||
config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
|
||||
else
|
||||
begin
|
||||
require 'openid'
|
||||
rescue LoadError
|
||||
begin
|
||||
gem 'ruby-openid', '>=2.0.4'
|
||||
rescue Gem::LoadError
|
||||
puts "Install the ruby-openid gem to enable OpenID support"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
config.to_prepare do
|
||||
OpenID::Util.logger = Rails.logger
|
||||
ActionController::Base.send :include, OpenIdAuthentication
|
||||
end
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
require 'uri'
|
||||
require 'openid/extensions/sreg'
|
||||
require 'openid/extensions/ax'
|
||||
require 'openid/store/filesystem'
|
||||
|
||||
require File.dirname(__FILE__) + '/open_id_authentication/association'
|
||||
require File.dirname(__FILE__) + '/open_id_authentication/nonce'
|
||||
require File.dirname(__FILE__) + '/open_id_authentication/db_store'
|
||||
require File.dirname(__FILE__) + '/open_id_authentication/request'
|
||||
require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
|
||||
|
||||
module OpenIdAuthentication
|
||||
OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
|
||||
|
||||
def self.store
|
||||
@@store
|
||||
end
|
||||
|
||||
def self.store=(*store_option)
|
||||
store, *parameters = *([ store_option ].flatten)
|
||||
|
||||
@@store = case store
|
||||
when :db
|
||||
OpenIdAuthentication::DbStore.new
|
||||
when :file
|
||||
OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
|
||||
else
|
||||
store
|
||||
end
|
||||
end
|
||||
|
||||
self.store = :db
|
||||
|
||||
class InvalidOpenId < StandardError
|
||||
end
|
||||
|
||||
class Result
|
||||
ERROR_MESSAGES = {
|
||||
:missing => "Sorry, the OpenID server couldn't be found",
|
||||
:invalid => "Sorry, but this does not appear to be a valid OpenID",
|
||||
:canceled => "OpenID verification was canceled",
|
||||
:failed => "OpenID verification failed",
|
||||
:setup_needed => "OpenID verification needs setup"
|
||||
}
|
||||
|
||||
def self.[](code)
|
||||
new(code)
|
||||
end
|
||||
|
||||
def initialize(code)
|
||||
@code = code
|
||||
end
|
||||
|
||||
def status
|
||||
@code
|
||||
end
|
||||
|
||||
ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
|
||||
|
||||
def successful?
|
||||
@code == :successful
|
||||
end
|
||||
|
||||
def unsuccessful?
|
||||
ERROR_MESSAGES.keys.include?(@code)
|
||||
end
|
||||
|
||||
def message
|
||||
ERROR_MESSAGES[@code]
|
||||
end
|
||||
end
|
||||
|
||||
# normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
|
||||
def self.normalize_identifier(identifier)
|
||||
# clean up whitespace
|
||||
identifier = identifier.to_s.strip
|
||||
|
||||
# if an XRI has a prefix, strip it.
|
||||
identifier.gsub!(/xri:\/\//i, '')
|
||||
|
||||
# dodge XRIs -- TODO: validate, don't just skip.
|
||||
unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
|
||||
# does it begin with http? if not, add it.
|
||||
identifier = "http://#{identifier}" unless identifier =~ /^http/i
|
||||
|
||||
# strip any fragments
|
||||
identifier.gsub!(/\#(.*)$/, '')
|
||||
|
||||
begin
|
||||
uri = URI.parse(identifier)
|
||||
uri.scheme = uri.scheme.downcase # URI should do this
|
||||
identifier = uri.normalize.to_s
|
||||
rescue URI::InvalidURIError
|
||||
raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
|
||||
end
|
||||
end
|
||||
|
||||
return identifier
|
||||
end
|
||||
|
||||
# deprecated for OpenID 2.0, where not all OpenIDs are URLs
|
||||
def self.normalize_url(url)
|
||||
ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
|
||||
self.normalize_identifier(url)
|
||||
end
|
||||
|
||||
protected
|
||||
def normalize_url(url)
|
||||
OpenIdAuthentication.normalize_url(url)
|
||||
end
|
||||
|
||||
def normalize_identifier(url)
|
||||
OpenIdAuthentication.normalize_identifier(url)
|
||||
end
|
||||
|
||||
# The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
|
||||
# because that's what the specification dictates in order to get browser auto-complete working across sites
|
||||
def using_open_id?(identity_url = nil) #:doc:
|
||||
identity_url ||= params[:openid_identifier] || params[:openid_url]
|
||||
!identity_url.blank? || params[:open_id_complete]
|
||||
end
|
||||
|
||||
def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
|
||||
identity_url ||= params[:openid_identifier] || params[:openid_url]
|
||||
|
||||
if params[:open_id_complete].nil?
|
||||
begin_open_id_authentication(identity_url, options, &block)
|
||||
else
|
||||
complete_open_id_authentication(&block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def begin_open_id_authentication(identity_url, options = {})
|
||||
identity_url = normalize_identifier(identity_url)
|
||||
return_to = options.delete(:return_to)
|
||||
method = options.delete(:method)
|
||||
|
||||
options[:required] ||= [] # reduces validation later
|
||||
options[:optional] ||= []
|
||||
|
||||
open_id_request = open_id_consumer.begin(identity_url)
|
||||
add_simple_registration_fields(open_id_request, options)
|
||||
add_ax_fields(open_id_request, options)
|
||||
redirect_to(open_id_redirect_url(open_id_request, return_to, method))
|
||||
rescue OpenIdAuthentication::InvalidOpenId => e
|
||||
yield Result[:invalid], identity_url, nil
|
||||
rescue OpenID::OpenIDError, Timeout::Error => e
|
||||
logger.error("[OPENID] #{e}")
|
||||
yield Result[:missing], identity_url, nil
|
||||
end
|
||||
|
||||
def complete_open_id_authentication
|
||||
params_with_path = params.reject { |key, value| request.path_parameters[key] }
|
||||
params_with_path.delete(:format)
|
||||
open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
|
||||
identity_url = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
|
||||
|
||||
case open_id_response.status
|
||||
when OpenID::Consumer::SUCCESS
|
||||
profile_data = {}
|
||||
|
||||
# merge the SReg data and the AX data into a single hash of profile data
|
||||
[ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
|
||||
if data_response.from_success_response( open_id_response )
|
||||
profile_data.merge! data_response.from_success_response( open_id_response ).data
|
||||
end
|
||||
end
|
||||
|
||||
yield Result[:successful], identity_url, profile_data
|
||||
when OpenID::Consumer::CANCEL
|
||||
yield Result[:canceled], identity_url, nil
|
||||
when OpenID::Consumer::FAILURE
|
||||
yield Result[:failed], identity_url, nil
|
||||
when OpenID::Consumer::SETUP_NEEDED
|
||||
yield Result[:setup_needed], open_id_response.setup_url, nil
|
||||
end
|
||||
end
|
||||
|
||||
def open_id_consumer
|
||||
OpenID::Consumer.new(session, OpenIdAuthentication.store)
|
||||
end
|
||||
|
||||
def add_simple_registration_fields(open_id_request, fields)
|
||||
sreg_request = OpenID::SReg::Request.new
|
||||
|
||||
# filter out AX identifiers (URIs)
|
||||
required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
|
||||
optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
|
||||
|
||||
sreg_request.request_fields(required_fields, true) unless required_fields.blank?
|
||||
sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
|
||||
sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
|
||||
open_id_request.add_extension(sreg_request)
|
||||
end
|
||||
|
||||
def add_ax_fields( open_id_request, fields )
|
||||
ax_request = OpenID::AX::FetchRequest.new
|
||||
|
||||
# look through the :required and :optional fields for URIs (AX identifiers)
|
||||
fields[:required].each do |f|
|
||||
next unless f =~ /^https?:\/\//
|
||||
ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
|
||||
end
|
||||
|
||||
fields[:optional].each do |f|
|
||||
next unless f =~ /^https?:\/\//
|
||||
ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
|
||||
end
|
||||
|
||||
open_id_request.add_extension( ax_request )
|
||||
end
|
||||
|
||||
def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
|
||||
open_id_request.return_to_args['_method'] = (method || request.method).to_s
|
||||
open_id_request.return_to_args['open_id_complete'] = '1'
|
||||
open_id_request.redirect_url(root_url, return_to || requested_url)
|
||||
end
|
||||
|
||||
def requested_url
|
||||
relative_url_root = self.class.respond_to?(:relative_url_root) ?
|
||||
self.class.relative_url_root.to_s :
|
||||
request.relative_url_root
|
||||
"#{request.protocol}#{request.host_with_port}#{ActionController::Base.relative_url_root}#{request.path}"
|
||||
end
|
||||
|
||||
def timeout_protection_from_identity_server
|
||||
yield
|
||||
rescue Timeout::Error
|
||||
Class.new do
|
||||
def status
|
||||
OpenID::FAILURE
|
||||
end
|
||||
|
||||
def msg
|
||||
"Identity server timed out"
|
||||
end
|
||||
end.new
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
module OpenIdAuthentication
|
||||
class Association < ActiveRecord::Base
|
||||
set_table_name :open_id_authentication_associations
|
||||
|
||||
def from_record
|
||||
OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
require 'openid/store/interface'
|
||||
|
||||
module OpenIdAuthentication
|
||||
class DbStore < OpenID::Store::Interface
|
||||
def self.cleanup_nonces
|
||||
now = Time.now.to_i
|
||||
Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
|
||||
end
|
||||
|
||||
def self.cleanup_associations
|
||||
now = Time.now.to_i
|
||||
Association.delete_all(['issued + lifetime > ?',now])
|
||||
end
|
||||
|
||||
def store_association(server_url, assoc)
|
||||
remove_association(server_url, assoc.handle)
|
||||
Association.create(:server_url => server_url,
|
||||
:handle => assoc.handle,
|
||||
:secret => assoc.secret,
|
||||
:issued => assoc.issued,
|
||||
:lifetime => assoc.lifetime,
|
||||
:assoc_type => assoc.assoc_type)
|
||||
end
|
||||
|
||||
def get_association(server_url, handle = nil)
|
||||
assocs = if handle.blank?
|
||||
Association.find_all_by_server_url(server_url)
|
||||
else
|
||||
Association.find_all_by_server_url_and_handle(server_url, handle)
|
||||
end
|
||||
|
||||
assocs.reverse.each do |assoc|
|
||||
a = assoc.from_record
|
||||
if a.expires_in == 0
|
||||
assoc.destroy
|
||||
else
|
||||
return a
|
||||
end
|
||||
end if assocs.any?
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
def remove_association(server_url, handle)
|
||||
Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
|
||||
end
|
||||
|
||||
def use_nonce(server_url, timestamp, salt)
|
||||
return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
|
||||
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
|
||||
Nonce.create(:server_url => server_url, :timestamp => timestamp, :salt => salt)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
module OpenIdAuthentication
|
||||
class Nonce < ActiveRecord::Base
|
||||
set_table_name :open_id_authentication_nonces
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
module OpenIdAuthentication
|
||||
module Request
|
||||
def self.included(base)
|
||||
base.alias_method_chain :request_method, :openid
|
||||
end
|
||||
|
||||
def request_method_with_openid
|
||||
if !parameters[:_method].blank? && parameters[:open_id_complete] == '1'
|
||||
parameters[:_method].to_sym
|
||||
else
|
||||
request_method_without_openid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# In Rails 2.3, the request object has been renamed
|
||||
# from AbstractRequest to Request
|
||||
if defined? ActionController::Request
|
||||
ActionController::Request.send :include, OpenIdAuthentication::Request
|
||||
else
|
||||
ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# http://trac.openidenabled.com/trac/ticket/156
|
||||
module OpenID
|
||||
@@timeout_threshold = 20
|
||||
|
||||
def self.timeout_threshold
|
||||
@@timeout_threshold
|
||||
end
|
||||
|
||||
def self.timeout_threshold=(value)
|
||||
@@timeout_threshold = value
|
||||
end
|
||||
|
||||
class StandardFetcher
|
||||
def make_http(uri)
|
||||
http = @proxy.new(uri.host, uri.port)
|
||||
http.read_timeout = http.open_timeout = OpenID.timeout_threshold
|
||||
http
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
namespace :open_id_authentication do
|
||||
namespace :db do
|
||||
desc "Creates authentication tables for use with OpenIdAuthentication"
|
||||
task :create => :environment do
|
||||
generate_migration(["open_id_authentication_tables", "add_open_id_authentication_tables"])
|
||||
end
|
||||
|
||||
desc "Upgrade authentication tables from ruby-openid 1.x.x to 2.x.x"
|
||||
task :upgrade => :environment do
|
||||
generate_migration(["upgrade_open_id_authentication_tables", "upgrade_open_id_authentication_tables"])
|
||||
end
|
||||
|
||||
def generate_migration(args)
|
||||
require 'rails_generator'
|
||||
require 'rails_generator/scripts/generate'
|
||||
|
||||
if ActiveRecord::Base.connection.supports_migrations?
|
||||
Rails::Generator::Scripts::Generate.new.run(args)
|
||||
else
|
||||
raise "Task unavailable to this database (no migration support)"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Clear the authentication tables"
|
||||
task :clear => :environment do
|
||||
OpenIdAuthentication::DbStore.cleanup_nonces
|
||||
OpenIdAuthentication::DbStore.cleanup_associations
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
class NormalizeTest < Test::Unit::TestCase
|
||||
include OpenIdAuthentication
|
||||
|
||||
NORMALIZATIONS = {
|
||||
"openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
|
||||
"http://openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
|
||||
"https://openid.aol.com/nextangler" => "https://openid.aol.com/nextangler",
|
||||
"HTTP://OPENID.AOL.COM/NEXTANGLER" => "http://openid.aol.com/NEXTANGLER",
|
||||
"HTTPS://OPENID.AOL.COM/NEXTANGLER" => "https://openid.aol.com/NEXTANGLER",
|
||||
"loudthinking.com" => "http://loudthinking.com/",
|
||||
"http://loudthinking.com" => "http://loudthinking.com/",
|
||||
"http://loudthinking.com:80" => "http://loudthinking.com/",
|
||||
"https://loudthinking.com:443" => "https://loudthinking.com/",
|
||||
"http://loudthinking.com:8080" => "http://loudthinking.com:8080/",
|
||||
"techno-weenie.net" => "http://techno-weenie.net/",
|
||||
"http://techno-weenie.net" => "http://techno-weenie.net/",
|
||||
"http://techno-weenie.net " => "http://techno-weenie.net/",
|
||||
"=name" => "=name"
|
||||
}
|
||||
|
||||
def test_normalizations
|
||||
NORMALIZATIONS.each do |from, to|
|
||||
assert_equal to, normalize_identifier(from)
|
||||
end
|
||||
end
|
||||
|
||||
def test_broken_open_id
|
||||
assert_raises(InvalidOpenId) { normalize_identifier(nil) }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
class OpenIdAuthenticationTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = Class.new do
|
||||
include OpenIdAuthentication
|
||||
def params() {} end
|
||||
end.new
|
||||
end
|
||||
|
||||
def test_authentication_should_fail_when_the_identity_server_is_missing
|
||||
open_id_consumer = mock()
|
||||
open_id_consumer.expects(:begin).raises(OpenID::OpenIDError)
|
||||
@controller.expects(:open_id_consumer).returns(open_id_consumer)
|
||||
@controller.expects(:logger).returns(mock(:error => true))
|
||||
|
||||
@controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
|
||||
assert result.missing?
|
||||
assert_equal "Sorry, the OpenID server couldn't be found", result.message
|
||||
end
|
||||
end
|
||||
|
||||
def test_authentication_should_be_invalid_when_the_identity_url_is_invalid
|
||||
@controller.send(:authenticate_with_open_id, "!") do |result, identity_url|
|
||||
assert result.invalid?, "Result expected to be invalid but was not"
|
||||
assert_equal "Sorry, but this does not appear to be a valid OpenID", result.message
|
||||
end
|
||||
end
|
||||
|
||||
def test_authentication_should_fail_when_the_identity_server_times_out
|
||||
open_id_consumer = mock()
|
||||
open_id_consumer.expects(:begin).raises(Timeout::Error, "Identity Server took too long.")
|
||||
@controller.expects(:open_id_consumer).returns(open_id_consumer)
|
||||
@controller.expects(:logger).returns(mock(:error => true))
|
||||
|
||||
@controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
|
||||
assert result.missing?
|
||||
assert_equal "Sorry, the OpenID server couldn't be found", result.message
|
||||
end
|
||||
end
|
||||
|
||||
def test_authentication_should_begin_when_the_identity_server_is_present
|
||||
@controller.expects(:begin_open_id_authentication)
|
||||
@controller.send(:authenticate_with_open_id, "http://someone.example.com")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
class StatusTest < Test::Unit::TestCase
|
||||
include OpenIdAuthentication
|
||||
|
||||
def test_state_conditional
|
||||
assert Result[:missing].missing?
|
||||
assert Result[:missing].unsuccessful?
|
||||
assert !Result[:missing].successful?
|
||||
|
||||
assert Result[:successful].successful?
|
||||
assert !Result[:successful].unsuccessful?
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
|
||||
gem 'activesupport'
|
||||
require 'active_support'
|
||||
|
||||
gem 'actionpack'
|
||||
require 'action_controller'
|
||||
|
||||
gem 'mocha'
|
||||
require 'mocha'
|
||||
|
||||
gem 'ruby-openid'
|
||||
require 'openid'
|
||||
|
||||
RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT
|
||||
require File.dirname(__FILE__) + "/../lib/open_id_authentication"
|
||||
7
backup.rails2.3/plugins/resource_feeder/README
Normal file
7
backup.rails2.3/plugins/resource_feeder/README
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
ResourceFeeder
|
||||
==============
|
||||
|
||||
Simple feeds for resources
|
||||
|
||||
NOTE: This plugin depends on the latest version of simply_helpful, available here:
|
||||
http://dev.rubyonrails.org/svn/rails/plugins/simply_helpful/
|
||||
22
backup.rails2.3/plugins/resource_feeder/Rakefile
Normal file
22
backup.rails2.3/plugins/resource_feeder/Rakefile
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the resource_feed plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the resource_feed plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'ResourceFeed'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
2
backup.rails2.3/plugins/resource_feeder/init.rb
Normal file
2
backup.rails2.3/plugins/resource_feeder/init.rb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
require 'resource_feeder'
|
||||
ActionController::Base.send(:include, ResourceFeeder::Rss, ResourceFeeder::Atom)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
require 'resource_feeder/rss'
|
||||
require 'resource_feeder/atom'
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
require 'resource_feeder/common'
|
||||
|
||||
module ResourceFeeder
|
||||
module Atom
|
||||
include ResourceFeeder::Common
|
||||
include ActionController::Routing
|
||||
extend self
|
||||
|
||||
def render_atom_feed_for(resources, options = {})
|
||||
render :text => atom_feed_for(resources, options), :content_type => Mime::ATOM
|
||||
end
|
||||
|
||||
def atom_feed_for(resources, options = {})
|
||||
xml = Builder::XmlMarkup.new(:indent => 2)
|
||||
|
||||
options[:feed] ||= {}
|
||||
options[:item] ||= {}
|
||||
options[:url_writer] ||= self
|
||||
|
||||
if options[:class] || resources.first
|
||||
klass = options[:class] || resources.first.class
|
||||
new_record = klass.new
|
||||
else
|
||||
options[:feed] = { :title => "Empty", :link => "http://example.com" }
|
||||
end
|
||||
|
||||
options[:feed][:title] ||= klass.name.pluralize
|
||||
options[:feed][:id] ||= "tag:#{request.host_with_port}:#{klass.name.pluralize}"
|
||||
options[:feed][:link] ||= polymorphic_url(new_record, :controller => options[:url_writer].controller_name)
|
||||
|
||||
options[:item][:title] ||= [ :title, :subject, :headline, :name ]
|
||||
options[:item][:description] ||= [ :description, :body, :content ]
|
||||
options[:item][:pub_date] ||= [ :updated_at, :updated_on, :created_at, :created_on ]
|
||||
options[:item][:author] ||= [ :author, :creator ]
|
||||
|
||||
resource_link = lambda { |r| polymorphic_url(r, :controller => options[:url_writer].controller_name) }
|
||||
|
||||
xml.instruct!
|
||||
xml.feed "xml:lang" => "en-US", "xmlns" => 'http://www.w3.org/2005/Atom' do
|
||||
xml.title(options[:feed][:title])
|
||||
xml.id(options[:feed][:id])
|
||||
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:feed][:link])
|
||||
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:feed][:self]) if options[:feed][:self]
|
||||
xml.subtitle(options[:feed][:description]) if options[:feed][:description]
|
||||
|
||||
for resource in resources
|
||||
published_at = call_or_read(options[:item][:pub_date], resource)
|
||||
|
||||
xml.entry do
|
||||
xml.title(call_or_read(options[:item][:title], resource))
|
||||
xml.content(call_or_read(options[:item][:description], resource), :type => 'html')
|
||||
xml.id("tag:#{request.host_with_port},#{published_at.xmlschema}:#{call_or_read(options[:item][:guid] || options[:item][:link] || resource_link, resource)}")
|
||||
xml.published(published_at.xmlschema)
|
||||
xml.updated((resource.respond_to?(:updated_at) ? call_or_read(options[:item][:pub_date] || :updated_at, resource) : published_at).xmlschema)
|
||||
xml.link(:rel => 'alternate', :type => 'text/html', :href => call_or_read(options[:item][:link] || options[:item][:guid] || resource_link, resource))
|
||||
|
||||
if author = call_or_read(options[:item][:author], resource)
|
||||
xml.author do
|
||||
xml.name()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
module ResourceFeeder
|
||||
module Common
|
||||
private
|
||||
def call_or_read(procedure_or_attributes, resource)
|
||||
case procedure_or_attributes
|
||||
when nil
|
||||
raise ArgumentError, "WTF is nil here? #{resource.inspect}"
|
||||
when Array
|
||||
attributes = procedure_or_attributes
|
||||
if attr = attributes.select { |a| resource.respond_to?(a) }.first
|
||||
resource.send attr
|
||||
end
|
||||
when Symbol
|
||||
attribute = procedure_or_attributes
|
||||
resource.send(attribute)
|
||||
when Proc
|
||||
procedure = procedure_or_attributes
|
||||
procedure.call(resource)
|
||||
else
|
||||
raise ArgumentError, "WTF is #{procedure_or_attributes.inspect} here? #{resource.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
require 'resource_feeder/common'
|
||||
|
||||
module ResourceFeeder
|
||||
module Rss
|
||||
include ResourceFeeder::Common
|
||||
include ActionController::Routing
|
||||
extend self
|
||||
|
||||
def render_rss_feed_for(resources, options = {})
|
||||
render :text => rss_feed_for(resources, options), :content_type => Mime::RSS
|
||||
end
|
||||
|
||||
def rss_feed_for(resources, options = {})
|
||||
xml = Builder::XmlMarkup.new(:indent => 2)
|
||||
|
||||
options[:feed] ||= {}
|
||||
options[:item] ||= {}
|
||||
options[:url_writer] ||= self
|
||||
|
||||
if options[:class] || resources.first
|
||||
klass = options[:class] || resources.first.class
|
||||
new_record = klass.new
|
||||
else
|
||||
options[:feed] = { :title => "Empty", :link => "http://example.com" }
|
||||
end
|
||||
use_content_encoded = options[:item].has_key?(:content_encoded)
|
||||
|
||||
options[:feed][:title] ||= klass.name.pluralize
|
||||
options[:feed][:link] ||= polymorphic_url(new_record, :controller => options[:url_writer].controller_name)
|
||||
options[:feed][:language] ||= "en-us"
|
||||
options[:feed][:ttl] ||= "40"
|
||||
|
||||
options[:item][:title] ||= [ :title, :subject, :headline, :name ]
|
||||
options[:item][:description] ||= [ :description, :body, :content ]
|
||||
options[:item][:pub_date] ||= [ :updated_at, :updated_on, :created_at, :created_on ]
|
||||
|
||||
resource_link = lambda { |r| polymorphic_url(r, :controller => options[:url_writer].controller_name) }
|
||||
|
||||
rss_root_attributes = { :version => 2.0 }
|
||||
rss_root_attributes.merge!("xmlns:content" => "http://purl.org/rss/1.0/modules/content/") if use_content_encoded
|
||||
|
||||
xml.instruct!
|
||||
|
||||
xml.rss(rss_root_attributes) do
|
||||
xml.channel do
|
||||
xml.title(options[:feed][:title])
|
||||
xml.link(options[:feed][:link])
|
||||
xml.description(options[:feed][:description]) if options[:feed][:description]
|
||||
xml.language(options[:feed][:language])
|
||||
xml.ttl(options[:feed][:ttl])
|
||||
|
||||
for resource in resources
|
||||
xml.item do
|
||||
xml.title(call_or_read(options[:item][:title], resource))
|
||||
xml.description(call_or_read(options[:item][:description], resource))
|
||||
if use_content_encoded then
|
||||
xml.content(:encoded) { xml.cdata!(call_or_read(options[:item][:content_encoded], resource)) }
|
||||
end
|
||||
xml.pubDate(call_or_read(options[:item][:pub_date], resource).to_s(:rfc822))
|
||||
xml.guid(call_or_read(options[:item][:guid] || options[:item][:link] || resource_link, resource))
|
||||
xml.link(call_or_read(options[:item][:link] || options[:item][:guid] || resource_link, resource))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
class AtomFeedTest < Test::Unit::TestCase
|
||||
attr_reader :request
|
||||
|
||||
def setup
|
||||
@request = OpenStruct.new
|
||||
@request.host_with_port = 'example.com'
|
||||
@records = Array.new(5).fill(Post.new)
|
||||
@records.each &:save
|
||||
end
|
||||
|
||||
def test_default_atom_feed
|
||||
atom_feed_for @records
|
||||
|
||||
assert_select 'feed' do
|
||||
assert_select '>title', 'Posts'
|
||||
assert_select '>id', "tag:#{request.host_with_port}:Posts"
|
||||
assert_select '>link' do
|
||||
assert_select "[rel='alternate']"
|
||||
assert_select "[type='text/html']"
|
||||
assert_select "[href='http://example.com/posts']"
|
||||
end
|
||||
assert_select 'entry', 5 do
|
||||
assert_select 'title', :text => 'feed title (title)'
|
||||
assert_select "content[type='html']", '<p>feed description (description)</p>'
|
||||
assert_select 'id', "tag:#{request.host_with_port},#{@records.first.created_at.xmlschema}:#{'http://example.com/posts/1'}"
|
||||
assert_select 'published', @records.first.created_at.xmlschema
|
||||
assert_select 'updated', @records.first.created_at.xmlschema
|
||||
assert_select 'link' do
|
||||
assert_select "[rel='alternate']"
|
||||
assert_select "[type='text/html']"
|
||||
assert_select "[href='http://example.com/posts/1']"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_allow_custom_feed_options
|
||||
atom_feed_for @records, :feed => { :title => 'Custom Posts', :link => '/posts', :description => 'stuff', :self => '/posts.atom' }
|
||||
|
||||
assert_select 'feed>title', 'Custom Posts'
|
||||
assert_select "feed>link[href='/posts']"
|
||||
assert_select 'feed>subtitle', 'stuff'
|
||||
assert_select 'feed>link' do
|
||||
assert_select "[rel='self']"
|
||||
assert_select "[type='application/atom+xml']"
|
||||
assert_select "[href='/posts.atom']"
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_allow_custom_item_attributes
|
||||
atom_feed_for @records, :item => { :title => :name, :description => :body, :pub_date => :create_date, :link => :id }
|
||||
|
||||
assert_select 'entry', 5 do
|
||||
assert_select 'title', :text => 'feed title (name)'
|
||||
assert_select "content[type='html']", '<p>feed description (body)</p>'
|
||||
assert_select 'published', (@records.first.created_at - 5.minutes).xmlschema
|
||||
assert_select 'updated', (@records.first.created_at - 5.minutes).xmlschema
|
||||
assert_select 'id', "tag:#{request.host_with_port},#{(@records.first.created_at - 5.minutes).xmlschema}:1"
|
||||
assert_select 'link' do
|
||||
assert_select "[rel='alternate']"
|
||||
assert_select "[type='text/html']"
|
||||
assert_select "[href='1']"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_allow_custom_item_attribute_blocks
|
||||
atom_feed_for @records, :item => { :title => lambda { |r| r.name }, :description => lambda { |r| r.body }, :pub_date => lambda { |r| r.create_date },
|
||||
:link => lambda { |r| "/#{r.created_at.to_i}" }, :guid => lambda { |r| r.created_at.to_i } }
|
||||
|
||||
assert_select 'entry', 5 do
|
||||
assert_select 'title', :text => 'feed title (name)'
|
||||
assert_select "content[type='html']", '<p>feed description (body)</p>'
|
||||
assert_select 'published', (@records.first.created_at - 5.minutes).xmlschema
|
||||
assert_select 'updated', (@records.first.created_at - 5.minutes).xmlschema
|
||||
assert_select 'id', /:\d+$/
|
||||
assert_select 'link' do
|
||||
assert_select "[rel='alternate']"
|
||||
assert_select "[type='text/html']"
|
||||
assert_select "[href=?]", /^\/\d+$/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
class RssFeedTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@records = Array.new(5).fill(Post.new)
|
||||
@records.each &:save
|
||||
end
|
||||
|
||||
def test_default_rss_feed
|
||||
rss_feed_for @records
|
||||
|
||||
assert_select 'rss[version="2.0"]' do
|
||||
assert_select 'channel' do
|
||||
assert_select '>title', 'Posts'
|
||||
assert_select '>link', 'http://example.com/posts'
|
||||
assert_select 'language', 'en-us'
|
||||
assert_select 'ttl', '40'
|
||||
end
|
||||
assert_select 'item', 5 do
|
||||
assert_select 'title', :text => 'feed title (title)'
|
||||
assert_select 'description', '<p>feed description (description)</p>'
|
||||
%w(guid link).each do |node|
|
||||
assert_select node, 'http://example.com/posts/1'
|
||||
end
|
||||
assert_select 'pubDate', @records.first.created_at.to_s(:rfc822)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_allow_custom_feed_options
|
||||
rss_feed_for @records, :feed => { :title => 'Custom Posts', :link => '/posts', :description => 'stuff', :language => 'en-gb', :ttl => '80' }
|
||||
|
||||
assert_select 'channel>title', 'Custom Posts'
|
||||
assert_select 'channel>link', '/posts'
|
||||
assert_select 'channel>description', 'stuff'
|
||||
assert_select 'channel>language', 'en-gb'
|
||||
assert_select 'channel>ttl', '80'
|
||||
end
|
||||
|
||||
def test_should_allow_custom_item_attributes
|
||||
rss_feed_for @records, :item => { :title => :name, :description => :body, :pub_date => :create_date, :link => :id }
|
||||
|
||||
assert_select 'item', 5 do
|
||||
assert_select 'title', :text => 'feed title (name)'
|
||||
assert_select 'description', '<p>feed description (body)</p>'
|
||||
assert_select 'pubDate', (@records.first.created_at - 5.minutes).to_s(:rfc822)
|
||||
assert_select 'link', '1'
|
||||
assert_select 'guid', '1'
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_allow_custom_item_attribute_blocks
|
||||
rss_feed_for @records, :item => { :title => lambda { |r| r.name }, :description => lambda { |r| r.body }, :pub_date => lambda { |r| r.create_date },
|
||||
:link => lambda { |r| "/#{r.created_at.to_i}" }, :guid => lambda { |r| r.created_at.to_i } }
|
||||
|
||||
assert_select 'item', 5 do
|
||||
assert_select 'title', :text => 'feed title (name)'
|
||||
assert_select 'description', '<p>feed description (body)</p>'
|
||||
assert_select 'pubDate', (@records.first.created_at - 5.minutes).to_s(:rfc822)
|
||||
end
|
||||
end
|
||||
|
||||
# note that assert_select isnt easily able to get elements that have xml namespaces (as it thinks they are
|
||||
# invalid html psuedo children), so we do some manual testing with the response body
|
||||
def test_should_allow_content_encoded_for_items
|
||||
rss_feed_for @records, :item => { :content_encoded => :full_html_body }
|
||||
|
||||
html_content = "<strong>Here is some <i>full</i> content, with out any excerpts</strong>"
|
||||
assert_equal 5, @response.body.scan("<![CDATA[#{html_content}]]>").size
|
||||
assert_select 'item', 5 do
|
||||
assert_select 'description + *', "<![CDATA[#{html_content}" # assert_select seems to strip the ending cdata tag
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_have_content_encoded_namespace_if_used
|
||||
rss_feed_for @records, :item => { :content_encoded => :full_html_body }
|
||||
assert_equal %[<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">\n],
|
||||
@response.body.grep(/<rss version="2\.0.*"/).first
|
||||
end
|
||||
|
||||
def test_should_have_normal_rss_root_without_content_encoded
|
||||
rss_feed_for @records
|
||||
assert_equal %[<rss version="2.0">\n],
|
||||
@response.body.grep(/<rss version="2\.0.*"/).first
|
||||
end
|
||||
|
||||
end
|
||||
64
backup.rails2.3/plugins/resource_feeder/test/test_helper.rb
Normal file
64
backup.rails2.3/plugins/resource_feeder/test/test_helper.rb
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
RAILS_ENV = 'test'
|
||||
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
|
||||
require 'action_controller/test_process'
|
||||
require 'breakpoint'
|
||||
require 'ostruct'
|
||||
|
||||
class Post
|
||||
attr_reader :id, :created_at
|
||||
def save; @id = 1; @created_at = Time.now.utc end
|
||||
def new_record?; @id.nil? end
|
||||
|
||||
[:title, :name].each do |attr_name|
|
||||
define_method attr_name do
|
||||
"feed title (#{attr_name})"
|
||||
end
|
||||
end
|
||||
|
||||
[:description, :body].each do |attr_name|
|
||||
define_method attr_name do
|
||||
"<p>feed description (#{attr_name})</p>"
|
||||
end
|
||||
end
|
||||
|
||||
def full_html_body
|
||||
"<strong>Here is some <i>full</i> content, with out any excerpts</strong>"
|
||||
end
|
||||
|
||||
def create_date
|
||||
@created_at - 5.minutes
|
||||
end
|
||||
end
|
||||
|
||||
class Test::Unit::TestCase
|
||||
include ResourceFeeder::Rss, ResourceFeeder::Atom
|
||||
|
||||
def render_feed(xml)
|
||||
@response = OpenStruct.new
|
||||
@response.headers = {'Content-Type' => 'text/xml'}
|
||||
@response.body = xml
|
||||
end
|
||||
|
||||
def rss_feed_for_with_ostruct(resources, options = {})
|
||||
render_feed rss_feed_for_without_ostruct(resources, options)
|
||||
end
|
||||
|
||||
def atom_feed_for_with_ostruct(resources, options = {})
|
||||
render_feed atom_feed_for_without_ostruct(resources, options)
|
||||
end
|
||||
|
||||
alias_method_chain :rss_feed_for, :ostruct
|
||||
alias_method_chain :atom_feed_for, :ostruct
|
||||
|
||||
def html_document
|
||||
@html_document ||= HTML::Document.new(@response.body, false, true)
|
||||
end
|
||||
|
||||
def posts_url
|
||||
"http://example.com/posts"
|
||||
end
|
||||
|
||||
def post_url(post)
|
||||
"http://example.com/posts/#{post.id}"
|
||||
end
|
||||
end
|
||||
5
backup.rails2.3/plugins/simple_ldap_authenticator/README
Normal file
5
backup.rails2.3/plugins/simple_ldap_authenticator/README
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
SimpleLdapAuthenticator
|
||||
=======================
|
||||
|
||||
Allows for simple authentication to an LDAP server with a minimum of
|
||||
configuration. See the RDoc for details.
|
||||
22
backup.rails2.3/plugins/simple_ldap_authenticator/Rakefile
Normal file
22
backup.rails2.3/plugins/simple_ldap_authenticator/Rakefile
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the simple_ldap_authenticator plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the simple_ldap_authenticator plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'SimpleLdapAuthenticator'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# Include hook code here
|
||||
#require 'simple_ldap_authenticator'
|
||||
|
|
@ -0,0 +1 @@
|
|||
# Install hook code here
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
# SimpleLdapAuthenticator
|
||||
#
|
||||
# This plugin supports both Ruby/LDAP and Net::LDAP, defaulting to Ruby/LDAP
|
||||
# if it is available. If both are installed and you want to force the use of
|
||||
# Net::LDAP, set SimpleLdapAuthenticator.ldap_library = 'net/ldap'.
|
||||
|
||||
# Allows for easily authenticating users via LDAP (or LDAPS). If authenticating
|
||||
# via LDAP to a server running on localhost, you should only have to configure
|
||||
# the login_format.
|
||||
#
|
||||
# Can be configured using the following accessors (with examples):
|
||||
# * login_format = '%s@domain.com' # Active Directory, OR
|
||||
# * login_format = 'cn=%s,cn=users,o=organization,c=us' # Other LDAP servers
|
||||
# * servers = ['dc1.domain.com', 'dc2.domain.com'] # names/addresses of LDAP servers to use
|
||||
# * use_ssl = true # for logging in via LDAPS
|
||||
# * port = 3289 # instead of 389 for LDAP or 636 for LDAPS
|
||||
# * logger = RAILS_DEFAULT_LOGGER # for logging authentication successes/failures
|
||||
#
|
||||
# The class is used as a global variable, you are not supposed to create an
|
||||
# instance of it. For example:
|
||||
#
|
||||
# require 'simple_ldap_authenticator'
|
||||
# SimpleLdapAuthenticator.servers = %w'dc1.domain.com dc2.domain.com'
|
||||
# SimpleLdapAuthenticator.use_ssl = true
|
||||
# SimpleLdapAuthenticator.login_format = '%s@domain.com'
|
||||
# SimpleLdapAuthenticator.logger = RAILS_DEFAULT_LOGGER
|
||||
# class LoginController < ApplicationController
|
||||
# def login
|
||||
# return redirect_to(:action=>'try_again') unless SimpleLdapAuthenticator.valid?(params[:username], params[:password])
|
||||
# session[:username] = params[:username]
|
||||
# end
|
||||
# end
|
||||
class SimpleLdapAuthenticator
|
||||
class << self
|
||||
@servers = ['127.0.0.1']
|
||||
@use_ssl = false
|
||||
@login_format = '%s'
|
||||
attr_accessor :servers, :use_ssl, :port, :login_format, :logger, :connection, :ldap_library
|
||||
|
||||
# Load the required LDAP library, either 'ldap' or 'net/ldap'
|
||||
def load_ldap_library
|
||||
return if @ldap_library_loaded
|
||||
if ldap_library
|
||||
if ldap_library == 'net/ldap'
|
||||
require 'net/ldap'
|
||||
else
|
||||
require 'ldap'
|
||||
require 'ldap/control'
|
||||
end
|
||||
else
|
||||
begin
|
||||
require 'ldap'
|
||||
require 'ldap/control'
|
||||
ldap_library = 'ldap'
|
||||
rescue LoadError
|
||||
require 'net/ldap'
|
||||
ldap_library = 'net/ldap'
|
||||
end
|
||||
end
|
||||
@ldap_library_loaded = true
|
||||
end
|
||||
|
||||
# The next LDAP server to which to connect
|
||||
def server
|
||||
servers[0]
|
||||
end
|
||||
|
||||
# The connection to the LDAP server. A single connection is made and the
|
||||
# connection is only changed if a server returns an error other than
|
||||
# invalid password.
|
||||
def connection
|
||||
return @connection if @connection
|
||||
load_ldap_library
|
||||
@connection = if ldap_library == 'net/ldap'
|
||||
Net::LDAP.new(:host=>server, :port=>(port), :encryption=>(:simple_tls if use_ssl))
|
||||
else
|
||||
(use_ssl ? LDAP::SSLConn : LDAP::Conn).new(server, port)
|
||||
end
|
||||
end
|
||||
|
||||
# The port to use. Defaults to 389 for LDAP and 636 for LDAPS.
|
||||
def port
|
||||
@port ||= use_ssl ? 636 : 389
|
||||
end
|
||||
|
||||
# Disconnect from current LDAP server and use a different LDAP server on the
|
||||
# next authentication attempt
|
||||
def switch_server
|
||||
self.connection = nil
|
||||
servers << servers.shift
|
||||
end
|
||||
|
||||
# Check the validity of a login/password combination
|
||||
def valid?(login, password)
|
||||
if ldap_library == 'net/ldap'
|
||||
connection.authenticate(login_format % login.to_s, password.to_s)
|
||||
begin
|
||||
if connection.bind
|
||||
logger.info("Authenticated #{login.to_s} by #{server}") if logger
|
||||
true
|
||||
else
|
||||
logger.info("Error attempting to authenticate #{login.to_s} by #{server}: #{connection.get_operation_result.code} #{connection.get_operation_result.message}") if logger
|
||||
switch_server unless connection.get_operation_result.code == 49
|
||||
false
|
||||
end
|
||||
rescue Net::LDAP::LdapError => error
|
||||
logger.info("Error attempting to authenticate #{login.to_s} by #{server}: #{error.message}") if logger
|
||||
switch_server
|
||||
false
|
||||
end
|
||||
else
|
||||
connection.unbind if connection.bound?
|
||||
begin
|
||||
connection.bind(login_format % login.to_s, password.to_s)
|
||||
connection.unbind
|
||||
logger.info("Authenticated #{login.to_s} by #{server}") if logger
|
||||
true
|
||||
rescue LDAP::ResultError => error
|
||||
connection.unbind if connection.bound?
|
||||
logger.info("Error attempting to authenticate #{login.to_s} by #{server}: #{error.message}") if logger
|
||||
switch_server unless error.message == 'Invalid credentials'
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# desc "Explaining what the task does"
|
||||
# task :simple_ldap_authenticator do
|
||||
# # Task goes here
|
||||
# end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
require 'test/unit'
|
||||
|
||||
class SimpleLdapAuthenticatorTest < Test::Unit::TestCase
|
||||
# Replace this with your real tests.
|
||||
def test_this_plugin
|
||||
flunk
|
||||
end
|
||||
end
|
||||
2
backup.rails2.3/plugins/skinny_spec/.gitignore
vendored
Normal file
2
backup.rails2.3/plugins/skinny_spec/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.DS_Store
|
||||
doc
|
||||
270
backup.rails2.3/plugins/skinny_spec/README.rdoc
Normal file
270
backup.rails2.3/plugins/skinny_spec/README.rdoc
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
= Skinny Spec
|
||||
|
||||
Skinny Spec is a collection of spec helper methods designed to help trim the fat and DRY up
|
||||
some of the bloat that sometimes results from properly specing your classes and templates.
|
||||
|
||||
== Requirements and Recommendations
|
||||
|
||||
Obviously you'll need to be using RSpec[http://github.com/dchelimsky/rspec/tree/master] and
|
||||
Rspec-Rails[http://github.com/dchelimsky/rspec-rails/tree/master] as your testing framework.
|
||||
|
||||
Skinny Spec was originally designed [and best enjoyed] if you're using
|
||||
Haml[http://github.com/nex3/haml/tree/master] and
|
||||
make_resourceful[http://github.com/rsl/make_resourceful/tree/master] but will default to
|
||||
ERb and a facsimile of Rails' default scaffolding [for the views and controllers, respectively]
|
||||
if Haml and/or make_resourceful are not available. I recommend using them though. :)
|
||||
|
||||
In addition, Skinny Spec uses Ruby2Ruby to make nicer expectation messages and you'll want to
|
||||
have that installed as well. It's not a dependency or anything but it <i>is</i> highly
|
||||
recommended.
|
||||
|
||||
== Setup
|
||||
|
||||
Once you've installed the plugin in your app's vendor/plugins folder, you're ready to rock!
|
||||
Skinny Spec includes itself into the proper RSpec classes so there's no configuration on your
|
||||
part. Sweet!
|
||||
|
||||
== Usage
|
||||
|
||||
The simplest way to use Skinny Specs is to generate a resource scaffold:
|
||||
|
||||
script/generate skinny_scaffold User
|
||||
|
||||
This command takes the usual complement of attribute definitions like
|
||||
<tt>script/generate scaffold</tt>. Then have a look at the generated files (particularly the
|
||||
specs) to see what's new and different with Skinny Spec.
|
||||
|
||||
=== Controller Specs
|
||||
|
||||
Let's look at the controller specs.
|
||||
|
||||
describe UsersController do
|
||||
describe "GET :index" do
|
||||
before(:each) do
|
||||
@users = stub_index(User)
|
||||
end
|
||||
|
||||
it_should_find_and_assign :users
|
||||
it_should_render :template, "index"
|
||||
end
|
||||
|
||||
# ...
|
||||
|
||||
describe "POST :create" do
|
||||
describe "when successful" do
|
||||
before(:each) do
|
||||
@user = stub_create(User)
|
||||
end
|
||||
|
||||
it_should_initialize_and_save :user
|
||||
it_should_redirect_to { user_url(@user) }
|
||||
end
|
||||
|
||||
# ...
|
||||
|
||||
First thing you should see is an example group for <tt>GET :index</tt>. That <tt>stub_index</tt> method there
|
||||
does a lot of work behind the curtain. I'll leave it up to you to check the documentation for it
|
||||
(and its brothers and sister methods like <tt>stub_new</tt>) but I will point out that the
|
||||
methods named <tt>stub_<i>controller_method</i></tt> should only be used for stubbing and
|
||||
mocking the main object of the method. To create mocks for other ancillary objects, please
|
||||
use <tt>stub_find_all</tt>, <tt>stub_find_one</tt>, and <tt>stub_initialize</tt>. The reason
|
||||
for this is because the former methods actually save us a step by defining an implicit
|
||||
controller method request. If you add a new method to your resource routing, you'll want to
|
||||
use the helper method <tt>define_request</tt> in those example groups to define an explicit
|
||||
request, like so:
|
||||
|
||||
describe "PUT :demote" do
|
||||
define_request { put :demote }
|
||||
|
||||
# ...
|
||||
end
|
||||
|
||||
You can also define a method called <tt>shared_request</tt> to "share" a
|
||||
<tt>define_request</tt> across nested describe blocks, like so:
|
||||
|
||||
describe "POST :create" do
|
||||
def shared_request
|
||||
post :create
|
||||
end
|
||||
|
||||
describe "when successful" do
|
||||
# ...
|
||||
end
|
||||
|
||||
describe "when unsuccessful" do
|
||||
# ...
|
||||
end
|
||||
end
|
||||
|
||||
Note: When you're adding longer, more complicated controller specs you can still leverage
|
||||
implicit and explicit requests by calling <tt>do_request</tt> in your spec as in the following
|
||||
example:
|
||||
|
||||
# Note this controller is UsersController and _not_ CategoriesController
|
||||
# and that loading the categories isn't part of the default actions
|
||||
# and cannot use the <tt>stub_<i>controller_method</i></tt> helpers
|
||||
# [which create implicit requests based on the controller method in the name]
|
||||
# but uses <tt>stub_find_all</tt> instead
|
||||
describe "GET :new" do
|
||||
before(:each) do
|
||||
@user = stub_new(User)
|
||||
@categories = stub_find_all(Category)
|
||||
end
|
||||
|
||||
# ...
|
||||
|
||||
it "should preload categories" do
|
||||
Category.should_receive(:find).with(:all)
|
||||
do_request
|
||||
end
|
||||
|
||||
it "should assign @categories" do
|
||||
do_request
|
||||
assigns[:categories].should == @categories
|
||||
end
|
||||
end
|
||||
|
||||
Finally we get to the meat of the spec and of Skinny Specs itself: the actual expectations.
|
||||
The first thing you'll notice is the use of example group (read: "describe" block) level methods
|
||||
instead of the usual example (read: "it") blocks. Using this helper at the example group level
|
||||
saves us three lines over using an example block. (If this isn't significant to you, this is
|
||||
probably the wrong plugin for you as well. Sorry.) Note that none of these methods use the
|
||||
instance variables defined in the "before" block because they are all nil at the example block
|
||||
level. Let's look at a sample method to see how it works:
|
||||
|
||||
it_should_find_and_assign :users
|
||||
|
||||
This actually wraps two different expectations: one that <tt>User.should_receive(:find).with(:all)</tt>
|
||||
and another that the instance variable <tt>@users</tt> is assigned with the return value from that finder call.
|
||||
If you need to add more detailed arguments to the find, you can easily break this into two different
|
||||
expectations like:
|
||||
|
||||
it_should_find :users, :limit => 2
|
||||
it_should_assign :users
|
||||
|
||||
See the documentation for the <tt>it_should_find</tt> for more information. You might have guessed that
|
||||
<tt>it_should_initialize_assign</tt> and <tt>it_should_render_template</tt> work in a similar
|
||||
fashion and you'd be right. Again, see the documentation for these individual methods for more
|
||||
information. Lots of information in those docs.
|
||||
|
||||
A useful helper method that doesn't appear in any of the scaffolding is <tt>with_default_restful_actions</tt>
|
||||
which takes a block and evaluates it for each of the RESTful controller actions. Very useful for
|
||||
spec'ing that these methods redirect to the login page when the user isn't logged in, for example. This
|
||||
method is designed to be used inside an example like so:
|
||||
|
||||
describe "when not logged in" do
|
||||
it "should redirect all requests to the login page" do
|
||||
with_default_restful_actions do
|
||||
response.should redirect_to(login_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Before we're through with the controller specs, let me point out one more important detail. In
|
||||
order to use <tt>it_should_redirect_to</tt> we have to send the routing inside a block argument
|
||||
there so it can be evaluated in the example context instead of the example group, where it
|
||||
completely blows up. This methodology is used anywhere routing is referred to in a "skinny",
|
||||
example group level spec.
|
||||
|
||||
=== View Specs
|
||||
|
||||
Now let's move to the view specs!
|
||||
|
||||
describe "/users/form.html.haml" do
|
||||
before(:each) do
|
||||
@user = mock_and_assign(User, :stub => {
|
||||
:name => "foo",
|
||||
:birthday => 1.week.ago,
|
||||
:adult => false
|
||||
})
|
||||
end
|
||||
|
||||
it_should_have_form_for :user
|
||||
|
||||
it_should_allow_editing :user, :name
|
||||
it_should_allow_editing :user, :birthday
|
||||
it_should_allow_editing :user, :adult
|
||||
|
||||
it_should_link_to_show :user
|
||||
it_should_link_to { users_path }
|
||||
end
|
||||
|
||||
Like the special <tt>stub_index</tt> methods in the controller
|
||||
specs, the view specs have a shorthand mock and stub helpers: <tt>mock_and_assign</tt> and
|
||||
<tt>mock_and_assign_collection</tt>. These are well documented so please check them out.
|
||||
|
||||
There are also some really nice helper methods that I'd like point out. First is
|
||||
<tt>it_should_have_form_for</tt>. This is a really good convenience wrapper that basically wraps
|
||||
the much longer:
|
||||
|
||||
it "should use form_for to generate the proper form action and options" do
|
||||
template.should_receive(:form_for).with(@user)
|
||||
do_render
|
||||
end
|
||||
|
||||
Next up is the <tt>it_should_allow_editing</tt> helper. I love this method the most because it
|
||||
really helps DRY up that view spec while at the same time being amazingly unbrittle. Instead of
|
||||
creating an expectation for a specific form element, this method creates a generalized expectation
|
||||
that there's a form element with the <tt>name</tt> attribute set in such away that it will
|
||||
generate the proper <tt>params</tt> to use in the controller to edit or create the instance.
|
||||
Check out the docs and the source for more information on this. Also check out
|
||||
<tt>it_should_have_form_element_for</tt> which is roughly equivalent for those times when you use
|
||||
<tt>form_tag</tt> instead.
|
||||
|
||||
Finally let's look at those <tt>it_should_link_to_<i>controller_method</i></tt> helpers.
|
||||
These methods (and there's one each for the controller methods
|
||||
<tt>new</tt>, <tt>edit</tt>, <tt>show</tt>, and <tt>delete</tt>) point to instance variables
|
||||
which you should be created in the "before" blocks with <tt>mock_and_assign</tt>. The other is
|
||||
<tt>it_should_allow_editing</tt> which is likewise covered extensively in the documentation and
|
||||
I will just point out here that, like <tt>it_should_link_to_edit</tt> and such, it takes a
|
||||
symbol for the name of the instance variable it refers to and <i>additionally</i> takes
|
||||
a symbol for the name of the attribute to be edited.
|
||||
|
||||
Also note that, when constructing a long form example, instead of defining an instance variable
|
||||
for the name of the template and calling <tt>render @that_template</tt> you can simply call
|
||||
<tt>do_render</tt> which takes the name of the template from the outermost example group where
|
||||
it is customarily stated.
|
||||
|
||||
=== Model Specs
|
||||
|
||||
Skinny Spec adds a matcher for the various ActiveRecord associations. On the example group level
|
||||
you call them like:
|
||||
|
||||
it_should_belong_to :manager
|
||||
it_should_have_many :clients
|
||||
|
||||
Within an example you can call them on either the class or the instance setup in the
|
||||
"before" block. These are equivalent:
|
||||
|
||||
@user.should belong_to(:group)
|
||||
User.should belong_to(:group)
|
||||
|
||||
I've also added some very basic validation helpers like <tt>it_should_validate_presence_of</tt>,
|
||||
<tt>it_should_validate_uniqueness_of</tt>, <tt>it_should_not_mass_assign</tt>. Please consult
|
||||
the documentation for more information.
|
||||
|
||||
== Miscellaneous Notes
|
||||
|
||||
In the scaffolding, I have used my own idiomatic Rails usage:
|
||||
|
||||
* All controller actions which use HTML forms [<tt>new</tt>, <tt>edit</tt>, etc] use a shared
|
||||
form and leverage <tt>form_for</tt> to its fullest by letting it create the appropriate
|
||||
action and options.
|
||||
* Some instances where you might expect link_to are button_to. This is to provide a common
|
||||
interface element which can be styled the same instead of a mishmash of links and buttons and
|
||||
inputs everywhere. To take full advantage of this, I usually override many of Rails' default
|
||||
helpers with custom ones that all use actual HTML <tt>BUTTON</tt> elements which are much
|
||||
easier to style than "button" typed <tt>INPUT</tt>. I've provided a text file in the
|
||||
"additional" folder of this plugin which you can use in your ApplicationHelper. (I also
|
||||
provide an optional override helper for the <tt>label</tt> method which uses
|
||||
<tt>#titleize</tt> instead of <tt>humanize</tt> for stylistic reasons).
|
||||
* Probably more that I can't think of.
|
||||
|
||||
== Credits and Thanks
|
||||
|
||||
Sections of this code were taken from or inspired by Rick Olsen's
|
||||
rspec_on_rails_on_crack[http://github.com/technoweenie/rspec_on_rails_on_crack/tree/master].
|
||||
Also thanks and props to Hampton Catlin and Nathan Weizenbaum for the lovely and imminently useable
|
||||
Haml and make_resourceful. Also also praises and glory to David Chelimsky and the Rspec crew.
|
||||
|
||||
Also thanks to Don Petersen, Nicolas Mérouze, Mikkel Malmberg, and Brandan Lennox for their suggestions and fixes.
|
||||
11
backup.rails2.3/plugins/skinny_spec/Rakefile
Normal file
11
backup.rails2.3/plugins/skinny_spec/Rakefile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
require 'rake'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Generate documentation for the Skinny Spec plugin'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = 'Skinny Spec'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# Please insert these into your ApplicationHelper
|
||||
|
||||
# Replacement for Rails' default submit_tag helper
|
||||
# using HTML button element rather than HTML input element
|
||||
def submit_tag(text, options = {})
|
||||
content_tag :button, text, options.merge(:type => :submit)
|
||||
end
|
||||
|
||||
# Replacement for Rails' default button_to helper
|
||||
# using HTML button element rather than HTML input element
|
||||
def button_to(name, options = {}, html_options = {})
|
||||
html_options = html_options.stringify_keys
|
||||
convert_boolean_attributes!(html_options, %w( disabled ))
|
||||
|
||||
method_tag = ''
|
||||
if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
|
||||
method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
|
||||
end
|
||||
|
||||
form_method = method.to_s == 'get' ? 'get' : 'post'
|
||||
|
||||
request_token_tag = ''
|
||||
if form_method == 'post' && protect_against_forgery?
|
||||
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
|
||||
end
|
||||
|
||||
if confirm = html_options.delete("confirm")
|
||||
html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
|
||||
end
|
||||
|
||||
url = options.is_a?(String) ? options : self.url_for(options)
|
||||
name ||= url
|
||||
|
||||
html_options.merge!("type" => "submit", "value" => name)
|
||||
|
||||
"<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
|
||||
method_tag + content_tag("button", name, html_options) + request_token_tag + "</div></form>"
|
||||
end
|
||||
|
||||
# Replacement for Rails' default button_to_function helper
|
||||
# using HTML button element rather than HTML input element
|
||||
def button_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!
|
||||
function = args[0] || ''
|
||||
|
||||
html_options.symbolize_keys!
|
||||
function = update_page(&block) if block_given?
|
||||
content_tag(:button, name, html_options.merge({
|
||||
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
|
||||
}))
|
||||
end
|
||||
|
||||
# Replacement for Rails' default label helper
|
||||
# using String#titleize rather than String#humanize
|
||||
def label(object_name, method, text = nil, options = {})
|
||||
text ||= method.to_s[].titleize
|
||||
super
|
||||
end
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
class SkinnyScaffoldGenerator < Rails::Generator::NamedBase
|
||||
attr_reader :controller_class_path, :controller_file_path, :controller_class_nesting,
|
||||
:controller_class_nesting_depth, :controller_class_name, :controller_underscore_name,
|
||||
:controller_plural_name, :template_language
|
||||
alias_method :controller_file_name, :controller_underscore_name
|
||||
alias_method :controller_singular_name, :controller_file_name
|
||||
alias_method :controller_table_name, :controller_plural_name
|
||||
|
||||
default_options :skip_migration => false
|
||||
|
||||
def initialize(runtime_args, runtime_options = {})
|
||||
super
|
||||
|
||||
base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@name.pluralize)
|
||||
@controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name)
|
||||
|
||||
if @controller_class_nesting.empty?
|
||||
@controller_class_name = @controller_class_name_without_nesting
|
||||
else
|
||||
@controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
|
||||
end
|
||||
end
|
||||
|
||||
def manifest
|
||||
record do |m|
|
||||
# Check for class naming collisions
|
||||
m.class_collisions controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper"
|
||||
m.class_collisions class_path, "#{class_name}"
|
||||
|
||||
# # Controller, helper, and views directories
|
||||
m.directory File.join('app', 'views', controller_class_path, controller_file_name)
|
||||
m.directory File.join('spec', 'views', controller_class_path, controller_file_name)
|
||||
m.directory File.join('app', 'helpers', controller_class_path)
|
||||
m.directory File.join('spec', 'helpers', controller_class_path)
|
||||
m.directory File.join('app', 'controllers', controller_class_path)
|
||||
m.directory File.join('spec', 'controllers', controller_class_path)
|
||||
m.directory File.join('app', 'models', class_path)
|
||||
m.directory File.join('spec', 'models', class_path)
|
||||
|
||||
# Views
|
||||
@template_language = defined?(Haml) ? "haml" : "erb"
|
||||
%w{index show form}.each do |action|
|
||||
m.template "#{action}.html.#{template_language}",
|
||||
File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.#{template_language}")
|
||||
m.template "#{action}.html_spec.rb",
|
||||
File.join('spec/views', controller_class_path, controller_file_name, "#{action}.html.#{template_language}_spec.rb")
|
||||
end
|
||||
m.template "index_partial.html.#{template_language}",
|
||||
File.join('app/views', controller_class_path, controller_file_name, "_#{file_name}.html.#{template_language}")
|
||||
m.template 'index_partial.html_spec.rb',
|
||||
File.join('spec/views', controller_class_path, controller_file_name, "_#{file_name}.html.#{template_language}_spec.rb")
|
||||
|
||||
# Helper
|
||||
m.template 'helper.rb',
|
||||
File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb")
|
||||
m.template 'helper_spec.rb',
|
||||
File.join('spec/helpers', controller_class_path, "#{controller_file_name}_helper_spec.rb")
|
||||
|
||||
# Controller
|
||||
m.template 'controller.rb',
|
||||
File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
|
||||
m.template 'controller_spec.rb',
|
||||
File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb")
|
||||
|
||||
# Model
|
||||
m.template 'model.rb',
|
||||
File.join('app/models', class_path, "#{file_name}.rb")
|
||||
m.template 'model_spec.rb',
|
||||
File.join('spec/models', class_path, "#{file_name}_spec.rb")
|
||||
|
||||
# Routing
|
||||
m.route_resources controller_file_name
|
||||
|
||||
unless options[:skip_migration]
|
||||
m.migration_template(
|
||||
'migration.rb', 'db/migrate',
|
||||
:assigns => {
|
||||
:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}",
|
||||
:attributes => attributes
|
||||
},
|
||||
:migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def banner
|
||||
"Usage: #{$0} skinny_scaffold ModelName [field:type, field:type]"
|
||||
end
|
||||
|
||||
def add_options!(opt)
|
||||
opt.separator ''
|
||||
opt.separator 'Options:'
|
||||
opt.on("--skip-migration",
|
||||
"Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
|
||||
end
|
||||
|
||||
def model_name
|
||||
class_name.demodulize
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
class <%= controller_class_name %>Controller < ApplicationController
|
||||
<%- if defined?(Resourceful::Maker) -%>
|
||||
make_resourceful do
|
||||
actions :all
|
||||
|
||||
# Let's get the most use from form_for and share a single form here!
|
||||
response_for :new, :edit do
|
||||
render :template => "<%= plural_name %>/form"
|
||||
end
|
||||
|
||||
response_for :create_fails, :update_fails do
|
||||
flash[:error] = "There was a problem!"
|
||||
render :template => "<%= plural_name %>/form"
|
||||
end
|
||||
end
|
||||
<%- else -%>
|
||||
# GET /<%= table_name %>
|
||||
# GET /<%= table_name %>.xml
|
||||
def index
|
||||
@<%= table_name %> = <%= class_name %>.find(:all)
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
format.xml { render :xml => @<%= table_name %> }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /<%= table_name %>/1
|
||||
# GET /<%= table_name %>/1.xml
|
||||
def show
|
||||
@<%= file_name %> = <%= class_name %>.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html # show.html.erb
|
||||
format.xml { render :xml => @<%= file_name %> }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /<%= table_name %>/new
|
||||
# GET /<%= table_name %>/new.xml
|
||||
def new
|
||||
@<%= file_name %> = <%= class_name %>.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :template => "<%= plural_name %>/form" }
|
||||
format.xml { render :xml => @<%= file_name %> }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /<%= table_name %>/1/edit
|
||||
def edit
|
||||
@<%= file_name %> = <%= class_name %>.find(params[:id])
|
||||
render :template => "<%= plural_name %>/form"
|
||||
end
|
||||
|
||||
# POST /<%= table_name %>
|
||||
# POST /<%= table_name %>.xml
|
||||
def create
|
||||
@<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>])
|
||||
|
||||
respond_to do |format|
|
||||
if @<%= file_name %>.save
|
||||
flash[:notice] = '<%= class_name %> was successfully created.'
|
||||
format.html { redirect_to(@<%= file_name %>) }
|
||||
format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> }
|
||||
else
|
||||
flash.now[:error] = '<%= class_name %> could not be created.'
|
||||
format.html { render :template => "<%= plural_name %>/form" }
|
||||
format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /<%= table_name %>/1
|
||||
# PUT /<%= table_name %>/1.xml
|
||||
def update
|
||||
@<%= file_name %> = <%= class_name %>.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @<%= file_name %>.update_attributes(params[:<%= file_name %>])
|
||||
flash[:notice] = '<%= class_name %> was successfully updated.'
|
||||
format.html { redirect_to(@<%= file_name %>) }
|
||||
format.xml { head :ok }
|
||||
else
|
||||
flash.now[:error] = '<%= class_name %> could not be created.'
|
||||
format.html { render :template => "<%= plural_name %>/form" }
|
||||
format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /<%= table_name %>/1
|
||||
# DELETE /<%= table_name %>/1.xml
|
||||
def destroy
|
||||
@<%= file_name %> = <%= class_name %>.find(params[:id])
|
||||
@<%= file_name %>.destroy
|
||||
|
||||
respond_to do |format|
|
||||
flash[:notice] = '<%= class_name %> was successfully deleted.'
|
||||
format.html { redirect_to(<%= table_name %>_url) }
|
||||
format.xml { head :ok }
|
||||
end
|
||||
end
|
||||
<%- end -%>
|
||||
end
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
require File.dirname(__FILE__) + '/../spec_helper'
|
||||
|
||||
describe <%= controller_class_name %>Controller do
|
||||
describe "GET :index" do
|
||||
before(:each) do
|
||||
@<%= plural_name %> = stub_index(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_find_and_assign :<%= plural_name %>
|
||||
it_should_render_template "index"
|
||||
end
|
||||
|
||||
describe "GET :new" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_new(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_initialize_and_assign :<%= singular_name %>
|
||||
it_should_render_template "form"
|
||||
end
|
||||
|
||||
describe "POST :create" do
|
||||
describe "when successful" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_create(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_initialize_and_save :<%= singular_name %>
|
||||
it_should_set_flash :notice
|
||||
it_should_redirect_to { <%= singular_name %>_url(@<%= singular_name %>) }
|
||||
end
|
||||
|
||||
describe "when unsuccessful" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_create(<%= class_name %>, :return => :false)
|
||||
end
|
||||
|
||||
it_should_initialize_and_assign :<%= singular_name %>
|
||||
it_should_set_flash :error
|
||||
it_should_render_template "form"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET :show" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_show(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_find_and_assign :<%= singular_name %>
|
||||
it_should_render_template "show"
|
||||
end
|
||||
|
||||
describe "GET :edit" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_edit(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_find_and_assign :<%= singular_name %>
|
||||
it_should_render_template "form"
|
||||
end
|
||||
|
||||
describe "PUT :update" do
|
||||
describe "when successful" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_update(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_find_and_update :<%= singular_name %>
|
||||
it_should_set_flash :notice
|
||||
it_should_redirect_to { <%= singular_name %>_url(@<%= singular_name %>) }
|
||||
end
|
||||
|
||||
describe "when unsuccessful" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_update(<%= class_name %>, :return => :false)
|
||||
end
|
||||
|
||||
it_should_find_and_assign :<%= singular_name %>
|
||||
it_should_set_flash :error
|
||||
it_should_render_template "form"
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE :destroy" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = stub_destroy(<%= class_name %>)
|
||||
end
|
||||
|
||||
it_should_find_and_destroy :<%= singular_name %>
|
||||
it_should_set_flash :notice
|
||||
it_should_redirect_to { <%= plural_name %>_url }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<h1><%= singular_name %>.new_record? ? "New" : "Edit" %> <%= model_name %></h1>
|
||||
<%% form_for(@<%= singular_name %>) do |f| %>
|
||||
<div id="form_errors">
|
||||
<%%= f.error_messages %>
|
||||
</div>
|
||||
<%- if attributes.blank? -%>
|
||||
<p>Add your form elements here, please!</p>
|
||||
<%- else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
<p>
|
||||
<%%= f.label :<%= attribute.name %> %><br />
|
||||
<%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
|
||||
</p>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<div id="commands">
|
||||
<%%= submit_tag "Save" %>
|
||||
<div id="navigation_commands">
|
||||
<%% unless @<%= singular_name %>.new_record? -%>
|
||||
<%%= button_to "Show", <%= singular_name %>_path(@<%= singular_name %>), :method => "get", :title => "Show <%= singular_name %>. Unsaved changes will be lost." %>
|
||||
<%% end -%>
|
||||
<%%= button_to "Back to List", <%= plural_name %>_path, :class => "cancel", :method => "get", :title => "Return to <%= singular_name %> list without saving changes" %>
|
||||
</div>
|
||||
</div>
|
||||
<%% end -%>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
%h1== #{@<%= singular_name %>.new_record? ? "New" : "Edit"} #{<%= model_name %>}
|
||||
- form_for @<%= singular_name %> do |f|
|
||||
#form_errors= f.error_messages
|
||||
<% if attributes.blank? -%>
|
||||
%p Add your form elements here, please!
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
%p
|
||||
= f.label :<%= attribute.name %>
|
||||
= f.<%= attribute.field_type %> :<%= attribute.name %>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
#commands
|
||||
= submit_tag "Save"
|
||||
#navigation_commands
|
||||
- unless @<%= singular_name %>.new_record?
|
||||
= button_to "Show", <%= singular_name %>_path(@<%= singular_name %>), :method => "get", :title => "Show <%= singular_name %>. Unsaved changes will be lost."
|
||||
= button_to "Back to List", <%= plural_name %>_path, :class => "cancel", :method => "get", :title => "Return to <%= singular_name %> list without saving changes"
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper'
|
||||
|
||||
describe "<%= File.join(controller_class_path, controller_singular_name) %>/form.html.<%= template_language %>" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => {
|
||||
<% if attributes.blank? -%>
|
||||
# Add your stub attributes and return values here like:
|
||||
# :name => "Foo", :address => "815 Oceanic Drive"
|
||||
<% else -%>
|
||||
<%- attributes.each_with_index do |attribute, index| -%>
|
||||
<%- case attribute.type when :string, :text -%>
|
||||
:<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :integer, :float, :decimal -%>
|
||||
:<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :boolean -%>
|
||||
:<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :date, :datetime, :time, :timestamp -%>
|
||||
:<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- else -%>
|
||||
:<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %> # Could not determine valid attribute
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
})
|
||||
end
|
||||
|
||||
it_should_have_form_for :<%= singular_name %>
|
||||
<% if attributes.blank? -%>
|
||||
# Add specs for editing attributes here, please! Like this:
|
||||
#
|
||||
# it_should_allow_editing :<%= singular_name %>, :foo
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
it_should_allow_editing :<%= singular_name %>, :<%= attribute.name %>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
|
||||
it_should_link_to_show :<%= singular_name %>
|
||||
it_should_link_to { <%= plural_name %>_path }
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
module <%= controller_class_name %>Helper
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../spec_helper'
|
||||
|
||||
describe <%= controller_class_name %>Helper do
|
||||
# Add your specs here or remove this file completely, please!
|
||||
end
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<h1><%= model_name %> List</h1>
|
||||
<table>
|
||||
<%%- if @<%= plural_name %>.empty? -%>
|
||||
<tbody>
|
||||
<tr class="empty">
|
||||
<td>There are no <%= plural_name.humanize.downcase %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<%%- else -%>
|
||||
<thead>
|
||||
<tr>
|
||||
<%- if attributes.blank? -%>
|
||||
<th><!-- Generic display column --></th>
|
||||
<%- else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
<th><%= attribute.name.titleize %></th>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<th class="show"><!-- "Show" link column --></th>
|
||||
<th class="edit"><!-- "Edit" link column --></th>
|
||||
<th class="delete"><!-- "Delete" link column --></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%%= render :partial => @<%= plural_name %> %>
|
||||
</tbody>
|
||||
<%%- end -%>
|
||||
</table>
|
||||
<div id="commands">
|
||||
<%%= button_to "New <%= singular_name.titleize %>", new_<%= singular_name %>_path, :method => "get" %>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
%h1 <%= model_name %> List
|
||||
%table
|
||||
- if @<%= plural_name %>.empty?
|
||||
%tbody
|
||||
%tr.empty
|
||||
%td== There are no <%= plural_name.humanize.downcase %>
|
||||
- else
|
||||
%thead
|
||||
%tr
|
||||
<% if attributes.blank? -%>
|
||||
%th= # Generic display column
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
%th <%= attribute.name.titleize %>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
%th.show= # 'Show' link column
|
||||
%th.edit= # 'Edit' link column
|
||||
%th.delete= # 'Delete' link column
|
||||
%tbody
|
||||
= render :partial => @<%= plural_name %>
|
||||
#commands
|
||||
= button_to "New <%= singular_name.titleize %>", new_<%= singular_name %>_path, :method => "get"
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper'
|
||||
|
||||
describe "<%= File.join(controller_class_path, controller_singular_name) %>/index.html.<%= template_language %>" do
|
||||
before(:each) do
|
||||
@<%= plural_name %> = mock_and_assign_collection(<%= model_name %>)
|
||||
template.stub! :render
|
||||
end
|
||||
|
||||
it "should render :partial => @<%= plural_name %>" do
|
||||
template.should_receive(:render).with(:partial => @<%= plural_name %>)
|
||||
do_render
|
||||
end
|
||||
|
||||
it_should_link_to_new :<%= singular_name %>
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<tr class="<%%= cycle("odd", "even") %>">
|
||||
<% if attributes.blank? -%>
|
||||
<td><%= model_name %> #<%%= <%= singular_name %>.id %></td>
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
<td><%%=h <%= singular_name %>.<%= attribute.name %> %></td>
|
||||
<%- end %>
|
||||
<% end -%>
|
||||
<td class="show"><%%= button_to "Show", <%= singular_name %>, :method => "get" %></td>
|
||||
<td class="edit"><%%= button_to "Edit", edit_<%= singular_name %>_path(<%= singular_name %>), :method => "get" %></td>
|
||||
<td class="delete"><%%= button_to "Delete", <%= singular_name %>, :method => "delete" %></td>
|
||||
</tr>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
%tr{:class => cycle("odd", "even")}
|
||||
<% if attributes.blank? -%>
|
||||
%td== <%= model_name %> #{<%= singular_name %>.id}
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
%td=h <%= singular_name %>.<%= attribute.name %>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
%td.show= button_to "Show", <%= singular_name %>_path(<%= singular_name %>), :method => "get"
|
||||
%td.edit= button_to "Edit", edit_<%= singular_name %>_path(<%= singular_name %>), :method => "get"
|
||||
%td.delete= button_to "Delete", <%= singular_name %>, :method => "delete"
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper'
|
||||
|
||||
describe "<%= File.join(controller_class_path, controller_singular_name) %>/_<%= singular_name %>.html.<%= template_language %>" do
|
||||
before(:each) do
|
||||
@<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => {
|
||||
<% if attributes.blank? -%>
|
||||
# Add your stub attributes and return values here like:
|
||||
# :name => "Foo", :created_at => 1.week.ago, :updated_at => nil
|
||||
<% else -%>
|
||||
<%- attributes.each_with_index do |attribute, index| -%>
|
||||
<%- case attribute.type when :string, :text -%>
|
||||
:<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :integer, :float, :decimal -%>
|
||||
:<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :boolean -%>
|
||||
:<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :date, :datetime, :time, :timestamp -%>
|
||||
:<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- else -%>
|
||||
:<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
})
|
||||
template.stub!(:<%= singular_name %>).and_return(@<%= singular_name %>)
|
||||
end
|
||||
|
||||
it_should_link_to_show :<%= singular_name %>
|
||||
it_should_link_to_edit :<%= singular_name %>
|
||||
it_should_link_to_delete :<%= singular_name %>
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
class <%= migration_name %> < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :<%= table_name %>, :force => true do |t|
|
||||
<% attributes.each do |attribute| -%>
|
||||
t.column :<%= attribute.name %>, :<%= attribute.type %>
|
||||
<% end -%>
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :<%= table_name %>
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
class <%= class_name %> < ActiveRecord::Base
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../spec_helper'
|
||||
|
||||
describe <%= class_name %> do
|
||||
def valid_attributes(args = {})
|
||||
{
|
||||
# Add valid attributes for building your model instances here!
|
||||
}.merge(args)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@<%= singular_name %> = <%= class_name %>.new
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
@<%= singular_name %>.destroy
|
||||
end
|
||||
|
||||
# Add your model specs here, please!
|
||||
# And don't forget about the association matchers built-in to skinny_spec like:
|
||||
#
|
||||
# it_should_have_many :foos
|
||||
# it_should_validate_presence_of :bar
|
||||
#
|
||||
# Check out the docs for more information.
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<h1>Show <%= model_name %></h1>
|
||||
<% if attributes.blank? -%>
|
||||
<p>Add your customized markup here, please!</p>
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
<p>
|
||||
<label><%= attribute.name.titleize %>:</label>
|
||||
<%%=h @<%= singular_name %>.<%= attribute.name %> %>
|
||||
</p>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
<div id="commands">
|
||||
<%%= button_to "Edit", edit_<%= singular_name %>_path(@<%= singular_name %>), :method => "get" %>
|
||||
<%%= button_to "Back to List", <%= plural_name %>_path, :method => "get" %>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
%h1== Show #{<%= model_name %>}
|
||||
<% if attributes.blank? -%>
|
||||
%p Add your customized markup here, please!
|
||||
<% else -%>
|
||||
<%- attributes.each do |attribute| -%>
|
||||
%p
|
||||
%label <%= attribute.name.titleize %>:
|
||||
=h @<%= singular_name %>.<%= attribute.name %>
|
||||
<%- end -%>
|
||||
<% end -%>
|
||||
#commands
|
||||
= button_to "Edit", edit_<%= singular_name %>_path(@<%= singular_name %>), :method => "get"
|
||||
= button_to "Back to List", <%= plural_name %>_path, :method => "get"
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper'
|
||||
|
||||
describe "<%= File.join(controller_class_path, controller_singular_name) %>/show.html.<%= template_language %>" do
|
||||
before(:each) do
|
||||
<% if attributes.blank? -%>
|
||||
@<%= singular_name %> = mock_and_assign(<%= model_name %>)
|
||||
<% else -%>
|
||||
@<%= singular_name %> = mock_and_assign(<%= model_name %>, :stub => {
|
||||
<%- attributes.each_with_index do |attribute, index| -%>
|
||||
<%- case attribute.type when :string, :text -%>
|
||||
:<%= attribute.name %> => "foo"<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :integer, :float, :decimal -%>
|
||||
:<%= attribute.name %> => 815<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :boolean -%>
|
||||
:<%= attribute.name %> => false<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- when :date, :datetime, :time, :timestamp -%>
|
||||
:<%= attribute.name %> => 1.week.ago<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- else -%>
|
||||
:<%= attribute.name %> => nil<%= index < attributes.size - 1 ? "," : "" %>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
})
|
||||
<% end -%>
|
||||
end
|
||||
|
||||
# Add your specs here, please! But remember not to make them brittle
|
||||
# by specing specing specific HTML elements and classes.
|
||||
|
||||
it_should_link_to_edit :<%= singular_name %>
|
||||
it_should_link_to { <%= plural_name %>_path }
|
||||
end
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
module LuckySneaks
|
||||
# These methods are mostly just called internally by various other spec helper
|
||||
# methods but you're welcome to use them as needed in your own specs.
|
||||
module CommonSpecHelpers
|
||||
# Stubs out Time.now and returns value to use when comparing it. Example:
|
||||
#
|
||||
# time_now = stub_time_now
|
||||
# @foo.some_method_that_resets_updated_at
|
||||
# @foo.updated_at.should == time_now
|
||||
def stub_time_now
|
||||
returning Time.now do |now|
|
||||
Time.stub!(:now).and_return(now)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns class for the specified name. Example:
|
||||
#
|
||||
# class_for("foo") # => Foo
|
||||
def class_for(name)
|
||||
name.to_s.constantize
|
||||
rescue NameError
|
||||
name.to_s.pluralize.classify.constantize
|
||||
# Let any other error rise!
|
||||
end
|
||||
|
||||
# Returns instance variable for the specified name. Example:
|
||||
#
|
||||
# instance_for("foo") # => @foo
|
||||
def instance_for(name)
|
||||
instance_variable_get("@#{name.to_s.underscore}")
|
||||
end
|
||||
|
||||
# Wraps a matcher that checks if the receiver contains an <tt>A</tt> element (link)
|
||||
# whose <tt>href</tt> attribute is set to the specified path.
|
||||
def have_link_to(path)
|
||||
have_tag("a[href='#{path}']")
|
||||
end
|
||||
|
||||
# Returns dummy value for specified attribute based on the datatype expected for that
|
||||
# attribute.
|
||||
def dummy_value_for(instance, attribute)
|
||||
if datatype = instance.column_for_attribute(attribute)
|
||||
actual = instance.send(attribute)
|
||||
case datatype.type
|
||||
when :string, :text
|
||||
actual == "foo" ? "bar" : "food"
|
||||
when :integer, :float, :decimal
|
||||
actual == 108 ? 815 : 108
|
||||
when :boolean
|
||||
actual ? false : true
|
||||
when :date, :datetime, :time, :timestamp
|
||||
actual == 1.week.ago ? 2.years.ago : 1.week.ago
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns class description text
|
||||
def class_description_text
|
||||
if self.class.respond_to?(:description_text)
|
||||
# Old school
|
||||
self.class.description_text
|
||||
else
|
||||
# New school
|
||||
self.class.description
|
||||
end
|
||||
end
|
||||
|
||||
# Returns description text
|
||||
def self_description_text
|
||||
if respond_to?(:description_text)
|
||||
# Old school
|
||||
description_text
|
||||
else
|
||||
# New school
|
||||
description
|
||||
end
|
||||
end
|
||||
|
||||
def described_type
|
||||
self.class.described_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
module LuckySneaks
|
||||
module ControllerRequestHelpers # :nodoc:
|
||||
def self.included(base)
|
||||
base.extend ExampleGroupMethods
|
||||
end
|
||||
|
||||
private
|
||||
def define_implicit_request(method)
|
||||
@controller_method = method
|
||||
@implicit_request = case method
|
||||
when :index, :new, :show, :edit
|
||||
proc { get method, params }
|
||||
when :create
|
||||
proc { post :create, params }
|
||||
when :update
|
||||
proc { put :update, params }
|
||||
when :destroy
|
||||
proc { put :destroy, params }
|
||||
end
|
||||
end
|
||||
|
||||
def eval_request
|
||||
instance_eval &self.class.instance_variable_get("@the_request")
|
||||
rescue ArgumentError # missing block
|
||||
try_shared_request_definition
|
||||
end
|
||||
alias do_request eval_request
|
||||
|
||||
def try_shared_request_definition
|
||||
if defined?(shared_request) == "method"
|
||||
shared_request
|
||||
elsif @implicit_request
|
||||
try_implicit_request
|
||||
else
|
||||
error_message = "Could not determine request definition for 'describe' context. "
|
||||
error_message << "Please use define_request or define a shared_request."
|
||||
raise ArgumentError, error_message
|
||||
end
|
||||
end
|
||||
|
||||
def try_implicit_request
|
||||
@implicit_request.call
|
||||
end
|
||||
|
||||
def get_response(&block)
|
||||
eval_request
|
||||
block.call(response) if block_given?
|
||||
response
|
||||
end
|
||||
|
||||
module ExampleGroupMethods
|
||||
# Defines a request at the example group ("describe") level to be evaluated in the examples. Example:
|
||||
#
|
||||
# define_request { get :index, params }
|
||||
#
|
||||
# <b>Note:</b> The following methods all define implicit requests: <tt>stub_index</tt>, <tt>stub_new</tt>,
|
||||
# <tt>stub_create</tt>, <tt>stub_show</tt>, <tt>stub_edit</tt>, <tt>stub_update</tt>, and
|
||||
# <tt>stub_destroy</tt>. Using them in your <tt>before</tt> blocks will allow you to forego
|
||||
# defining explicit requests using <tt>define_request</tt>. See
|
||||
# LuckySneaks::ControllerStubHelpers for information on these methods.
|
||||
def define_request(&block)
|
||||
raise ArgumentError, "Must provide a block to define a request!" unless block_given?
|
||||
@the_request = block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,571 @@
|
|||
$:.unshift File.join(File.dirname(__FILE__), "..")
|
||||
require "skinny_spec"
|
||||
|
||||
module LuckySneaks
|
||||
module ControllerSpecHelpers # :nodoc:
|
||||
include LuckySneaks::CommonSpecHelpers
|
||||
include LuckySneaks::ControllerRequestHelpers
|
||||
include LuckySneaks::ControllerStubHelpers
|
||||
|
||||
def self.included(base)
|
||||
base.extend ExampleGroupMethods
|
||||
base.extend ControllerRequestHelpers::ExampleGroupMethods
|
||||
end
|
||||
|
||||
# Evaluates the specified block for each of the RESTful controller methods.
|
||||
# This is useful to spec that all controller methods redirect when no user is
|
||||
# logged in.
|
||||
def with_default_restful_actions(params = {}, &block)
|
||||
{
|
||||
:get => :index,
|
||||
:get => :new,
|
||||
:post => :create
|
||||
}.each do |method_id, message|
|
||||
self.send method_id, message, params
|
||||
block.call
|
||||
end
|
||||
{
|
||||
:get => :edit,
|
||||
:put => :update,
|
||||
:delete => :destroy
|
||||
}.each do |method_id, message|
|
||||
if params[:before]
|
||||
params.delete(:before).call
|
||||
end
|
||||
# Presuming any id will do
|
||||
self.send method_id, message, params.merge(:id => 1)
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def create_ar_class_expectation(name, method, argument = nil, options = {})
|
||||
args = []
|
||||
unless options.delete(:only_method)
|
||||
args << argument unless argument.nil?
|
||||
args << hash_including(options) unless options.empty?
|
||||
end
|
||||
method = options.delete(:find_method) if options[:find_method]
|
||||
if args.empty?
|
||||
class_for(name).should_receive(method).and_return(instance_for(name))
|
||||
else
|
||||
class_for(name).should_receive(method).with(*args).and_return(instance_for(name))
|
||||
end
|
||||
end
|
||||
|
||||
def create_positive_ar_instance_expectation(name, method, *args)
|
||||
instance = instance_for(name)
|
||||
if args.empty?
|
||||
instance.should_receive(method).and_return(true)
|
||||
else
|
||||
instance.should_receive(method).with(*args).and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
# These methods are designed to be used at the example group [read: "describe"] level
|
||||
# to simplify and DRY up common expectations.
|
||||
module ExampleGroupMethods
|
||||
# Creates an expectation that the controller method calls <tt>ActiveRecord::Base.find</tt>.
|
||||
# Examples:
|
||||
#
|
||||
# it_should_find :foos # => Foo.should_receive(:find).with(:all)
|
||||
# it_should_find :foos, :all # An explicit version of the above
|
||||
# it_should_find :foos, :conditions => {:foo => "bar"} # => Foo.should_receive(:find).with(:all, :conditions => {"foo" => "bar"}
|
||||
# it_should_find :foos, "joe", :method => :find_all_by_name # Foo.should_receive(:find_all_by_name).with("joe")
|
||||
# it_should_find :foo # => Foo.should_recieve(:find).with(@foo.id.to_s)
|
||||
# it_should_find :foo, :params => "id" # => Foo.should_receive(:find).with(params[:id].to_s)
|
||||
# it_should_find :foo, 2 # => Foo.should_receive(:find).with("2")
|
||||
# it_should_find :foo, "joe", :method => :find_by_name # => Foo.should_recieve(:find_by_name).with("joe")
|
||||
#
|
||||
# <b>Note:</b> All params (key and value) will be strings if they come from a form element and are handled
|
||||
# internally with this expectation.
|
||||
def it_should_find(name, *args)
|
||||
name_string = name.to_s
|
||||
name_message = if name_string == name_string.singularize
|
||||
"a #{name}"
|
||||
else
|
||||
name
|
||||
end
|
||||
it "should find #{name_message}" do
|
||||
options = args.extract_options!
|
||||
# Blech!
|
||||
argument = if param = params[options.delete(:params)]
|
||||
param.to_s
|
||||
else
|
||||
if args.first
|
||||
args.first
|
||||
elsif (instance = instance_variable_get("@#{name}")).is_a?(ActiveRecord::Base)
|
||||
instance.id.to_s
|
||||
else
|
||||
:all
|
||||
end
|
||||
end
|
||||
find_method = options.delete(:method) || :find
|
||||
create_ar_class_expectation name, find_method, argument, options
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_find</tt>. This creates an expectation that
|
||||
# the class never receives <tt>find</tt> at all.
|
||||
def it_should_not_find(name)
|
||||
name_string = name.to_s
|
||||
name_message = if name_string == name_string.singularize
|
||||
"a #{name}"
|
||||
else
|
||||
name
|
||||
end
|
||||
it "should not find #{name_message}" do
|
||||
if name_string == name_string.singularize
|
||||
class_for(name).should_not_receive(:find)
|
||||
else
|
||||
class_for(name).should_not_receive(:find).with(:all)
|
||||
end
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method calls <tt>ActiveRecord::Base.new</tt>.
|
||||
# Takes optional <tt>params</tt> for the initialization arguments. Example
|
||||
#
|
||||
# it_should_initialize :foo # => Foo.should_receive(:new)
|
||||
# it_should_initialize :foo, :params => :bar # => Foo.should_receive(:new).with(params[:bar])
|
||||
# it_should_initialize :foo, :bar => "baz" # => Foo.should_receive(:new).with(:bar => "baz")
|
||||
def it_should_initialize(name, options = {})
|
||||
it "should initialize a #{name}" do
|
||||
create_ar_class_expectation name, :new, params[options.delete(:params)], options
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_initialize</tt>. This creates an expectation
|
||||
# that the class never recieves <tt>new</tt> at all.
|
||||
def it_should_not_initialize(name)
|
||||
it "should initialize a #{name}" do
|
||||
class_for(name).should_not_receive(:new)
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method calls <tt>ActiveRecord::Base#save</tt> on the
|
||||
# named instance. Example:
|
||||
#
|
||||
# it_should_save :foo # => @foo.should_receive(:save).and_return(true)
|
||||
#
|
||||
# <b>Note:</b> This helper should not be used to spec a failed <tt>save</tt> call. Use <tt>it_should_assign</tt>
|
||||
# instead, to verify that the instance is captured in an instance variable for the inevitable re-rendering
|
||||
# of the form template.
|
||||
def it_should_save(name)
|
||||
it "should save the #{name}" do
|
||||
create_positive_ar_instance_expectation name, :save
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_update</tt>. This creates an expectation
|
||||
# that the instance never receives <tt>save</tt> at all.
|
||||
def it_should_not_save(name)
|
||||
it "should not save the #{name}" do
|
||||
instance_for(name).should_not_receive(:save)
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method calls <tt>ActiveRecord::Base#update_attributes</tt>
|
||||
# on the named instance. Takes optional argument for <tt>params</tt> to specify in the
|
||||
# expectation. Examples:
|
||||
#
|
||||
# it_should_update :foo # => @foo.should_receive(:update_attributes).and_return(true)
|
||||
# it_should_update :foo, :params => :bar # => @foo.should_receive(:update_attributes).with(params[:bar]).and_return(true)
|
||||
#
|
||||
# <b>Note:</b> This helper should not be used to spec a failed <tt>update_attributes</tt> call. Use
|
||||
# <tt>it_should_assign</tt> instead, to verify that the instance is captured in an instance variable
|
||||
# for the inevitable re-rendering of the form template.
|
||||
def it_should_update(name, options = {})
|
||||
it "should update the #{name}" do
|
||||
create_positive_ar_instance_expectation name, :update_attributes, params[name]
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_update</tt>. This creates an expectation
|
||||
# that the instance never receives <tt>update_attributes</tt> at all.
|
||||
def it_should_not_update(name)
|
||||
it "should not update the #{name}" do
|
||||
instance_for(name).should_not_receive(:update_attributes)
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method calls <tt>ActiveRecord::Base#destroy</tt> on the named
|
||||
# instance. Example:
|
||||
#
|
||||
# it_should_destroy :foo # => @foo.should_receive(:destroy).and_return(true)
|
||||
#
|
||||
# <b>Note:</b> This helper should not be used to spec a failed <tt>destroy</tt> call. Use
|
||||
# <tt>it_should_assign</tt> instead, if you need to verify that the instance is captured in an instance
|
||||
# variable if it is re-rendered somehow. This is probably a really edge use case.
|
||||
def it_should_destroy(name, options = {})
|
||||
it "should delete the #{name}" do
|
||||
create_positive_ar_instance_expectation name, :destroy
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_destroy</tt>. This creates an expectation
|
||||
# that the instance never receives <tt>destroy</tt> at all.
|
||||
def it_should_not_destroy(name)
|
||||
it "should not destroy the #{name}" do
|
||||
instance_for(name).should_not_receive(:destroy)
|
||||
eval_request
|
||||
end
|
||||
end
|
||||
|
||||
# Creates expectation[s] that the controller method should assign the specified
|
||||
# instance variables along with any specified values. Examples:
|
||||
#
|
||||
# it_should_assign :foo # => assigns[:foo].should == @foo
|
||||
# it_should_assign :foo => "bar" # => assigns[:foo].should == "bar"
|
||||
# it_should_assign :foo => :nil # => assigns[:foo].should be_nil
|
||||
# it_should_assign :foo => :not_nil # => assigns[:foo].should_not be_nil
|
||||
# it_should_assign :foo => :undefined # => controller.send(:instance_variables).should_not include("@foo")
|
||||
#
|
||||
# Very special thanks to Rick Olsen for the basis of this code. The only reason I even
|
||||
# redefine it at all is purely an aesthetic choice for specs like "it should foo"
|
||||
# over ones like "it foos".
|
||||
def it_should_assign(*names)
|
||||
names.each do |name|
|
||||
if name.is_a?(Symbol)
|
||||
it_should_assign name => name
|
||||
elsif name.is_a?(Hash)
|
||||
name.each do |key, value|
|
||||
it_should_assign_instance_variable key, value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Essentially shorthand for <tt>it_should_assign name => :nil</tt>. This method can take multiple
|
||||
# instance variable names, creating this shorthand for each name. See the docs for
|
||||
# <tt>it_should_assign</tt> for more information.
|
||||
def it_should_not_assign(*names)
|
||||
names.each do |name|
|
||||
# Assuming name is a symbol
|
||||
it_should_assign name => :nil
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the separate expectations <tt>it_should_find</tt> and <tt>it_should_assign</tt>
|
||||
# for simple cases. If you need more control over the parameters of the find, this
|
||||
# isn't the right helper method and you should write out the two expectations separately.
|
||||
def it_should_find_and_assign(*names)
|
||||
names.each do |name|
|
||||
it_should_find name, :only_method => true
|
||||
it_should_assign name
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_find_and_assign</tt>. This creates an
|
||||
# expectation that the class never receives <tt>find</tt> at all and that
|
||||
# no matching instance variable is ever created.
|
||||
def it_should_not_find_and_assign(*names)
|
||||
names.each do |name|
|
||||
it_should_not_find name
|
||||
it_should_assign name => :nil
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the separate expectations <tt>it_should_initialize</tt> and <tt>it_should_assign</tt>
|
||||
# for simple cases. If you need more control over the parameters of the initialization, this
|
||||
# isn't the right helper method and you should write out the two expectations separately.
|
||||
#
|
||||
# <b>Note:</b> This method is used for controller methods like <tt>new</tt>, where the instance
|
||||
# is initialized without being saved (this includes failed <tt>create</tt> requests).
|
||||
# If you want to spec that the controller method successfully saves the instance,
|
||||
# please use <tt>it_should_initialize_and_save</tt>.
|
||||
def it_should_initialize_and_assign(*names)
|
||||
names.each do |name|
|
||||
it_should_initialize name, :only_method => true
|
||||
it_should_assign name
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_initialize_and_assign</tt>. This creates an
|
||||
# expectation that the class never receives <tt>new</tt> at all and that
|
||||
# no matching instance variable is ever created.
|
||||
def it_should_not_initialize_and_assign(*names)
|
||||
names.each do |name|
|
||||
it_should_not_initialize name
|
||||
it_should_assign name => :nil
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the separate expectations <tt>it_should_initialize</tt> and <tt>it_should_save</tt>
|
||||
# for simple cases. If you need more control over the parameters of the initialization, this
|
||||
# isn't the right helper method and you should write out the two expectations separately.
|
||||
#
|
||||
# <b>Note:</b> This method is used for controller methods like <tt>create</tt>, where the instance
|
||||
# is initialized and successfully saved. If you want to spec that the instance is created
|
||||
# but not saved, just use <tt>it_should_initialize_and_assign</tt>.
|
||||
def it_should_initialize_and_save(*names)
|
||||
names.each do |name|
|
||||
it_should_initialize name, :only_method => true
|
||||
it_should_save name
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the separate expectations <tt>it_should_find</tt> and <tt>it_should_update</tt>
|
||||
# for simple cases. If you need more control over the parameters of the find, this
|
||||
# isn't the right helper method and you should write out the two expectations separately.
|
||||
#
|
||||
# <b>Note:</b> This method is used for controller methods like <tt>update</tt>, where the
|
||||
# instance is loaded from the database and successfully saved. If you want to spec that the
|
||||
# instance is found but not saved, just use <tt>it_should_find_and_assign</tt>.
|
||||
def it_should_find_and_update(*names)
|
||||
names.each do |name|
|
||||
it_should_find name, :only_method => true
|
||||
it_should_update name
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the separate expectations <tt>it_should_find</tt> and <tt>it_should_destroy</tt>
|
||||
# for simple cases. If you need more control over the parameters of the find, this
|
||||
# isn't the right helper method and you should write out the two expectations separately.
|
||||
def it_should_find_and_destroy(*names)
|
||||
names.each do |name|
|
||||
it_should_find name, :only_method => true
|
||||
it_should_destroy name
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the specified collection (<tt>flash</tt>, <tt>session</tt>,
|
||||
# <tt>params</tt>, <tt>cookies</tt>) contains the specified key and value. To specify that
|
||||
# the collection should be set to <tt>nil</tt>, specify the value as :nil instead.
|
||||
def it_should_set(collection, key, value = nil, &block)
|
||||
it "should set #{collection}[:#{key}]#{' with ' + value.inspect if value}" do
|
||||
# Allow flash.now[:foo] to remain in the flash
|
||||
flash.stub!(:sweep) if collection == :flash
|
||||
eval_request
|
||||
if value
|
||||
if value == :nil
|
||||
self.send(collection)[key].should be_nil
|
||||
else
|
||||
self.send(collection)[key].should == value
|
||||
end
|
||||
elsif block_given?
|
||||
self.send(collection)[key].should == instance_eval(&block)
|
||||
else
|
||||
self.send(collection)[key].should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :flash</tt>. To specify that the collection should be set
|
||||
# to <tt>nil</tt>, specify the value as :nil instead.
|
||||
def it_should_set_flash(name, value = nil, &block)
|
||||
it_should_set :flash, name, value, &block
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :flash, :nil</tt>.
|
||||
def it_should_not_set_flash(name)
|
||||
it_should_set :flash, name, :nil
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :session</tt>. To specify that the collection should be set
|
||||
# to <tt>nil</tt>, specify the value as :nil instead.
|
||||
def it_should_set_session(name, value = nil, &block)
|
||||
it_should_set :session, name, value, &block
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :session, :nil</tt>.
|
||||
def it_should_not_set_session(name)
|
||||
it_should_set :session, name, :nil
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :params</tt>. To specify that the collection should be set
|
||||
# to <tt>nil</tt>, specify the value as :nil instead.
|
||||
def it_should_set_params(name, value = nil, &block)
|
||||
it_should_set :params, name, value, &block
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :params, :nil</tt>.
|
||||
def it_should_not_set_params(name)
|
||||
it_should_set :params, name, :nil
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :cookies</tt>. To specify that the collection should be set
|
||||
# to <tt>nil</tt>, specify the value as :nil instead.
|
||||
def it_should_set_cookies(name, value = nil, &block)
|
||||
it_should_set :cookies, name, value, &block
|
||||
end
|
||||
|
||||
# Wraps <tt>it_should_set :cookies, :nil</tt>.
|
||||
def it_should_not_set_cookies(name)
|
||||
it_should_set :cookies, name, :nil
|
||||
end
|
||||
|
||||
# Wraps the various <tt>it_should_render_<i>foo</i></tt> methods:
|
||||
# <tt>it_should_render_template</tt>, <tt>it_should_render_partial</tt>,
|
||||
# <tt>it_should_render_xml</tt>, <tt>it_should_render_json</tt>,
|
||||
# <tt>it_should_render_formatted</tt>, and <tt>it_should_render_nothing</tt>.
|
||||
def it_should_render(render_method, *args)
|
||||
send "it_should_render_#{render_method}", *args
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method renders the specified template.
|
||||
# Accepts the following options which create additional expectations.
|
||||
#
|
||||
# <tt>:content_type</tt>:: Creates an expectation that the Content-Type header for the response
|
||||
# matches the one specified
|
||||
# <tt>:status</tt>:: Creates an expectation that the HTTP status for the response
|
||||
# matches the one specified
|
||||
def it_should_render_template(name, options = {})
|
||||
create_status_expectation options[:status] if options[:status]
|
||||
it "should render '#{name}' template" do
|
||||
eval_request
|
||||
response.should render_template(name)
|
||||
end
|
||||
create_content_type_expectation(options[:content_type]) if options[:content_type]
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method renders the specified partial.
|
||||
# Accepts the following options which create additional expectations.
|
||||
#
|
||||
# <tt>:content_type</tt>:: Creates an expectation that the Content-Type header for the response
|
||||
# matches the one specified
|
||||
# <tt>:status</tt>:: Creates an expectation that the HTTP status for the response
|
||||
# matches the one specified
|
||||
def it_should_render_partial(name, options = {})
|
||||
create_status_expectation options[:status] if options[:status]
|
||||
it "should render '#{name}' partial" do
|
||||
controller.expect_render(:partial => name)
|
||||
eval_request
|
||||
end
|
||||
create_content_type_expectation(options[:content_type]) if options[:content_type]
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method renders the specified record via <tt>to_xml</tt>.
|
||||
# Accepts the following options which create additional expectations.
|
||||
#
|
||||
# <tt>:content_type</tt>:: Creates an expectation that the Content-Type header for the response
|
||||
# matches the one specified
|
||||
# <tt>:status</tt>:: Creates an expectation that the HTTP status for the response
|
||||
# matches the one specified
|
||||
def it_should_render_xml(record = nil, options = {}, &block)
|
||||
it_should_render_formatted :xml, record, options, &block
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method renders the specified record via <tt>to_json</tt>.
|
||||
# Accepts the following options which create additional expectations.
|
||||
#
|
||||
# <tt>:content_type</tt>:: Creates an expectation that the Content-Type header for the response
|
||||
# matches the one specified
|
||||
# <tt>:status</tt>:: Creates an expectation that the HTTP status for the response
|
||||
# matches the one specified
|
||||
def it_should_render_json(record = nil, options = {}, &block)
|
||||
it_should_render_formatted :json, record, options, &block
|
||||
end
|
||||
|
||||
# Called internally by <tt>it_should_render_xml</tt> and <tt>it_should_render_json</tt>
|
||||
# but should not really be called much externally unless you have defined your own
|
||||
# formats with a matching <tt>to_foo</tt> method on the record.
|
||||
#
|
||||
# Which is probably never.
|
||||
def it_should_render_formatted(format, record = nil, options = {}, &block)
|
||||
create_status_expectation options[:status] if options[:status]
|
||||
it "should render #{format.inspect}" do
|
||||
if record.is_a?(Hash)
|
||||
options = record
|
||||
record = nil
|
||||
end
|
||||
if record.nil? && !block_given?
|
||||
raise ArgumentError, "it_should_render must be called with either a record or a block and neither was given."
|
||||
else
|
||||
if record
|
||||
pieces = record.to_s.split(".")
|
||||
record = instance_variable_get("@#{pieces.shift}")
|
||||
record = record.send(pieces.shift) until pieces.empty?
|
||||
end
|
||||
block ||= proc { record.send("to_#{format}") }
|
||||
get_response do |response|
|
||||
response.should have_text(block.call)
|
||||
end
|
||||
end
|
||||
end
|
||||
create_content_type_expectation(options[:content_type]) if options[:content_type]
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method returns a blank page. You'd already
|
||||
# know when and why to use this so I'm not typing it out.
|
||||
def it_should_render_nothing(options = {})
|
||||
create_status_expectation options[:status] if options[:status]
|
||||
it "should render :nothing" do
|
||||
get_response do |response|
|
||||
response.body.strip.should be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method redirects to the specified destination. Example:
|
||||
#
|
||||
# it_should_redirect_to { foos_url }
|
||||
#
|
||||
# <b>Note:</b> This method takes a block to evaluate the route in the example
|
||||
# context rather than the example group context.
|
||||
def it_should_redirect_to(hint = nil, &route)
|
||||
if hint.nil? && route.respond_to?(:to_ruby)
|
||||
hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
|
||||
end
|
||||
it "should redirect to #{(hint || route)}" do
|
||||
eval_request
|
||||
response.should redirect_to(instance_eval(&route))
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_redirect_to</tt>.
|
||||
def it_should_not_redirect_to(hint = nil, &route)
|
||||
if hint.nil? && route.respond_to?(:to_ruby)
|
||||
hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
|
||||
end
|
||||
it "should not redirect to #{(hint || route)}" do
|
||||
eval_request
|
||||
response.should_not redirect_to(instance_eval(&route))
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the controller method redirects back to the previous page
|
||||
def it_should_redirect_to_referer
|
||||
it "should redirect to the referring page" do
|
||||
request.env["HTTP_REFERER"] = "http://test.host/referer"
|
||||
eval_request
|
||||
response.should redirect_to("http://test.host/referer")
|
||||
end
|
||||
end
|
||||
alias it_should_redirect_to_referrer it_should_redirect_to_referer
|
||||
|
||||
private
|
||||
def it_should_assign_instance_variable(name, value)
|
||||
expectation_proc = case value
|
||||
when :nil
|
||||
proc { assigns[name].should be_nil }
|
||||
when :not_nil
|
||||
proc { assigns[name].should_not be_nil }
|
||||
when :undefined
|
||||
proc { controller.send(:instance_variables).should_not include("@{name}") }
|
||||
when Symbol
|
||||
if (instance_variable = instance_variable_get("@#{name}")).nil?
|
||||
proc { assigns[name].should_not be_nil }
|
||||
else
|
||||
proc { assigns[name].should == instance_variable }
|
||||
end
|
||||
else
|
||||
proc { assigns[name].should == value }
|
||||
end
|
||||
it "should #{value == :nil ? 'not ' : ''}assign @#{name}" do
|
||||
eval_request
|
||||
instance_eval &expectation_proc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
module LuckySneaks # :nodoc:
|
||||
# These methods are designed to be used in your example <tt>before</tt> blocks to accomplish
|
||||
# a whole lot of functionality with just a tiny bit of effort. The methods which correspond
|
||||
# to the controller methods perform the most duties as they create the mock_model instances,
|
||||
# stub out all the necessary methods, and also create implicit requests to DRY up your spec
|
||||
# file even more. You are encouraged to use these methods to setup the basic calls for your
|
||||
# resources and only resort to the other methods when mocking and stubbing secondary objects
|
||||
# and calls.
|
||||
module ControllerStubHelpers
|
||||
# Stubs out <tt>find :all</tt> and returns a collection of <tt>mock_model</tt>
|
||||
# instances of that class. Accepts the following options:
|
||||
#
|
||||
# <b>:find_method</b>:: Method to use as finder call. Default is <tt>:find</tt>.
|
||||
# <b>Note:</b> When specifying the method, the call is stubbed
|
||||
# to accept any arguments. Caveat programmer.
|
||||
# <b>:format</b>:: Format of the request. Used to only add <tt>to_xml</tt> and
|
||||
# <tt>to_json</tt> when actually needed.
|
||||
# <b>:size</b>:: Number of instances to return in the result. Default is 3.
|
||||
# <b>:stub</b>:: Additional methods to stub on the instances
|
||||
#
|
||||
# Any additional options will be passed as arguments to the class find.
|
||||
# You will want to make sure to pass those arguments to the <tt>it_should_find</tt> spec as well.
|
||||
def stub_find_all(klass, options = {})
|
||||
returning(Array.new(options[:size] || 3){mock_model(klass)}) do |collection|
|
||||
stub_out klass, options.delete(:stub)
|
||||
if format = options.delete(:format)
|
||||
stub_formatted collection, format
|
||||
params[:format] = format
|
||||
end
|
||||
if find_method = options[:find_method]
|
||||
# Not stubbing specific arguments here
|
||||
# If you need more specificity, write a custom example
|
||||
klass.stub!(find_method).and_return(collection)
|
||||
else
|
||||
klass.stub!(:find).with(:all).and_return(collection)
|
||||
klass.stub!(:find).with(:all, hash_including(options)).and_return(collection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_find_all</tt> but additionally defines an implicit request <tt>get :index</tt>.
|
||||
def stub_index(klass, options = {})
|
||||
define_implicit_request :index
|
||||
stub_find_all klass, options
|
||||
end
|
||||
|
||||
# Stubs out <tt>new</tt> method and returns a <tt>mock_model</tt> instance marked as a new record.
|
||||
# Accepts the following options:
|
||||
#
|
||||
# <b>:format</b>:: Format of the request. Used to only add <tt>to_xml</tt> and
|
||||
# <tt>to_json</tt> when actually needed.
|
||||
# <b>:stub</b>:: Additional methods to stub on the instances
|
||||
#
|
||||
# It also accepts some options used to stub out <tt>save</tt> with a specified <tt>true</tt>
|
||||
# or <tt>false</tt> but you should be using <tt>stub_create</tt> in that case.
|
||||
def stub_initialize(klass, options = {})
|
||||
returning mock_model(klass) do |member|
|
||||
stub_out member, options.delete(:stub)
|
||||
if format = options[:format]
|
||||
stub_formatted member, format
|
||||
params[:format] = format
|
||||
end
|
||||
klass.stub!(:new).and_return(member)
|
||||
if options[:params]
|
||||
klass.stub!(:new).with(hash_including(options[:params])).and_return(member)
|
||||
end
|
||||
if options[:stub_save]
|
||||
stub_ar_method member, :save, options[:return]
|
||||
else
|
||||
member.stub!(:new_record?).and_return(true)
|
||||
member.stub!(:id).and_return(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_initialize</tt> which additionally defines an implicit request <tt>get :new</tt>.
|
||||
def stub_new(klass, options = {})
|
||||
define_implicit_request :new
|
||||
stub_initialize klass, options
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_initialize</tt> which additionally defines an implicit request <tt>post :create</tt>.
|
||||
#
|
||||
# <b>Note:</b> If <tt>stub_create<tt> is provided an optional <tt>:params</tt> hash,
|
||||
# those params will be added to the example's <tt>params</tt> object.
|
||||
def stub_create(klass, options = {})
|
||||
define_implicit_request :create
|
||||
class_name = klass.name.underscore
|
||||
options[:params] ||= params[class_name]
|
||||
stub_initialize klass, options.merge(:stub_save => true)
|
||||
end
|
||||
|
||||
# Stubs out <tt>find</tt> and returns a single <tt>mock_model</tt>
|
||||
# instances of that class. Accepts the following options:
|
||||
#
|
||||
# <b>:find_method</b>:: Method to use as finder call. Default is <tt>:find</tt>.
|
||||
# <b>:format</b>:: Format of the request. Used to only add <tt>to_xml</tt> and
|
||||
# <tt>to_json</tt> when actually needed.
|
||||
# <b>:stub</b>:: Additional methods to stub on the instances
|
||||
# <b>:current_object</b>:: If set to true, <tt>find</tt> will set <tt>params[:id]</tt>
|
||||
# using the <tt>id</tt> of the <tt>mock_model</tt> instance
|
||||
# and use that value as an argument when stubbing <tt>find</tt>
|
||||
#
|
||||
# Any additional options will be passed as arguments to <tt>find</tt>.You will want
|
||||
# to make sure to pass those arguments to the <tt>it_should_find</tt> spec as well.
|
||||
#
|
||||
# <b>Note:</b> The option <tt>:stub_ar</tt> is used internally by <tt>stub_update</tt>
|
||||
# and <tt>stub_destroy</tt>. If you need to stub <tt>update_attributes</tt> or
|
||||
# <tt>destroy</tt> you should be using the aforementioned methods instead.
|
||||
def stub_find_one(klass, options = {})
|
||||
returning mock_model(klass) do |member|
|
||||
stub_out member, options.delete(:stub)
|
||||
if format = options.delete(:format)
|
||||
stub_formatted member, format
|
||||
params[:format] = format
|
||||
end
|
||||
if options.delete(:current_object)
|
||||
params[:id] = member.id
|
||||
if ar_stub = options.delete(:stub_ar)
|
||||
stub_ar_method member, ar_stub, options.delete(:return), options.delete(:update_params)
|
||||
end
|
||||
end
|
||||
if find_method = options.delete(:find_method)
|
||||
klass.stub!(find_method).and_return(member)
|
||||
else
|
||||
# Stubbing string and non-string just to be safe
|
||||
klass.stub!(:find).with(member.id).and_return(member)
|
||||
klass.stub!(:find).with(member.id.to_s).and_return(member)
|
||||
unless options.empty?
|
||||
klass.stub!(:find).with(member.id, hash_including(options)).and_return(member)
|
||||
klass.stub!(:find).with(member.id.to_s, hash_including(options)).and_return(member)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# <b>Note:</b> Use of this method with :child options (to mock
|
||||
# association) is deprecated. Please use <tt>stub_association</tt>.
|
||||
#
|
||||
# Same as <tt>stub_find_one</tt> but setups the instance as the parent
|
||||
# of the specified association. Example:
|
||||
#
|
||||
# stub_parent(Document, :child => :comments)
|
||||
#
|
||||
# This stubs <tt>Document.find</tt>, <tt>@document.comments</tt> (which
|
||||
# will return <tt>Comment</tt> class), as well as <tt>params[:document_id]</tt>.
|
||||
# This method is meant to be used in the controller for the specified child
|
||||
# (<tt>CommentsController</tt> in this instance) in situations like:
|
||||
#
|
||||
# def index
|
||||
# @document = Document.find(params[:document_id])
|
||||
# @comments = @document.comments.find(:all)
|
||||
# end
|
||||
def stub_parent(klass, options = {})
|
||||
returning stub_find_one(klass, options) do |member|
|
||||
params[klass.name.foreign_key] = member.id
|
||||
if offspring = options.delete(:child)
|
||||
puts "stub_parent with :child option has been marked for deprecation"
|
||||
puts "please use stub_association to create the mock instead"
|
||||
member.stub!(offspring).and_return(class_for(offspring))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_find_one</tt> which additionally defines an implicit request <tt>get :show</tt>.
|
||||
def stub_show(klass, options = {})
|
||||
define_implicit_request :show
|
||||
stub_find_one klass, options.merge(:current_object => true)
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_find_one</tt> which additionally defines an implicit request <tt>get :edit</tt>.
|
||||
def stub_edit(klass, options = {})
|
||||
define_implicit_request :edit
|
||||
stub_find_one klass, options.merge(:current_object => true)
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_find_one</tt> which additionally defines an implicit request <tt>put :update</tt>
|
||||
# and stubs out the <tt>update_attribute</tt> method on the instance as well.
|
||||
#
|
||||
# <b>Note:</b> If <tt>stub_update<tt> is provided an optional <tt>:params</tt> hash,
|
||||
# those params will be added to the example's <tt>params</tt> object.
|
||||
def stub_update(klass, options = {})
|
||||
define_implicit_request :update
|
||||
stub_find_one klass, options.merge(:current_object => true, :stub_ar => :update_attributes)
|
||||
end
|
||||
|
||||
# Alias for <tt>stub_find_one</tt> which additionally defines an implicit request <tt>delete :destroy</tt>
|
||||
# and stubs out the <tt>destroy</tt> method on the instance as well.
|
||||
def stub_destroy(klass, options = {})
|
||||
define_implicit_request :destroy
|
||||
stub_find_one klass, options.merge(:current_object => true, :stub_ar => :destroy)
|
||||
end
|
||||
|
||||
# Stubs <tt>to_xml</tt> or <tt>to_json</tt> respectively based on <tt>format</tt> argument.
|
||||
def stub_formatted(object, format)
|
||||
return unless format
|
||||
object.stub!("to_#{format}").and_return("#{object.class} formatted as #{format}")
|
||||
end
|
||||
|
||||
# Creates a mock object representing an association proxy, stubs the appropriate
|
||||
# method on the parent object and returns that association proxy.
|
||||
# Accepts the following option:
|
||||
#
|
||||
# <b>:stub</b>:: Additional methods to stub on the mock proxy object
|
||||
def stub_association(object, association, options = {})
|
||||
# I know options isn't implemented anywhere
|
||||
object_name = instance_variables.select{|name| instance_variable_get(name) == object}
|
||||
returning mock("Association proxy for #{object_name}.#{association}") do |proxy|
|
||||
stub_out proxy, options[:stub] if options[:stub]
|
||||
object.stub!(association).and_return(proxy)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Stubs out multiple methods. You shouldn't be calling this yourself and if you do
|
||||
# you should be able to understand the code yourself, right?
|
||||
def stub_out(object, stubs = {})
|
||||
return if stubs.nil?
|
||||
stubs.each do |method, value|
|
||||
if value
|
||||
object.stub!(method).and_return(value)
|
||||
else
|
||||
object.stub!(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Stubs out ActiveRecord::Base methods like #save, #update_attributes, etc
|
||||
# that may be called on a found or instantiated mock_model instance.
|
||||
def stub_ar_method(object, method, return_value, params = {})
|
||||
if params.blank?
|
||||
object.stub!(method).and_return(return_value ? false : true)
|
||||
else
|
||||
object.stub!(method).with(hash_including(params)).and_return(return_value ? false : true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,496 @@
|
|||
$:.unshift File.join(File.dirname(__FILE__), "..")
|
||||
require "skinny_spec"
|
||||
|
||||
module LuckySneaks
|
||||
# These methods are designed to be used in your example [read: "it"] blocks
|
||||
# to make your model specs a little more DRY. You might also be interested
|
||||
# in checking out the example block [read: "describe"] level versions in of these
|
||||
# methods which can DRY things up even more:
|
||||
# LuckySneaks::ModelSpecHelpers::ExampleGroupLevelMethods.
|
||||
#
|
||||
# Also check out the methods in LuckySneaks::ModelSpecHelpers::AssociationMatcher
|
||||
# for some helpful matcher helper methods to use with these methods if you want to spec
|
||||
# options on your association setups.
|
||||
module ModelSpecHelpers
|
||||
include LuckySneaks::CommonSpecHelpers
|
||||
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ExampleGroupLevelMethods
|
||||
end
|
||||
|
||||
# These methods cannot be used alone but are used in compliment with the association
|
||||
# matchers in LuckySneaks::ModelSpecHelpers like <tt>have_many</tt>. Example:
|
||||
#
|
||||
# describe User do
|
||||
# it "should have many memberships" do
|
||||
# User.should have_many(:memberships)
|
||||
# end
|
||||
#
|
||||
# it "should have many sites through memberships" do
|
||||
# User.should have_many(:sites).through(:memberships)
|
||||
# end
|
||||
#
|
||||
# it "should belong to a manager" do
|
||||
# User.should belong_to(:manager).with_counter_cache
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# <b>Note:</b> To spec these sorts of options using the example block helpers like
|
||||
# <tt>it_should_have_many</tt>, just add them as options directly. This will use
|
||||
# <tt>with_options</tt> rather than any specific matcher helpers but will have the same
|
||||
# effects. Example:
|
||||
#
|
||||
# describe User do
|
||||
# it_should_have_many :sites, :through => :memberships
|
||||
# end
|
||||
class AssociationMatcher
|
||||
def initialize(associated, macro) # :nodoc:
|
||||
@associated = associated
|
||||
@macro = macro
|
||||
@options = {}
|
||||
end
|
||||
|
||||
def matches?(main_model) # :nodoc:
|
||||
unless main_model.respond_to?(:reflect_on_association)
|
||||
if main_model.class.respond_to?(:reflect_on_association)
|
||||
main_model = main_model.class
|
||||
else
|
||||
@not_model = main_model
|
||||
return false
|
||||
end
|
||||
end
|
||||
if @association = main_model.reflect_on_association(@associated)
|
||||
@options.all?{|k, v| @association.options[k] == v ||
|
||||
[@association.options[k]] == v} # Stupid to_a being obsoleted!
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message # :nodoc:
|
||||
if @not_model
|
||||
" expected: #{@not_model} to be a subclass of ActiveRecord::Base class, but was not"
|
||||
elsif @association
|
||||
" expected: #{association_with(@options)}\n got: #{association_with(@association.options)}"
|
||||
else
|
||||
" expected: #{association_with(@options)}, but the association does not exist"
|
||||
end
|
||||
end
|
||||
|
||||
def negative_failure_message # :nodoc:
|
||||
if @association
|
||||
" expected: #{association_with(@options)}\n got: #{association_with(@association.options)}"
|
||||
else
|
||||
" expected: #{association_with(@options)} to not occur but it does"
|
||||
end
|
||||
end
|
||||
|
||||
# The following public methods are chainable extensions on the main matcher
|
||||
# Examples:
|
||||
#
|
||||
# Foo.should have_many(:bars).through(:foobars).with_dependent(:destroy)
|
||||
# Bar.should belong_to(:baz).with_class_name("Unbaz")
|
||||
def through(through_model)
|
||||
@options[:through] = through_model
|
||||
self
|
||||
end
|
||||
|
||||
def and_includes(included_models)
|
||||
@options[:include] = included_models
|
||||
self
|
||||
end
|
||||
|
||||
def and_extends(*modules)
|
||||
@options[:extends] = modules
|
||||
self
|
||||
end
|
||||
|
||||
def with_counter_cache(counter_cache = true)
|
||||
if counter_cache
|
||||
@options[:counter_cache] = counter_cache
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def uniq(*irrelevant_args)
|
||||
@options[:uniq] = true
|
||||
self
|
||||
end
|
||||
alias and_is_unique uniq
|
||||
alias with_unique uniq
|
||||
|
||||
def polymorphic(*irrelevant_args)
|
||||
@options[:polymorphic] = true
|
||||
self
|
||||
end
|
||||
alias and_is_polymorphic polymorphic
|
||||
alias with_polymorphic polymorphic
|
||||
|
||||
def as(interface)
|
||||
@options[:as] = interface
|
||||
end
|
||||
|
||||
# Use this to just specify the options as a hash.
|
||||
# Note: It will completely override any previously set options
|
||||
def with_options(options = {})
|
||||
options.each{|k, v| @options[k] = v}
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
# Takes care of methods like with_dependent(:destroy)
|
||||
def method_missing(method_id, *args, &block)
|
||||
method_name = method_id.to_s
|
||||
if method_name =~ /^with_(.*)/
|
||||
@options[$1.to_sym] = args
|
||||
self
|
||||
else
|
||||
super method_id, *args, &block
|
||||
end
|
||||
end
|
||||
|
||||
def association_with(options)
|
||||
option_string = (options.nil? || options.empty?) ? "" : options.inspect
|
||||
unless option_string.blank?
|
||||
option_string.sub! /^\{(.*)\}$/, ', \1'
|
||||
option_string.gsub! /\=\>/, ' => '
|
||||
end
|
||||
"#{@macro} :#{@associated}#{option_string}"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates matcher that checks if the receiver has a <tt>belongs_to</tt> association
|
||||
# with the specified model.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def belong_to(model)
|
||||
AssociationMatcher.new model, :belongs_to
|
||||
end
|
||||
|
||||
# Creates matcher that checks if the receiver has a <tt>have_one</tt> association
|
||||
# with the specified model.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def have_one(model)
|
||||
AssociationMatcher.new model, :has_one
|
||||
end
|
||||
|
||||
# Creates matcher that checks if the receiver has a <tt>have_many</tt> association
|
||||
# with the specified model.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def have_many(models)
|
||||
AssociationMatcher.new models, :has_many
|
||||
end
|
||||
|
||||
# Creates matcher that checks if the receiver has a <tt>have_and_belong_to_many</tt> association
|
||||
# with the specified model.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def have_and_belong_to_many(models)
|
||||
AssociationMatcher.new models, :has_and_belongs_to_many
|
||||
end
|
||||
|
||||
private
|
||||
def class_or_instance
|
||||
@model_spec_class_or_instance ||= class_for(described_type) || instance
|
||||
end
|
||||
|
||||
def instance
|
||||
@model_spec_instance ||= instance_for(described_type)
|
||||
end
|
||||
|
||||
# These methods are designed to be used at the example group [read: "describe"] level
|
||||
# to simplify and DRY up common expectations. Some of these methods are wrappers for
|
||||
# matchers which can also be used on the example level [read: within an "it" block]. See
|
||||
# LuckySneaks::ModelSpecHelpers for more information.
|
||||
#
|
||||
# <b>Note:</b> The validation matchers are only meant to be used for simple validation checking
|
||||
# not as a one-size-fits-all solution.
|
||||
module ExampleGroupLevelMethods
|
||||
# Creates an expectation that the current model being spec'd has a <tt>belong_to</tt>
|
||||
# association with the specified model. Accepts optional arguments which are appended to
|
||||
# the <tt>belong_to</tt> spec like this:
|
||||
#
|
||||
# it_should_belong_to :document, :counter_cache => true
|
||||
#
|
||||
# which is the same as writing out:
|
||||
#
|
||||
# it "should belong to document" do
|
||||
# Comment.should belong_to(:document).with_options(:counter_cache => true)
|
||||
# end
|
||||
#
|
||||
# If you want a more detailed spec description text, feel free to write this out in the long
|
||||
# form and use <tt>belong_to</tt> and its related matcher helpers.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def it_should_belong_to(model, options = {})
|
||||
it "should belong to a #{model}" do
|
||||
if options.empty?
|
||||
class_or_instance.should belong_to(model)
|
||||
else
|
||||
class_or_instance.should belong_to(model).with_options(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd has a <tt>have_one</tt>
|
||||
# association with the specified model. Accepts optional arguments which are appended to
|
||||
# the <tt>have_one</tt> spec like this:
|
||||
#
|
||||
# it_should_have_one :last_comment, :class_name => "Comment", :order => "created_at DESC"
|
||||
#
|
||||
# which is the same as writing out:
|
||||
#
|
||||
# it "should have one document" do
|
||||
# Document.should have_one(:last_comment).with_options(:class_name => "Comment", :order => "created_at DESC")
|
||||
# end
|
||||
#
|
||||
# If you want a more detailed spec description text, feel free to write this out in the long
|
||||
# form and use <tt>have_one</tt> and its related matcher helpers.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def it_should_have_one(model, options = {})
|
||||
it "should have one #{model}" do
|
||||
if options.empty?
|
||||
class_or_instance.should have_one(model)
|
||||
else
|
||||
class_or_instance.should have_one(model).with_options(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd has a <tt>have_many</tt>
|
||||
# association with the specified model. Accepts optional arguments which are appended to
|
||||
# the <tt>have_many</tt> spec like this:
|
||||
#
|
||||
# it_should_have_many :memberships, :through => :sites
|
||||
#
|
||||
# which is the same as writing out:
|
||||
#
|
||||
# it "should have many memberships" do
|
||||
# User.should have_many(:memberships).with_options(:through => :sites)
|
||||
# end
|
||||
#
|
||||
# If you want a more detailed spec description text, feel free to write this out in the long
|
||||
# form and use <tt>have_many</tt> and its related matcher helpers.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def it_should_have_many(models, options = {})
|
||||
it "should have many #{models}" do
|
||||
if options.empty?
|
||||
class_or_instance.should have_many(models)
|
||||
else
|
||||
class_or_instance.should have_many(models).with_options(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd has a <tt>have_and_belong_to_many</tt>
|
||||
# association with the specified model. Accepts optional arguments which are appended to
|
||||
# the <tt>have_and_belong_to_many</tt> spec like this:
|
||||
#
|
||||
# it_should_have_and_belong_to_many :documents, :include => :attachments
|
||||
#
|
||||
# which is the same as writing out:
|
||||
#
|
||||
# it "should belong to document" do
|
||||
# User.should have_and_belong_to_many(:documents).with_options(:include => :attachments)
|
||||
# end
|
||||
#
|
||||
# If you want a more detailed spec description text, feel free to write this out in the long
|
||||
# form and use <tt>have_and_belong_to_many</tt> and its related matcher helpers.
|
||||
#
|
||||
# <b>Note:</b> The argument should be a symbol as in the model's association definition
|
||||
# and not the model's class name.
|
||||
def it_should_have_and_belong_to_many(models, options = {})
|
||||
it "should have and belong to many #{models}" do
|
||||
if options.empty?
|
||||
class_or_instance.should have_and_belong_to_many(models)
|
||||
else
|
||||
class_or_instance.should have_and_belong_to_many(models).with_options(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that new instances of the model being spec'd
|
||||
# should initialise the specified attributes with a default value.
|
||||
#
|
||||
# it_should_default_attributes :status => 'new'
|
||||
#
|
||||
def it_should_default_attributes(hash_attribute_values)
|
||||
hash_attribute_values.each_pair do |a,v|
|
||||
it "should default #{a} attribute to #{v}" do
|
||||
class_or_instance.new.send(a).should == v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd <tt>validates_presence_of</tt>
|
||||
# the specified attribute. Takes an optional custom message to match the one in the model's
|
||||
# validation.
|
||||
def it_should_validate_presence_of(attribute, message = default_error_message(:blank))
|
||||
it "should not be valid if #{attribute} is blank" do
|
||||
instance.send "#{attribute}=", nil
|
||||
instance.errors_on(attribute).should include(message)
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_validate_presence_of</tt>. See that method for more
|
||||
# details. You'd probably only be using this in a nested example block to compare that
|
||||
# one scenario validates presence and another does not (because of conditions in
|
||||
# <tt>:if/:unless</tt>).
|
||||
def it_should_not_validate_presence_of(attribute, message = default_error_message(:blank))
|
||||
it "should be valid if #{attribute} is blank" do
|
||||
instance.send "#{attribute}=", nil
|
||||
instance.errors_on(attribute).should_not include(message)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd <tt>validates_inclusion_of</tt>
|
||||
# the specified attribute. Takes an optional custom message to match the one in the model's
|
||||
# validation.
|
||||
def it_should_validate_inclusion_of(attribute, options = {}, message = default_error_message(:inclusion))
|
||||
it "should validate #{attribute} is in #{options[:in].to_s}" do
|
||||
# We specifically do not try to go below the range on String and character ranges because that problem set is unpredictable.
|
||||
lower = options[:in].first.respond_to?(:-) ? options[:in].first - 0.0001 : nil
|
||||
higher = options[:in].last.succ
|
||||
|
||||
instance.send "#{attribute}=", lower
|
||||
instance.errors_on(attribute).should include(message)
|
||||
|
||||
instance.send "#{attribute}=", higher
|
||||
instance.errors_on(attribute).should include(message)
|
||||
|
||||
instance.send "#{attribute}=", (lower+higher)/2
|
||||
instance.errors_on(attribute).should_not include(message)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd <tt>validates_numericality_of</tt>
|
||||
# the specified attribute. Takes an optional custom message to match the one in the model's
|
||||
# validation.
|
||||
def it_should_validate_numericality_of(attribute, message = default_error_message(:not_a_number))
|
||||
it "should validate #{attribute} is a numeric" do
|
||||
instance.send "#{attribute}=", "NaN"
|
||||
instance.errors_on(attribute).should include(message)
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_validate_numericality_of</tt>. See that method for more
|
||||
# details. You'd probably only be using this in a nested example block to compare that
|
||||
# one scenario validates presence and another does not (because of conditions in
|
||||
# <tt>:if/:unless</tt>).
|
||||
def it_should_not_validate_numericality_of(attribute, message = default_error_message(:not_a_number))
|
||||
it "should not validate #{attribute} is a numeric" do
|
||||
instance.send "#{attribute}=", "NaN"
|
||||
instance.errors_on(attribute).should_not include(message)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd <tt>validates_confirmation_of</tt>
|
||||
# the specified attribute. Takes an optional custom message to match the one in the model's
|
||||
# validation.
|
||||
def it_should_validate_confirmation_of(attribute, message = default_error_message(:confirmation))
|
||||
it "should validate confirmation of #{attribute}" do
|
||||
dummy_value = dummy_value_for(instance, attribute) || "try a string"
|
||||
instance.send "#{attribute}=", dummy_value
|
||||
instance.send "#{attribute}_confirmation=", dummy_value.succ
|
||||
instance.errors_on(attribute).should include(message)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd <tt>validates_uniqueness_of</tt>
|
||||
# the specified attribute. Takes an optional custom message to match the one in the model's
|
||||
# validation.
|
||||
#
|
||||
# <b>Note:</b> This method will fail completely if <tt>valid_attributes</tt>
|
||||
# does not provide all the attributes needed to create a valid record.
|
||||
def it_should_validate_uniqueness_of(attribute, message = default_error_message(:taken))
|
||||
it "should validate uniqueness of #{attribute}" do
|
||||
previous_instance = instance.class.create!(valid_attributes)
|
||||
instance.attributes = valid_attributes
|
||||
instance.errors_on(attribute).should include(message)
|
||||
previous_instance.destroy
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_validate_uniqueness_of</tt>. See that method for more
|
||||
# details. You'd probably only be using this in a nested example block to compare that
|
||||
# one scenario validates presence and another does not (because of conditions in
|
||||
# <tt>:if/:unless</tt>).
|
||||
def it_should_not_validate_uniqueness_of(attribute, message = default_error_message(:taken))
|
||||
it "should not validate uniqueness of #{attribute}" do
|
||||
previous_instance = instance.class.create!(valid_attributes)
|
||||
instance.attributes = valid_attributes
|
||||
instance.errors_on(attribute).should_not include(message)
|
||||
previous_instance.destroy
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd accepts the specified values as
|
||||
# valid for the specified attribute. This is most likely used with <tt>validates_format_of</tt>
|
||||
# but there's nothing saying it couldn't be another validation.
|
||||
def it_should_accept_as_valid(attribute, *values)
|
||||
values.flatten.each do |value|
|
||||
value_inspect = case value
|
||||
when String : "'#{value}'"
|
||||
when NilClass : "nil"
|
||||
else value
|
||||
end
|
||||
it "should accept #{value_inspect} as a valid #{attribute}" do
|
||||
instance.send "#{attribute}=", value
|
||||
instance.errors_on(attribute).should == []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd does not accept the specified
|
||||
# values as valid for the specified attribute. This is most likely used with
|
||||
# <tt>validates_format_of</tt> but there's nothing saying it couldn't be another validation.
|
||||
# Takes an optional argument <tt>:message => "some custom error messsage"</tt> for
|
||||
# spec'ing the actual error message.
|
||||
def it_should_not_accept_as_valid(attribute, *values)
|
||||
options = values.extract_options!
|
||||
values.flatten.each do |value|
|
||||
value_inspect = case value
|
||||
when String : "'#{value}'"
|
||||
when NilClass : "nil"
|
||||
else value
|
||||
end
|
||||
it "should not accept #{value_inspect} as a valid #{attribute}" do
|
||||
instance.send "#{attribute}=", value
|
||||
if options[:message]
|
||||
instance.errors_on(attribute).should include(options[:message])
|
||||
else
|
||||
instance.should have_at_least(1).errors_on(attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the current model being spec'd doesn't allow mass-assignment
|
||||
# of the specified attribute.
|
||||
def it_should_not_mass_assign(attribute)
|
||||
it "should not allow mass-assignment of #{attribute}" do
|
||||
lambda {
|
||||
instance.send :attributes=, {attribute => dummy_value_for(instance, attribute)}
|
||||
}.should_not change(instance, attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def default_error_message(attribute)
|
||||
if defined?(I18n)
|
||||
I18n.translate attribute, :scope => "activerecord.errors.messages"
|
||||
else
|
||||
ActiveRecord::Errors.default_error_messages[attribute]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,577 @@
|
|||
$:.unshift File.join(File.dirname(__FILE__), "..")
|
||||
require "skinny_spec"
|
||||
|
||||
module LuckySneaks
|
||||
# These methods are designed to be used in your example [read: "it"] blocks
|
||||
# to make your view specs less brittle and more DRY. You might also be interested
|
||||
# in checking out the example block [read: "describe"] level versions in of these
|
||||
# methods which can DRY things up even more:
|
||||
# LuckySneaks::ViewSpecHelpers::ExampleGroupLevelMethods.
|
||||
module ViewSpecHelpers
|
||||
include LuckySneaks::CommonSpecHelpers
|
||||
include LuckySneaks::ViewStubHelpers
|
||||
include ActionController::PolymorphicRoutes
|
||||
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ExampleGroupLevelMethods
|
||||
end
|
||||
|
||||
# Wraps a matcher that checks if the receiver contains a <tt>FORM</tt> element with
|
||||
# its <tt>action</tt> attribute set to the specified path.
|
||||
def submit_to(path)
|
||||
have_tag("form[action=#{path}]")
|
||||
end
|
||||
|
||||
# Wraps a matcher that checks if the receiver contains any of several form elements
|
||||
# that would return sufficient named parameters to allow editing of the specified
|
||||
# attribute on the specified instance. Example:
|
||||
#
|
||||
# response.should allow_editing(@foo, "bar")
|
||||
#
|
||||
# can be satisfied by any of the following HTML elements:
|
||||
#
|
||||
# <input name="foo[bar]" type="text" />
|
||||
# <input name="foo[bar]" type="checkbox" />
|
||||
# <input name="foo[bar_ids][]" type="checkbox" />
|
||||
# <select name="foo[bar]"></select>
|
||||
# <textarea name="foo[bar]"></textarea>
|
||||
def allow_editing(instance, attribute)
|
||||
instance_name = instance.class.name.underscore.downcase
|
||||
column = instance.column_for_attribute(attribute)
|
||||
if column && [Date, Time].include?(column.klass)
|
||||
have_tag(
|
||||
"input[name='#{instance_name}[#{attribute}]'],
|
||||
select[name=?]", /#{instance_name}\[#{attribute}\(.*\)\]/
|
||||
)
|
||||
else
|
||||
have_tag(
|
||||
"input[type='text'][name='#{instance_name}[#{attribute}]'],
|
||||
input[type='password'][name='#{instance_name}[#{attribute}]'],
|
||||
select[name='#{instance_name}[#{attribute}]'],
|
||||
textarea[name='#{instance_name}[#{attribute}]'],
|
||||
input[type='checkbox'][name='#{instance_name}[#{attribute}]'],
|
||||
input[type='checkbox'][name='#{instance_name}[#{attribute.to_s.tableize.singularize}_ids][]'],
|
||||
input[type='radio'][name='#{instance_name}[#{attribute}]']"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps a matcher that checks if the receiver contains a <tt>FORM</tt> element
|
||||
# whose <tt>enctype</tt> attribute is set to <tt>"multipart/form-data"<tt>
|
||||
# and contains an <tt>INPUT</tt> element whose <tt>name</tt> attribute correlates
|
||||
# with the provided instance and attribute.
|
||||
def allow_uploading(instance, attribute)
|
||||
instance_name = instance.class.name.underscore.downcase
|
||||
have_tag("form[enctype='multipart/form-data'] input[type='file'][name='#{instance_name}[#{attribute}]']")
|
||||
end
|
||||
|
||||
# Wraps a matcher that checks if the receiver contains an <tt>A</tt> element (link)
|
||||
# whose <tt>href</tt> attribute is set to the specified path or a <tt>FORM</tt>
|
||||
# element whose <tt>action</tt> attribute is set to the specified path.
|
||||
def have_link_or_button_to(path)
|
||||
have_tag(
|
||||
"a[href='#{path}'],
|
||||
form[action='#{path}'] input,
|
||||
form[action='#{path}'] button"
|
||||
)
|
||||
end
|
||||
alias have_link_to have_link_or_button_to
|
||||
alias have_button_to have_link_or_button_to
|
||||
|
||||
# Wraps <tt>have_link_or_button_to new_polymorphic_path<tt> for the specified class which
|
||||
# corresponds with the <tt>new</tt> method of the controller.
|
||||
#
|
||||
# <b>Note:</b> This method may takes a string or symbol representing the model's name
|
||||
# to send to <tt>have_link_or_button_to_show</tt> or the model's name itself.
|
||||
def have_link_or_button_to_new(name)
|
||||
have_link_or_button_to new_polymorphic_path(name.is_a?(ActiveRecord::Base) ? name : class_for(name))
|
||||
end
|
||||
|
||||
# Wraps <tt>have_link_or_button_to polymorphic_path(instance)<tt> which
|
||||
# corresponds with the <tt>show</tt> method of the controller.
|
||||
def have_link_or_button_to_show(instance)
|
||||
path = polymorphic_path(instance)
|
||||
have_tag(
|
||||
"a[href='#{path}'],
|
||||
form[action='#{path}'][method='get'] input,
|
||||
form[action='#{path}'][method='get'] button,
|
||||
form[action='#{path}'] input[name='_method'][value='get'] + input,
|
||||
form[action='#{path}'] input[name='_method'][value='get'] + button"
|
||||
)
|
||||
end
|
||||
alias have_link_to_show have_link_or_button_to_show
|
||||
alias have_button_to_show have_link_or_button_to_show
|
||||
|
||||
# Wraps <tt>have_link_or_button_to edit_polymorphic_path(instance)<tt> which
|
||||
# corresponds with the <tt>edit</tt> method of the controller.
|
||||
def have_link_or_button_to_edit(instance)
|
||||
have_link_or_button_to edit_polymorphic_path(instance)
|
||||
end
|
||||
alias have_link_to_edit have_link_or_button_to_edit
|
||||
alias have_button_to_edit have_link_or_button_to_edit
|
||||
|
||||
# Wraps a matcher that checks if the receiver contains the HTML created by Rails'
|
||||
# <tt>button_to</tt> helper: to wit, a <tt>FORM</tt> element whose <tt>action</tt>
|
||||
# attribute is pointed at the <tt>polymorphic_path</tt> of the instance
|
||||
# and contains an <tt>INPUT</tt> named "_method" with a value of "delete".
|
||||
def have_button_to_delete(instance)
|
||||
path = polymorphic_path(instance)
|
||||
have_tag(
|
||||
"form[action='#{path}'] input[name='_method'][value='delete'] + input,
|
||||
form[action='#{path}'] input[name='_method'][value='delete'] + button,
|
||||
a[href=\"#{path}\"][onclick*=\"f.method = 'POST'\"][onclick*=\"m.setAttribute('name', '_method'); m.setAttribute('value', 'delete')\"]"
|
||||
)
|
||||
end
|
||||
|
||||
# Creates a <tt>mock_model</tt> instance and adds it to the <tt>assigns</tt> collection
|
||||
# using either the name passed as the first argument or the underscore version
|
||||
# of its class name. Accepts optional arguments to stub out additional methods
|
||||
# (and their return values) on the <tt>mock_model</tt> instance. Example:
|
||||
#
|
||||
# mock_and_assign(Foo, :stub => {:bar => "bar"})
|
||||
#
|
||||
# is the same as running <tt>assigns[:foo] = mock_model(Foo, :bar => "bar")</tt>.
|
||||
#
|
||||
# mock_and_assign(Foo, "special_foo", :stub => {:bar => "baz"})
|
||||
#
|
||||
# is the same as running <tt>assigns[:special_foo] = mock_model(Foo, :bar => "baz").
|
||||
#
|
||||
# <b>Note:</b> Adding to the assigns collection returns the object added, so this can
|
||||
# be chained a la <tt>@foo = mock_and_assign(Foo)</tt>.
|
||||
def mock_and_assign(klass, *args)
|
||||
options = args.extract_options!
|
||||
mocked = if options[:stub]
|
||||
self.respond_to?(:stub_model) ? stub_model(klass, options[:stub]) : mock_model(klass, options[:stub])
|
||||
else
|
||||
self.respond_to?(:stub_model) ? stub_model(klass) : mock_model(klass)
|
||||
end
|
||||
yield mocked if block_given?
|
||||
self.assigns[args.first || "#{klass}".underscore] = mocked
|
||||
end
|
||||
|
||||
# Creates an array of <tt>mock_model</tt> instances in the manner of
|
||||
# <tt>mock_and_assign</tt>. Accepts <tt>option[:size]</tt> which sets the size
|
||||
# of the array (default is 3).
|
||||
def mock_and_assign_collection(klass, *args)
|
||||
options = args.extract_options!
|
||||
return_me = Array.new(options[:size] || 3) do
|
||||
mocked = if options[:stub]
|
||||
self.respond_to?(:stub_model) ? stub_model(klass, options[:stub]) : mock_model(klass, options[:stub])
|
||||
else
|
||||
self.respond_to?(:stub_model) ? stub_model(klass) : mock_model(klass)
|
||||
end
|
||||
yield mocked if block_given?
|
||||
mocked
|
||||
end
|
||||
self.assigns[args.first || "#{klass}".tableize] = return_me
|
||||
end
|
||||
|
||||
private
|
||||
def do_render
|
||||
if @the_template
|
||||
render @the_template
|
||||
elsif File.exists?(File.join(RAILS_ROOT, "app/views", class_description_text))
|
||||
render class_description_text
|
||||
else
|
||||
error_message = "Cannot determine template for render. "
|
||||
error_message << "Please define @the_template in the before block "
|
||||
error_message << "or name your describe block so that it indicates the correct template."
|
||||
raise NameError, error_message
|
||||
end
|
||||
end
|
||||
|
||||
# These methods are designed to be used at the example group [read: "describe"] level
|
||||
# to simplify and DRY up common expectations. Most of these methods are wrappers for
|
||||
# matchers which can also be used on the example level [read: within an "it" block]. See
|
||||
# LuckySneaks::ViewSpecHelpers for more information.
|
||||
module ExampleGroupLevelMethods
|
||||
include LuckySneaks::CommonSpecHelpers
|
||||
|
||||
# Creates an expectation which calls <tt>submit_to</tt> on the response
|
||||
# from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a Proc to evaluate the route not simply a named route
|
||||
# helper, which would be undefined in the scope of the example block.
|
||||
def it_should_submit_to(hint = nil, &route)
|
||||
if hint.nil? && route.respond_to?(:to_ruby)
|
||||
hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
|
||||
end
|
||||
it "should submit to #{(hint || route)}" do
|
||||
do_render
|
||||
response.should submit_to(instance_eval(&route))
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_submit_to</tt>. See that method for more
|
||||
# details.
|
||||
def it_should_not_submit_to(hint = nil, &route)
|
||||
if hint.nil? && route.respond_to?(:to_ruby)
|
||||
hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
|
||||
end
|
||||
it "should not submit to #{(hint || route)}" do
|
||||
do_render
|
||||
response.should_not submit_to(instance_eval(&route))
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the template uses Rails' <tt>form_for</tt> to generate
|
||||
# the proper form action and method to create or update the specified object.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name to create the expectation for <tt>form_for</tt>
|
||||
# not an instance variable, which would be nil in the scope of the example block.
|
||||
# If you use namespacing for your <tt>form_for</tt>, you'll have to manually write out
|
||||
# a similar spec.
|
||||
def it_should_have_form_for(name, options = {})
|
||||
it "should have a form_for(@#{name})" do
|
||||
if options.empty?
|
||||
template.should_receive(:form_for).with(instance_for(name))
|
||||
else
|
||||
template.should_receive(:form_for).with(instance_for(name), hash_including(options))
|
||||
end
|
||||
do_render
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_have_form_for</tt>. See that method for more
|
||||
# details.
|
||||
def it_should_not_have_form_for(name, options = {})
|
||||
it "should not have a form_for(@#{name})" do
|
||||
if options.empty?
|
||||
template.should_not_receive(:form_for).with(instance_for(name))
|
||||
else
|
||||
template.should_not_receive(:form_for).with(instance_for(name), hash_including(options))
|
||||
end
|
||||
do_render
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation which calls <tt>allow_editing</tt> on the rendered
|
||||
# template for each attribute specified. See the docs for <tt>allow_editing</tt>
|
||||
# for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name to send to <tt>allow_editing</tt>
|
||||
# not an instance variable, which would be nil in the scope of the example block.
|
||||
def it_should_allow_editing(instance_name, *attributes)
|
||||
attributes.flatten!
|
||||
attributes.each do |attribute|
|
||||
it "should allow editing of @#{instance_name}##{attribute}" do
|
||||
do_render
|
||||
response.should allow_editing(instance_for(instance_name), attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_allow_editing</tt>. See that method for more
|
||||
# details.
|
||||
def it_should_not_allow_editing(instance_name, *attributes)
|
||||
attributes.flatten!
|
||||
attributes.each do |attribute|
|
||||
it "should not allow editing of @#{instance_name}##{attribute}" do
|
||||
do_render
|
||||
response.should_not allow_editing(instance_for(instance_name), attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation which calls <tt>allow_uploading</tt> on the rendered
|
||||
# template for each attribute specified. See the docs for <tt>allow_uploading</tt>
|
||||
# for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name to send to <tt>allow_uploading</tt>
|
||||
# not an instance variable, which would be nil in the scope of the example block.
|
||||
def it_should_allow_uploading(instance_name, *attributes)
|
||||
attributes.flatten!
|
||||
attributes.each do |attribute|
|
||||
it "should allow editing of @#{instance_name}##{attribute}" do
|
||||
do_render
|
||||
response.should allow_uploading(instance_for(instance_name), attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_allow_uploading</tt>. See that method for more
|
||||
# details.
|
||||
def it_should_not_allow_uploading(instance_name, *attributes)
|
||||
attributes.flatten!
|
||||
attributes.each do |attribute|
|
||||
it "should not allow editing of @#{instance_name}##{attribute}" do
|
||||
do_render
|
||||
response.should_not allow_uploading(instance_for(instance_name), attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation that the rendered template contains a <tt>FORM</tt> element
|
||||
# (<tt>INPUT</tt>, <tt>TEXTAREA</tt>, or <tt>SELECT</tt>) with the specified name.
|
||||
def it_should_have_form_element_for(name)
|
||||
it "should have a form element named '#{name}'" do
|
||||
do_render
|
||||
response.should have_tag(
|
||||
"form input[name='#{name}'],
|
||||
form textarea[name='#{name}'],
|
||||
form select[name='#{name}']"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_have_form_element_for</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_have_form_element_for(name)
|
||||
it "should not have a form element named '#{name}'" do
|
||||
do_render
|
||||
response.should_not have_tag(
|
||||
"form input[name='#{name}'],
|
||||
form textarea[name='#{name}'],
|
||||
form select[name='#{name}']"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to</tt> on the response
|
||||
# from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a block to evaluate the route in the example context
|
||||
# instead of the example group context.
|
||||
def it_should_link_to(hint = nil, &route)
|
||||
if hint.nil? && route.respond_to?(:to_ruby)
|
||||
hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
|
||||
end
|
||||
it "should have a link/button to #{(hint || route)}" do
|
||||
do_render
|
||||
response.should have_link_or_button_to(instance_eval(&route))
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to it_should_link_to
|
||||
alias it_should_have_button_to it_should_link_to
|
||||
alias it_should_have_button_or_link_to it_should_link_to
|
||||
|
||||
# Negative version of <tt>it_should_link_to</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_link_to(hint = nil, &route)
|
||||
if hint.nil? && route.respond_to?(:to_ruby)
|
||||
hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
|
||||
end
|
||||
it "should have a link/button to #{(hint || route)}" do
|
||||
do_render
|
||||
response.should_not have_link_or_button_to(instance_eval(&route))
|
||||
end
|
||||
end
|
||||
alias it_should_not_have_link_to it_should_not_link_to
|
||||
alias it_should_not_have_button_to it_should_not_link_to
|
||||
alias it_should_not_have_button_or_link_to it_should_not_link_to
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_new</tt> on the response
|
||||
# from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method may takes a string or symbol representing the model's name
|
||||
# to send to <tt>have_link_or_button_to_show</tt> or the model's name itself.
|
||||
def it_should_link_to_new(name)
|
||||
it "should have a link/button to create a new #{name}" do
|
||||
do_render
|
||||
response.should have_link_or_button_to_new(name)
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_new it_should_link_to_new
|
||||
alias it_should_have_button_to_new it_should_link_to_new
|
||||
alias it_should_have_button_or_link_to_new it_should_link_to_new
|
||||
|
||||
# Negative version of <tt>it_should_link_to_show</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_link_to_new(name)
|
||||
it "should have a link/button to create a new #{name}" do
|
||||
do_render
|
||||
response.should_not have_link_or_button_to_new(name)
|
||||
end
|
||||
end
|
||||
alias it_should_not_have_link_to_new it_should_not_link_to_new
|
||||
alias it_should_not_have_button_to_new it_should_not_link_to_new
|
||||
alias it_should_not_have_button_or_link_to_new it_should_not_link_to_new
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_show</tt> on the response
|
||||
# from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name to send to <tt>have_link_or_button_to_show</tt>
|
||||
# not an instance variable, which would be nil in the scope of the example block.
|
||||
def it_should_link_to_show(name)
|
||||
it "should have a link/button to show @#{name}" do
|
||||
do_render
|
||||
response.should have_link_or_button_to_show(instance_for(name))
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_show it_should_link_to_show
|
||||
alias it_should_have_button_to_show it_should_link_to_show
|
||||
alias it_should_have_button_or_link_to_show it_should_link_to_show
|
||||
|
||||
# Negative version of <tt>it_should_link_to_show</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_link_to_show(name)
|
||||
it "should have a link/button to show @#{name}" do
|
||||
do_render
|
||||
response.should_not have_link_or_button_to_show(instance_for(name))
|
||||
end
|
||||
end
|
||||
alias it_should_not_have_link_to_show it_should_not_link_to_show
|
||||
alias it_should_not_have_button_to_show it_should_not_link_to_show
|
||||
alias it_should_not_have_button_or_link_to_show it_should_not_link_to_show
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_show</tt>
|
||||
# for each member of the instance variable matching the specified name
|
||||
# on the response from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name and not an instance variable, which would be nil
|
||||
# in the scope of the example block.
|
||||
def it_should_link_to_show_each(name)
|
||||
it "should have a link/button to show each member of @#{name}" do
|
||||
do_render
|
||||
instance_for(name).each do |member|
|
||||
response.should have_link_or_button_to_show(member)
|
||||
end
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_show_each it_should_link_to_show_each
|
||||
alias it_should_have_button_to_show_each it_should_link_to_show_each
|
||||
alias it_should_have_button_or_link_to_show_each it_should_link_to_show_each
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_edit</tt> on the response
|
||||
# from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name to send to <tt>have_link_or_button_to_edit</tt>
|
||||
# not an instance variable, which would be nil in the scope of the example block.
|
||||
def it_should_link_to_edit(name)
|
||||
it "should have a link/button to edit @#{name}" do
|
||||
do_render
|
||||
response.should have_link_or_button_to_edit(instance_for(name))
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_edit it_should_link_to_edit
|
||||
alias it_should_have_button_to_edit it_should_link_to_edit
|
||||
alias it_should_have_button_or_link_to_edit it_should_link_to_edit
|
||||
|
||||
# Negative version of <tt>it_should_link_to_edit</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_link_to_edit(name)
|
||||
it "should have a link/button to edit @#{name}" do
|
||||
do_render
|
||||
response.should_not have_link_or_button_to_edit(instance_for(name))
|
||||
end
|
||||
end
|
||||
alias it_should_not_have_link_to_edit it_should_not_link_to_edit
|
||||
alias it_should_not_have_button_to_edit it_should_not_link_to_edit
|
||||
alias it_should_not_have_button_or_link_to_edit it_should_not_link_to_edit
|
||||
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_edit</tt>
|
||||
# for each member of the instance variable matching the specified name
|
||||
# on the response from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name and not an instance variable, which would be nil
|
||||
# in the scope of the example block.
|
||||
def it_should_link_to_edit_each(name)
|
||||
it "should have a link/button to edit each member of @#{name}" do
|
||||
do_render
|
||||
instance_for(name).each do |member|
|
||||
response.should have_link_or_button_to_edit(member)
|
||||
end
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_edit_each it_should_link_to_edit_each
|
||||
alias it_should_have_button_to_edit_each it_should_link_to_edit_each
|
||||
alias it_should_have_button_or_link_to_edit_each it_should_link_to_edit_each
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_delete</tt> on the response
|
||||
# from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name to send to <tt>have_link_or_button_to_delete</tt>
|
||||
# not an instance variable, which would be nil in the scope of the example block.
|
||||
def it_should_link_to_delete(name)
|
||||
it "should have a link/button to delete @#{name}" do
|
||||
do_render
|
||||
response.should have_button_to_delete(instance_for(name))
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_delete it_should_link_to_delete
|
||||
alias it_should_have_button_to_delete it_should_link_to_delete
|
||||
alias it_should_have_button_or_link_to_delete it_should_link_to_delete
|
||||
|
||||
# Negative version of <tt>it_should_link_to_delete</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_link_to_delete(name)
|
||||
it "should not have a link/button to delete @#{name}" do
|
||||
do_render
|
||||
response.should_not have_button_to_delete(instance_for(name))
|
||||
end
|
||||
end
|
||||
alias it_should_not_have_link_to_delete it_should_not_link_to_delete
|
||||
alias it_should_not_have_button_to_delete it_should_not_link_to_delete
|
||||
alias it_should_not_have_button_or_link_to_delete it_should_not_link_to_delete
|
||||
|
||||
# Creates an expectation which calls <tt>have_link_or_button_to_delete</tt>
|
||||
# for each member of the instance variable matching the specified name
|
||||
# on the response from rendering the template. See that method for more details.
|
||||
#
|
||||
# <b>Note:</b> This method takes a string or symbol representing the instance
|
||||
# variable's name and not an instance variable, which would be nil
|
||||
# in the scope of the example block.
|
||||
def it_should_link_to_delete_each(name)
|
||||
it "should have a link/button to delete each member of @#{name}" do
|
||||
do_render
|
||||
instance_for(name).each do |member|
|
||||
response.should have_button_to_delete(member)
|
||||
end
|
||||
end
|
||||
end
|
||||
alias it_should_have_link_to_delete_each it_should_link_to_delete_each
|
||||
alias it_should_have_button_to_delete_each it_should_link_to_delete_each
|
||||
alias it_should_have_button_or_link_to_delete_each it_should_link_to_delete_each
|
||||
|
||||
# Creates an expectation that the template should call <tt>render :partial</tt>
|
||||
# with the specified template.
|
||||
def it_should_render_partial(name)
|
||||
it "should render :partial => '#{name}'" do
|
||||
template.should_receive(:render).with(hash_including(:partial => name))
|
||||
do_render
|
||||
end
|
||||
end
|
||||
|
||||
# Negative version of <tt>it_should_render_partial</tt>. See that method
|
||||
# for more details.
|
||||
def it_should_not_render_partial(name)
|
||||
it "should not render :partial => '#{name}'" do
|
||||
template.should_not_receive(:render).with(hash_including(:partial => name))
|
||||
do_render
|
||||
end
|
||||
end
|
||||
|
||||
# Sets <tt>@the_template</tt> (for use in <tt>do_render</tt>) using the current
|
||||
# example group description. Example:
|
||||
#
|
||||
# describe "users/index.haml.erb" do
|
||||
# use_describe_for_template!
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# This is equivalent to setting <tt>@the_template = "users/index.haml.erb"</tt>
|
||||
# in a before block.
|
||||
def use_describe_for_template!
|
||||
template = self_description_text
|
||||
if File.exists?(File.join(RAILS_ROOT, "app/views", template))
|
||||
before(:each) do
|
||||
@the_template = template
|
||||
end
|
||||
else
|
||||
error_message = "You called use_describe_for_template! "
|
||||
error_message << "but 'app/views/#{template}' does not exist. "
|
||||
raise NameError, error_message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
$:.unshift File.join(File.dirname(__FILE__), "..")
|
||||
require "skinny_spec"
|
||||
|
||||
module LuckySneaks
|
||||
# These methods are designed to be used in your example <tt>before</tt> blocks to accomplish
|
||||
# a whole lot of functionality with just a tiny bit of effort.
|
||||
module ViewStubHelpers
|
||||
# Shorthand for the following stub:
|
||||
#
|
||||
# template.stub!(:render).with(hash_including(:partial => anything))
|
||||
def stub_partial_rendering!
|
||||
template.stub!(:render).with(hash_including(:partial => anything))
|
||||
end
|
||||
end
|
||||
end
|
||||
26
backup.rails2.3/plugins/skinny_spec/lib/skinny_spec.rb
Normal file
26
backup.rails2.3/plugins/skinny_spec/lib/skinny_spec.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Let's make sure everyone else is loaded
|
||||
require File.expand_path(RAILS_ROOT + "/config/environment")
|
||||
require 'spec'
|
||||
require 'spec/rails'
|
||||
begin
|
||||
require 'ruby2ruby'
|
||||
rescue LoadError
|
||||
puts "-----"
|
||||
puts "Attention: skinny_spec requires ruby2ruby for nicer route descriptions"
|
||||
puts "It is highly recommended that you install it: sudo gem install ruby2ruby"
|
||||
puts "-----"
|
||||
end
|
||||
|
||||
# Let's load our family now
|
||||
require "lucky_sneaks/common_spec_helpers"
|
||||
require "lucky_sneaks/controller_request_helpers"
|
||||
require "lucky_sneaks/controller_spec_helpers"
|
||||
require "lucky_sneaks/controller_stub_helpers"
|
||||
require "lucky_sneaks/model_spec_helpers"
|
||||
require "lucky_sneaks/view_spec_helpers"
|
||||
|
||||
# Let's all come together
|
||||
Spec::Rails::Example::ViewExampleGroup.send :include, LuckySneaks::ViewSpecHelpers
|
||||
Spec::Rails::Example::HelperExampleGroup.send :include, LuckySneaks::CommonSpecHelpers
|
||||
Spec::Rails::Example::ControllerExampleGroup.send :include, LuckySneaks::ControllerSpecHelpers
|
||||
Spec::Rails::Example::ModelExampleGroup.send :include, LuckySneaks::ModelSpecHelpers
|
||||
46
backup.rails2.3/plugins/swf_fu/CHANGELOG.rdoc
Normal file
46
backup.rails2.3/plugins/swf_fu/CHANGELOG.rdoc
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
= swf_fu --- History
|
||||
|
||||
== Version 1.4.0 - May 8, 2010
|
||||
|
||||
* Any option can be a block, in which case it is called (with the source swf passed as argument)
|
||||
|
||||
== Version 1.3.1 - February 5, 2010
|
||||
|
||||
* Improved compatibility with Rails 3.0: swf_tag now outputs html_safe content.
|
||||
|
||||
* Got rid of deprecation warning in Rails 2.2+ when using swf_tag in block form.
|
||||
|
||||
== Version 1.3.0 - June 20, 2009
|
||||
|
||||
* Updated to swf_object v2.2. Change should not be noticeable to users, except compatibility improvements and better auto install. Added the option +switch_off_auto_hide_show+.
|
||||
|
||||
== Version 1.2.0 - January 14, 2009
|
||||
|
||||
* flashvars[:id] will now default to the DOM id of the object. I didn't want to have any extra defaults than the very basic ones, but there is no easy way to get this from Flash (see http://www.actionscript.org/forums/showthread.php3?t=136044 ) and no easy way to specify that using +swf_default_options+.
|
||||
* If flashvars is a string (e.g. "myVar=myValue") it will be parsed into a hash so that the behaviour for default values apply to strings or hashes. swf_default_options[:flashvars] can also be a string and will also be parsed before being merged.
|
||||
* Small bug fix: the options passed as hashes (:flashvars, :parameters and :html_options) were changed if swf_default_options[:flashvars, ...] existed. They are now left unchanged.
|
||||
|
||||
== Version 1.1.0 - January 3, 2009
|
||||
|
||||
* Improved the way to specify alternate content
|
||||
|
||||
== Version 1.0.3 - January 3, 2009
|
||||
|
||||
* Improved javascript initialization
|
||||
|
||||
:initialize => [1, 2, 3] # produces in javascript: obj.initialize(1,2,3) instead of ([1,2,3])
|
||||
# no :initialize produces in javascript: obj.initialize() instead of (null)
|
||||
:initialize => nil # stil produces obj.initialize(null)
|
||||
|
||||
== Version 1.0.2 - January 3, 2009
|
||||
|
||||
* Bug fix for flashvars in dynamic method
|
||||
|
||||
== Version 1.0.1 - January 2, 2009
|
||||
|
||||
* File reorganization
|
||||
* Bug fix for default options
|
||||
|
||||
== Version 1.0 - X-mas, 2008
|
||||
|
||||
=== Initial release.
|
||||
31
backup.rails2.3/plugins/swf_fu/FLASH_OBJECT.rdoc
Normal file
31
backup.rails2.3/plugins/swf_fu/FLASH_OBJECT.rdoc
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
== Compatibility with FlashObject
|
||||
|
||||
This document is intended for users of FlashObject, a (much older) swf embedding plugin that inspired swf_fu.
|
||||
|
||||
You can choose to:
|
||||
|
||||
1) keep both. They won't interfere and +flashobject_tag+ will continue to use the older SWFObject 1.5 library.
|
||||
|
||||
2) remove FlashObject:
|
||||
|
||||
script/plugin remove flashobject_helper
|
||||
|
||||
You can also manually remove <tt>javascripts/flashobject.js</tt>
|
||||
|
||||
+swf_fu+ will take over the +flashobject_tag+ and will use the new SWFObject 2.2 library.
|
||||
This should not have impacts as long as:
|
||||
* your swf path is absolute (e.g. "/path/to/my_flash.swf"). If it is relative, move your swf file from 'public/' to the new 'public/swfs/' asset folder
|
||||
* you include the default javascripts (otherwise you need to include 'swfobject' explicitely and stop including 'flashobject')
|
||||
* you don't use the javascript object before the page is loaded. SWFObject 2.2 makes the changes to the web page later
|
||||
* you don't rely on the +verify_file_exists+ option (it doesn't do anything anymore)
|
||||
|
||||
In either case 1 or 2, you change existing calls to +flashobject_tag+ for +swf_tag+ at your leisure.
|
||||
The interface is similar and the main differences are some options name changes:
|
||||
:flash_id => :id
|
||||
:variables => :flashvars
|
||||
:background_color => options[:parameters][:bgcolor]
|
||||
|
||||
Moreover, the following defaults are gone:
|
||||
:flashvars[:lzproxied]
|
||||
:parameters[:scale]
|
||||
:parameters[:bgcolor]
|
||||
29
backup.rails2.3/plugins/swf_fu/LICENSE
Normal file
29
backup.rails2.3/plugins/swf_fu/LICENSE
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# swf_fu plugin for rails
|
||||
# Copyright (c) 2010, Marc-André Lafortune.
|
||||
# All rights reserved.
|
||||
# Inspired by FlashObject by Davide D'Agostino aka DAddYE (http://www.lipsiasoft.com)
|
||||
# Uses SWFObject.js 2.1 (http://code.google.com/p/swfobject)
|
||||
#
|
||||
# Licensed under the terms of the (modified) BSD License below:
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the <organization> nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY
|
||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
92
backup.rails2.3/plugins/swf_fu/README.rdoc
Normal file
92
backup.rails2.3/plugins/swf_fu/README.rdoc
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
= +swf_fu+
|
||||
|
||||
With the +swf_fu+ plugin, rails treats your swf files like any other asset (images, javascripts, etc...).
|
||||
|
||||
+swf_fu+ (pronounced "swif-fu", bonus joke for french speakers) uses SWFObject 2.2 to embed swf objects in HTML and supports all its options.
|
||||
SWFObject 2 is such a nice library that Adobe now uses it as the official way to embed swf!
|
||||
SWFObject's project can be found at http://code.google.com/p/swfobject
|
||||
|
||||
+swf_fu+ has been tested with rails v2.0 up to v3.0b and has decent test coverage so <tt>rake test:plugins</tt> should reveal any incompatibility. Comments and pull requests welcome: http://github.com/marcandre/swf_fu
|
||||
|
||||
== Install
|
||||
|
||||
Assuming you have git[http://git-scm.com/] installed (check with <tt>git version</tt>), it is easy to install from your applications directory:
|
||||
|
||||
rails plugin install git://github.com/marcandre/swf_fu.git # rails 3
|
||||
|
||||
script/plugin install git://github.com/marcandre/swf_fu.git # rails 2 (starting at 2.0.2)
|
||||
|
||||
For older versions of +rails+ or without +git+, you can always download
|
||||
+swf_fu+ from github[http://github.com/marcandre/swf_fu/archives/master] and then install it manually:
|
||||
|
||||
rails plugin install ~/Download/swf_fu # rails 3
|
||||
|
||||
script/plugin install ~/Downloads/swf_fu # rails 2.x
|
||||
|
||||
== Usage
|
||||
|
||||
=== Embedding in HTML
|
||||
|
||||
To embed a swf file, use +swf_tag+:
|
||||
<%= swf_tag "i_like_flashing" %>
|
||||
|
||||
Exactly like images and javascripts, +swf_tag+ will use +swf_path+
|
||||
to determine the path of the swf file; it will assume it is in <tt>/public/swfs/</tt>
|
||||
unless specified otherwise and it will add the ".swf" extension automatically.
|
||||
|
||||
You can specify alternate content either with the options <tt>:alt => "Get Flash!"</tt> or you can use +swf_tag+ as a block:
|
||||
|
||||
<% swf_tag "i_like_flashing" do %>
|
||||
Get Flash
|
||||
<% end %>
|
||||
|
||||
=== Options
|
||||
|
||||
* <tt>:id</tt> - the DOM +id+ of the flash +object+ element that is used to contain the Flash object; defaults to the name of the swf in +source+
|
||||
* <tt>:width, :height</tt> - the width & height of the Flash object. Defaults to "100%". These could also specified using :size
|
||||
* <tt>:size</tt> - the size of the Flash object, in the form "400x300".
|
||||
* <tt>:mode</tt> - Either :dynamic (default) or :static. Refer to SWFObject's doc[http://code.google.com/p/swfobject/wiki/documentation#Should_I_use_the_static_or_dynamic_publishing_method?]
|
||||
* <tt>:flashvars</tt> - a Hash of variables that are passed to the swf. Can also be a string like <tt>"foo=bar&hello=world"</tt>. Defaults to <tt>{:id => the DOM id}</tt>
|
||||
* <tt>:parameters</tt> - a Hash of configuration parameters for the swf. See Adobe's doc[http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_12701#optional]
|
||||
* <tt>:html_options</tt> - a Hash of extra html options for the <tt>object</tt> tag.
|
||||
* <tt>:alt</tt> - HTML text that is displayed when the Flash player is not available. Defaults to a "Get Flash" image pointing to Adobe Flash's installation page. This can also be specified as a block (see embedding section). In Rails 3, this text is _assumed_ to be HTML, so there is no need to call +html_safe+ on it.
|
||||
* <tt>:flash_version</tt> - the version of the Flash player that is required (e.g. "7" (default) or "8.1.0")
|
||||
* <tt>:auto_install</tt> - a swf file that will upgrade flash player if needed (defaults to "expressInstall" which was installed by +swf_fu+)
|
||||
* <tt>:javascript_class</tt> - specify a javascript class (e.g. "MyFlash") for your flash object. If it exists, the initialize method will be called.
|
||||
* <tt>:initialize</tt> - arguments to pass to the initialization method of your javascript class.
|
||||
* <tt>:div_id</tt> - the DOM +id+ of the containing div itself. Defaults to <tt>"#{option[:id]}_div"</tt>
|
||||
* <tt>:switch_off_auto_hide_show</tt> - switch off SWFObject's default hide/show behavior. SWFObject temporarily hides your SWF or alternative content until the library has decided which content to display. Defaults to nil.
|
||||
|
||||
You can override these default options with a global setting:
|
||||
|
||||
ActionView::Base.swf_default_options = {:mode => :static} # All swf_tag will use the static mode by default
|
||||
|
||||
Any of these options can be a +Proc+, in which case it will be called each time swf_tag is called.
|
||||
|
||||
For example, the following will generate unique IDs:
|
||||
|
||||
my_swf_counter = 0
|
||||
ActionView::Base.swf_default_options[:id] = Proc.new{"swf_unique_id_#{my_swf_counter+=1}"}
|
||||
|
||||
=== Javascript
|
||||
|
||||
+swf_fu+ will add 'swfobject' to the list of default javascript files. If you don't include
|
||||
the default javascripts, a simple <tt>javascript_include "swfobject"</tt> is needed.
|
||||
|
||||
=== swf_path
|
||||
|
||||
+swf_tag+ implements and relies on +swf_path+ which behaves in a similar fashion to +image_path+, +javascript_path+, etc...:
|
||||
|
||||
swf_path("example") => "/swfs/example.swf"
|
||||
swf_path("example.swf") => "/swfs/example.swf"
|
||||
swf_path("fonts/optima") => "/swfs/fonts/optima.swf"
|
||||
swf_path("/fonts/optima") => "/fonts/optima.swf"
|
||||
swf_path("http://www.example.com/game.swf") => "http://www.example.com/game.swf"
|
||||
|
||||
It takes into account the global setting +asset_host+, like any other asset:
|
||||
|
||||
ActionController::Base.asset_host = "http://assets.example.com"
|
||||
image_path("logo.jpg") => "http://assets.example.com/images/logo.jpg"
|
||||
swf_path("fonts/optima") => "http://assets.example.com/swfs/fonts/optima.swf""
|
||||
|
||||
Copyright (c) 2010 Marc-André Lafortune, released under the BSD license
|
||||
22
backup.rails2.3/plugins/swf_fu/Rakefile
Normal file
22
backup.rails2.3/plugins/swf_fu/Rakefile
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the swf_fu plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the swf_fu plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Swf Fu'
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-m README.rdoc'
|
||||
rdoc.rdoc_files.include('*.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
File diff suppressed because one or more lines are too long
BIN
backup.rails2.3/plugins/swf_fu/assets/swfs/expressInstall.swf
Executable file
BIN
backup.rails2.3/plugins/swf_fu/assets/swfs/expressInstall.swf
Executable file
Binary file not shown.
14
backup.rails2.3/plugins/swf_fu/init.rb
Normal file
14
backup.rails2.3/plugins/swf_fu/init.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
ActionView::Helpers::AssetTagHelper rescue require 'action_view/helpers/asset_tag_helper' # Might be needed in some testing environments
|
||||
require File.dirname(__FILE__) + "/lib/action_view/helpers/swf_fu_helper"
|
||||
require File.dirname(__FILE__) + "/lib/action_view/helpers/asset_tag_helper/swf_asset"
|
||||
|
||||
# ActionView::Helpers is for recent rails version, ActionView::Base for older ones (in which case ActionView::Helpers::AssetTagHelper is also needed for tests...)
|
||||
ActionView::Helpers.class_eval { include ActionView::Helpers::SwfFuHelper } # For recent rails version...
|
||||
ActionView::Base.class_eval { include ActionView::Helpers::SwfFuHelper } # ...and for older ones
|
||||
ActionView::TestCase.class_eval { include ActionView::Helpers::SwfFuHelper } if defined? ActionView::TestCase # ...for tests in older versions
|
||||
|
||||
begin
|
||||
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :default => ["swfobject"]
|
||||
rescue NoMethodError # I think this might fail in Rails 2.1.x
|
||||
ActionView::Helpers::AssetTagHelper.register_javascript_include_default 'swfobject'
|
||||
end
|
||||
24
backup.rails2.3/plugins/swf_fu/install.rb
Normal file
24
backup.rails2.3/plugins/swf_fu/install.rb
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
require "fileutils"
|
||||
|
||||
# Some paths
|
||||
src = File.dirname(__FILE__)+"/assets"
|
||||
dest = File.dirname(__FILE__)+"/../../../public"
|
||||
|
||||
filename = "#{dest}/javascripts/swfobject.js"
|
||||
unless File.exist?(filename)
|
||||
FileUtils.cp "#{src}/javascripts/swfobject.js", filename
|
||||
puts "Copying 'swfobject.js'"
|
||||
end
|
||||
|
||||
unless File.exist?("#{dest}/swfs/")
|
||||
FileUtils.mkdir "#{dest}/swfs/"
|
||||
puts "Creating new 'swfs' directory for swf assets"
|
||||
end
|
||||
|
||||
filename = "#{dest}/swfs/expressInstall.swf"
|
||||
unless File.exist?(filename)
|
||||
FileUtils.cp "#{src}/swfs/expressInstall.swf", filename
|
||||
puts "Copying 'expressInstall.swf', the default flash auto-installer."
|
||||
end
|
||||
|
||||
puts "Installation done."
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
module ActionView #:nodoc:
|
||||
|
||||
# <tt>ActionView::Base.swf_default_options</tt> is a hash that
|
||||
# will be used to specify defaults in priority to the standard
|
||||
# defaults.
|
||||
class Base
|
||||
@@swf_default_options = {}
|
||||
cattr_accessor :swf_default_options
|
||||
end
|
||||
|
||||
module Helpers # :nodoc:
|
||||
module AssetTagHelper
|
||||
|
||||
# Computes the path to an swf asset in the public 'swfs' directory.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by +swf_tag+ to build the swf path.
|
||||
#
|
||||
# ==== Examples
|
||||
# swf_path("example") # => /swfs/example.swf
|
||||
# swf_path("example.swf") # => /swfs/example.swf
|
||||
# swf_path("fonts/optima") # => /swfs/fonts/optima.swf
|
||||
# swf_path("/fonts/optima") # => /fonts/optima.swf
|
||||
# swf_path("http://www.example.com/game.swf") # => http://www.example.com/game.swf
|
||||
#
|
||||
# It takes into account the global setting +asset_host+, like any other asset:
|
||||
#
|
||||
# ActionController::Base.asset_host = "http://assets.example.com"
|
||||
# image_path("logo.jpg") # => http://assets.example.com/images/logo.jpg
|
||||
# swf_path("fonts/optima") # => http://assets.example.com/swfs/fonts/optima.swf
|
||||
#
|
||||
def swf_path(source)
|
||||
if defined? SwfTag
|
||||
SwfTag.new(self, @controller, source).public_path
|
||||
else
|
||||
compute_public_path(source, SwfAsset::DIRECTORY, SwfAsset::EXTENSION)
|
||||
end
|
||||
end
|
||||
alias_method :path_to_swf, :swf_path # aliased to avoid conflicts with a swf_path named route
|
||||
|
||||
private
|
||||
module SwfAsset # :nodoc:
|
||||
DIRECTORY = 'swfs'.freeze
|
||||
EXTENSION = 'swf'.freeze
|
||||
|
||||
def directory
|
||||
DIRECTORY
|
||||
end
|
||||
|
||||
def extension
|
||||
EXTENSION
|
||||
end
|
||||
end
|
||||
|
||||
# AssetTag is available since 2.1.1 (http://github.com/rails/rails/commit/900fd6eca9dd97d2341e89bcb27d7a82d62965bf )
|
||||
class SwfTag < AssetTag # :nodoc:
|
||||
include SwfAsset
|
||||
end if defined? AssetTag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
module ActionView #:nodoc:
|
||||
module Helpers # :nodoc:
|
||||
module SwfFuHelper
|
||||
# Returns a set of tags that display a Flash object within an
|
||||
# HTML page.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:id</tt> - the DOM +id+ of the flash +object+ element that is used to contain the Flash object; defaults to the name of the swf in +source+
|
||||
# * <tt>:width, :height</tt> - the width & height of the Flash object. Defaults to "100%". These could also specified using :size
|
||||
# * <tt>:size</tt> - the size of the Flash object, in the form "400x300".
|
||||
# * <tt>:mode</tt> - Either :dynamic (default) or :static. Refer to SWFObject's doc[http://code.google.com/p/swfobject/wiki/documentation#Should_I_use_the_static_or_dynamic_publishing_method?]
|
||||
# * <tt>:flashvars</tt> - a Hash of variables that are passed to the swf. Can also be a string like <tt>"foo=bar&hello=world"</tt>
|
||||
# * <tt>:parameters</tt> - a Hash of configuration parameters for the swf. See Adobe's doc[http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_12701#optional]
|
||||
# * <tt>:alt</tt> - HTML text that is displayed when the Flash player is not available. Defaults to a "Get Flash" image pointing to Adobe Flash's installation page.
|
||||
# * <tt>:flash_version</tt> - the version of the Flash player that is required (e.g. "7" (default) or "8.1.0")
|
||||
# * <tt>:auto_install</tt> - a swf file that will upgrade flash player if needed (defaults to "expressInstall" which was installed by swf_fu)
|
||||
# * <tt>:javascript_class</tt> - specify a javascript class (e.g. "MyFlash") for your flash object. The initialize method will be called when the flash object is ready.
|
||||
# * <tt>:initialize</tt> - arguments to pass to the initialization method of your javascript class.
|
||||
# * <tt>:div_id</tt> - the DOM +id+ of the containing div itself. Defaults to <tt>"#{option[:id]}"_div</tt>
|
||||
#
|
||||
def swf_tag(source, options={}, &block)
|
||||
Generator.new(source, options, self).generate(&block)
|
||||
end
|
||||
|
||||
# For compatibility with the older FlashObject.
|
||||
# It modifies the given options before calling +swf_tag+.
|
||||
# See FLASH_OBJECT.rdoc
|
||||
def flashobject_tag_for_compatibility(source, options={})
|
||||
options = options.reverse_merge(
|
||||
:auto_install => nil,
|
||||
:parameters => {:scale => "noscale"},
|
||||
:variables => {:lzproxied => false},
|
||||
:flash_id => "flashcontent_#{rand(1_100)}",
|
||||
:background_color => "#ffffff"
|
||||
)
|
||||
{ :variables => :flashvars, :flash_id => :id }.each{|from, to| options[to] ||= options.delete(from) }
|
||||
options[:parameters][:bgcolor] ||= options.delete(:background_color)
|
||||
swf_tag(source, options)
|
||||
end
|
||||
|
||||
alias_method :flashobject_tag, :flashobject_tag_for_compatibility unless defined? flashobject_tag
|
||||
|
||||
private
|
||||
DEFAULTS = {
|
||||
:width => "100%",
|
||||
:height => "100%",
|
||||
:flash_version => 7,
|
||||
:mode => :dynamic,
|
||||
:auto_install => "expressInstall",
|
||||
:alt => <<-"EOS".squeeze(" ").strip.freeze
|
||||
<a href="http://www.adobe.com/go/getflashplayer">
|
||||
<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
|
||||
</a>
|
||||
EOS
|
||||
}.freeze
|
||||
|
||||
class Generator # :nodoc:
|
||||
VALID_MODES = [:static, :dynamic]
|
||||
def initialize(source, options, view)
|
||||
@view = view
|
||||
@source = view.swf_path(source)
|
||||
options = ActionView::Base.swf_default_options.merge(options)
|
||||
options.each do |key, value|
|
||||
options[key] = value.call(source) if value.respond_to?(:call)
|
||||
end
|
||||
[:html_options, :parameters, :flashvars].each do |k|
|
||||
options[k] = convert_to_hash(options[k]).reverse_merge convert_to_hash(ActionView::Base.swf_default_options[k])
|
||||
end
|
||||
options.reverse_merge!(DEFAULTS)
|
||||
options[:id] ||= source.gsub(/^.*\//, '').gsub(/\.swf$/,'')
|
||||
options[:id] = force_to_valid_id(options[:id])
|
||||
options[:div_id] ||= options[:id]+"_div"
|
||||
options[:div_id] = force_to_valid_id(options[:div_id])
|
||||
options[:width], options[:height] = options[:size].scan(/^(\d*%?)x(\d*%?)$/).first if options[:size]
|
||||
options[:auto_install] &&= @view.swf_path(options[:auto_install])
|
||||
options[:flashvars][:id] ||= options[:id]
|
||||
@mode = options.delete(:mode)
|
||||
@options = options
|
||||
unless VALID_MODES.include? @mode
|
||||
raise ArgumentError, "options[:mode] should be either #{VALID_MODES.join(' or ')}"
|
||||
end
|
||||
end
|
||||
|
||||
def force_to_valid_id(id)
|
||||
id = id.gsub /[^A-Za-z0-9\-_]/, "_" # HTML id can only contain these characters
|
||||
id = "swf_" + id unless id =~ /^[A-Z]/i # HTML id must start with alpha
|
||||
id
|
||||
end
|
||||
|
||||
def generate(&block)
|
||||
if block_given?
|
||||
@options[:alt] = @view.capture(&block)
|
||||
if Rails::VERSION::STRING >= "3.0"
|
||||
send(@mode)
|
||||
elsif Rails::VERSION::STRING < "2.2"
|
||||
@view.concat(send(@mode), block.binding)
|
||||
else
|
||||
@view.concat(send(@mode))
|
||||
end
|
||||
else
|
||||
send(@mode)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
CONCAT = ActiveSupport.const_defined?(:SafeBuffer) ? :safe_concat : :concat
|
||||
def convert_to_hash(s)
|
||||
case s
|
||||
when Hash
|
||||
s
|
||||
when nil
|
||||
{}
|
||||
when String
|
||||
s.split("&").inject({}) do |h, kvp|
|
||||
key, value = kvp.split("=")
|
||||
h[key.to_sym] = CGI::unescape(value)
|
||||
h
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "#{s} should be a Hash, a String or nil"
|
||||
end
|
||||
end
|
||||
|
||||
def convert_to_string(h)
|
||||
h.map do |key_value|
|
||||
key_value.map{|val| CGI::escape(val.to_s)}.join("=")
|
||||
end.join("&")
|
||||
end
|
||||
|
||||
def static
|
||||
param_list = @options[:parameters].map{|k,v| %(<param name="#{k}" value="#{v}"/>) }.join("\n")
|
||||
param_list += %(\n<param name="flashvars" value="#{convert_to_string(@options[:flashvars])}"/>) unless @options[:flashvars].empty?
|
||||
html_options = @options[:html_options].map{|k,v| %(#{k}="#{v}")}.join(" ")
|
||||
r = @view.javascript_tag(
|
||||
%(swfobject.registerObject("#{@options[:id]}_container", "#{@options[:flash_version]}", #{@options[:auto_install].to_json});)
|
||||
)
|
||||
r.send CONCAT, <<-"EOS".strip
|
||||
<div id="#{@options[:div_id]}"><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="#{@options[:width]}" height="#{@options[:height]}" id="#{@options[:id]}_container" #{html_options}>
|
||||
<param name="movie" value="#{@source}" />
|
||||
#{param_list}
|
||||
<!--[if !IE]>-->
|
||||
<object type="application/x-shockwave-flash" data="#{@source}" width="#{@options[:width]}" height="#{@options[:height]}" id="#{@options[:id]}">
|
||||
#{param_list}
|
||||
<!--<![endif]-->
|
||||
#{@options[:alt]}
|
||||
<!--[if !IE]>-->
|
||||
</object>
|
||||
<!--<![endif]-->
|
||||
</object></div>
|
||||
EOS
|
||||
r << @view.javascript_tag(extend_js) if @options[:javascript_class]
|
||||
r.send CONCAT, library_check
|
||||
r
|
||||
end
|
||||
|
||||
def dynamic
|
||||
@options[:html_options] = @options[:html_options].merge(:id => @options[:id])
|
||||
@options[:parameters] = @options[:parameters].dup # don't modify the original parameters
|
||||
args = (([@source] + @options.values_at(:div_id,:width,:height,:flash_version)).map(&:to_s) +
|
||||
@options.values_at(:auto_install,:flashvars,:parameters,:html_options)
|
||||
).map(&:to_json).join(",")
|
||||
preambule = @options[:switch_off_auto_hide_show] ? "swfobject.switchOffAutoHideShow();" : ""
|
||||
r = @view.javascript_tag(preambule + "swfobject.embedSWF(#{args})")
|
||||
r.send CONCAT, <<-"EOS".strip
|
||||
<div id="#{@options[:div_id]}">
|
||||
#{@options[:alt]}
|
||||
</div>
|
||||
EOS
|
||||
r << @view.javascript_tag("swfobject.addDomLoadEvent(function(){#{extend_js}})") if @options[:javascript_class]
|
||||
r.send CONCAT, library_check
|
||||
r
|
||||
end
|
||||
|
||||
def extend_js
|
||||
arglist = case
|
||||
when @options[:initialize].instance_of?(Array)
|
||||
@options[:initialize].map(&:to_json).join(",")
|
||||
when @options.has_key?(:initialize)
|
||||
@options[:initialize].to_json
|
||||
else
|
||||
""
|
||||
end
|
||||
"Object.extend($('#{@options[:id]}'), #{@options[:javascript_class]}.prototype).initialize(#{arglist})"
|
||||
end
|
||||
|
||||
def library_check
|
||||
return "" unless 'development' == ENV['RAILS_ENV']
|
||||
@view.javascript_tag(<<-"EOS")
|
||||
if (typeof swfobject == 'undefined') {
|
||||
document.getElementById('#{@options[:div_id]}').innerHTML = '<strong>Warning:</strong> SWFObject.js was not loaded properly. Make sure you <tt><%= javascript_include_tag :defaults %></tt> or <tt><%= javascript_include_tag :swfobject %></tt>';
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end #class Generator
|
||||
end
|
||||
end
|
||||
end
|
||||
42
backup.rails2.3/plugins/swf_fu/test/results.rb
Normal file
42
backup.rails2.3/plugins/swf_fu/test/results.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
DYNAMIC_RESULT = <<'EOS'
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
swfobject.embedSWF("/swfs/mySwf.swf","mySwf_div","456","123","7","/swfs/expressInstall.swf",{"myVar":"value 1 \u003E 2","id":"mySwf"},{"play":true},{"id":"mySwf"})
|
||||
//]]>
|
||||
</script><div id="mySwf_div">
|
||||
<a href="http://www.adobe.com/go/getflashplayer">
|
||||
<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
|
||||
</a>
|
||||
</div><script type="text/javascript">
|
||||
//<![CDATA[
|
||||
swfobject.addDomLoadEvent(function(){Object.extend($('mySwf'), SomeClass.prototype).initialize({"be":"good"})})
|
||||
//]]>
|
||||
</script>
|
||||
EOS
|
||||
|
||||
STATIC_RESULT = <<'EOS'
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
swfobject.registerObject("mySwf_container", "7", "/swfs/expressInstall.swf");
|
||||
//]]>
|
||||
</script><div id="mySwf_div"><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="456" height="123" id="mySwf_container" class="lots">
|
||||
<param name="movie" value="/swfs/mySwf.swf" />
|
||||
<param name="play" value="true"/>
|
||||
<param name="flashvars" value="myVar=value+1+%3E+2&id=mySwf"/>
|
||||
<!--[if !IE]>-->
|
||||
<object type="application/x-shockwave-flash" data="/swfs/mySwf.swf" width="456" height="123" id="mySwf">
|
||||
<param name="play" value="true"/>
|
||||
<param name="flashvars" value="myVar=value+1+%3E+2&id=mySwf"/>
|
||||
<!--<![endif]-->
|
||||
<a href="http://www.adobe.com/go/getflashplayer">
|
||||
<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
|
||||
</a>
|
||||
<!--[if !IE]>-->
|
||||
</object>
|
||||
<!--<![endif]-->
|
||||
</object></div><script type="text/javascript">
|
||||
//<![CDATA[
|
||||
Object.extend($('mySwf'), SomeClass.prototype).initialize({"be":"good"})
|
||||
//]]>
|
||||
</script>
|
||||
EOS
|
||||
159
backup.rails2.3/plugins/swf_fu/test/swf_fu_test.rb
Normal file
159
backup.rails2.3/plugins/swf_fu/test/swf_fu_test.rb
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
require File.expand_path(File.dirname(__FILE__)+'/test_helper')
|
||||
require File.expand_path(File.dirname(__FILE__)+'/results')
|
||||
|
||||
class SwfFuTest < ActionView::TestCase
|
||||
def assert_same_stripped(expect, test)
|
||||
expect, test = [expect, test].map{|s| s.split("\n").map(&:strip)}
|
||||
same = expect & test
|
||||
delta_expect, delta_test = [expect, test].map{|a| a-same}
|
||||
STDOUT << "\n\n---- Actual result: ----\n" << test.join("\n") << "\n---------\n" unless delta_expect == delta_test
|
||||
assert_equal delta_expect, delta_test
|
||||
end
|
||||
|
||||
context "swf_path" do
|
||||
context "with no special asset host" do
|
||||
should "deduce the extension" do
|
||||
assert_equal swf_path("example.swf"), swf_path("example")
|
||||
assert_starts_with "/swfs/example.swf", swf_path("example.swf")
|
||||
end
|
||||
|
||||
should "accept relative paths" do
|
||||
assert_starts_with "/swfs/whatever/example.swf", swf_path("whatever/example.swf")
|
||||
end
|
||||
|
||||
should "leave full paths alone" do
|
||||
["/full/path.swf", "http://www.example.com/whatever.swf"].each do |p|
|
||||
assert_starts_with p, swf_path(p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with custom asset host" do
|
||||
HOST = "http://assets.example.com"
|
||||
setup do
|
||||
ActionController::Base.asset_host = HOST
|
||||
end
|
||||
|
||||
teardown do
|
||||
ActionController::Base.asset_host = nil
|
||||
end
|
||||
|
||||
should "take it into account" do
|
||||
assert_equal "#{HOST}/swfs/whatever.swf", swf_path("whatever")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "swf_tag" do
|
||||
COMPLEX_OPTIONS = { :width => "456", :height => 123,
|
||||
:flashvars => {:myVar => "value 1 > 2"}.freeze,
|
||||
:javascript_class => "SomeClass",
|
||||
:initialize => {:be => "good"}.freeze,
|
||||
:parameters => {:play => true}.freeze
|
||||
}.freeze
|
||||
|
||||
should "understand size" do
|
||||
assert_equal swf_tag("hello", :size => "123x456"),
|
||||
swf_tag("hello", :width => 123, :height => "456")
|
||||
end
|
||||
|
||||
should "only accept valid modes" do
|
||||
assert_raise(ArgumentError) { swf_tag("xyz", :mode => :xyz) }
|
||||
end
|
||||
|
||||
context "with custom defaults" do
|
||||
setup do
|
||||
test = {:flashvars=> {:xyz => "abc", :hello => "world"}.freeze, :mode => :static, :size => "400x300"}.freeze
|
||||
@expect = swf_tag("test", test)
|
||||
@expect_with_hello = swf_tag("test", :flashvars => {:xyz => "abc", :hello => "my friend"}, :mode => :static, :size => "400x300")
|
||||
ActionView::Base.swf_default_options = test
|
||||
end
|
||||
|
||||
should "respect them" do
|
||||
assert_equal @expect, swf_tag("test")
|
||||
end
|
||||
|
||||
should "merge suboptions" do
|
||||
assert_equal @expect_with_hello, swf_tag("test", :flashvars => {:hello => "my friend"}.freeze)
|
||||
end
|
||||
|
||||
teardown { ActionView::Base.swf_default_options = {} }
|
||||
end
|
||||
|
||||
context "with proc options" do
|
||||
should "call them" do
|
||||
expect = swf_tag("test", :id => "generated_id_for_test")
|
||||
assert_equal expect, swf_tag("test", :id => Proc.new{|arg| "generated_id_for_#{arg}"})
|
||||
end
|
||||
|
||||
should "call global default's everytime" do
|
||||
expect1 = swf_tag("test", :id => "call_number_1")
|
||||
expect2 = swf_tag("test", :id => "call_number_2")
|
||||
cnt = 0
|
||||
ActionView::Base.swf_default_options = { :id => Proc.new{ "call_number_#{cnt+=1}" }}
|
||||
assert_equal expect1, swf_tag("test")
|
||||
assert_equal expect2, swf_tag("test")
|
||||
end
|
||||
end
|
||||
|
||||
context "with static mode" do
|
||||
setup { ActionView::Base.swf_default_options = {:mode => :static} }
|
||||
|
||||
should "deal with string flashvars" do
|
||||
assert_equal swf_tag("hello", :flashvars => "xyz=abc", :mode => :static),
|
||||
swf_tag("hello", :flashvars => {:xyz => "abc"}, :mode => :static)
|
||||
end
|
||||
|
||||
should "produce the expected code" do
|
||||
assert_same_stripped STATIC_RESULT, swf_tag("mySwf", COMPLEX_OPTIONS.merge(:html_options => {:class => "lots"}.freeze).freeze)
|
||||
end
|
||||
|
||||
teardown { ActionView::Base.swf_default_options = {} }
|
||||
end
|
||||
|
||||
context "with dynamic mode" do
|
||||
should "produce the expected code" do
|
||||
assert_same_stripped DYNAMIC_RESULT, swf_tag("mySwf", COMPLEX_OPTIONS)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
should "enforce HTML id validity" do
|
||||
div_result = '<div id="swf_123-456_ok___X_div">'
|
||||
assert_match /#{div_result}/, swf_tag("123-456_ok$!+X")
|
||||
obj_result = '"id":"swf_123-456_ok___X"'
|
||||
assert_match /#{obj_result}/, swf_tag("123-456_ok$!+X")
|
||||
end
|
||||
|
||||
should "treat initialize arrays as list of parameters" do
|
||||
assert_match 'initialize("hello","world")', swf_tag("mySwf", :initialize => ["hello", "world"], :javascript_class => "SomeClass")
|
||||
end
|
||||
|
||||
if ActiveSupport.const_defined?(:SafeBuffer)
|
||||
should "be html safe" do
|
||||
assert swf_tag("test").html_safe?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "flashobject_tag" do
|
||||
should "be the same as swf_tag with different defaults" do
|
||||
assert_same_stripped swf_tag("mySwf",
|
||||
:auto_install => nil,
|
||||
:parameters => {:scale => "noscale", :bgcolor => "#ffffff"},
|
||||
:flashvars => {:lzproxied => false},
|
||||
:id => "myFlash"
|
||||
), flashobject_tag("mySwf", :flash_id => "myFlash")
|
||||
end
|
||||
|
||||
should "be the same with custom settings" do
|
||||
assert_same_stripped swf_tag("mySwf",
|
||||
:auto_install => nil,
|
||||
:parameters => {:scale => "noborder", :bgcolor => "#ffffff"},
|
||||
:flashvars => {:answer_is => 42},
|
||||
:id => "myFlash"
|
||||
), flashobject_tag("mySwf", :flash_id => "myFlash", :parameters => {:scale => "noborder"}, :variables => {:answer_is => 42})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
20
backup.rails2.3/plugins/swf_fu/test/test_helper.rb
Normal file
20
backup.rails2.3/plugins/swf_fu/test/test_helper.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
gem 'activesupport', '~> 2.3'
|
||||
require 'active_support'
|
||||
gem 'activerecord', '~> 2.3'
|
||||
require 'active_record'
|
||||
gem 'actionpack', '~> 2.3'
|
||||
require 'active_support'
|
||||
require 'action_view'
|
||||
require 'action_controller'
|
||||
|
||||
#require File.dirname(__FILE__)+'/../../../../config/environment.rb'
|
||||
require 'action_view/test_case'
|
||||
require "action_controller/test_process"
|
||||
require 'shoulda'
|
||||
require File.dirname(__FILE__) + '/../init'
|
||||
|
||||
def assert_starts_with(start, what)
|
||||
assert what.starts_with?(start), "#{what} does not start with #{start}"
|
||||
end
|
||||
6
backup.rails2.3/plugins/swf_fu/uninstall.rb
Normal file
6
backup.rails2.3/plugins/swf_fu/uninstall.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
require "fileutils"
|
||||
|
||||
dest = File.dirname(__FILE__) + "/../../../public"
|
||||
FileUtils.rm "#{dest}/javascripts/swfobject.js" rescue puts "Warning: swfobject.js could not be deleted"
|
||||
FileUtils.rm "#{dest}/swfs/expressInstall.swf" rescue puts "Warning: expressInstall.swf could not be deleted"
|
||||
Dir.rmdir "#{dest}/swfs/" rescue "don't worry if directory is not empty"
|
||||
20
backup.rails2.3/plugins/translate/MIT-LICENSE
Normal file
20
backup.rails2.3/plugins/translate/MIT-LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2009 [name of plugin creator]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
63
backup.rails2.3/plugins/translate/README
Normal file
63
backup.rails2.3/plugins/translate/README
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
Translate
|
||||
=========
|
||||
|
||||
This plugin provides a web interface for translating Rails I18n texts (requires Rails 2.2 or higher) from one locale to another. The plugin has been tested only with the simple I18n backend that ships with Rails. I18n texts are read from and written to YAML files under config/locales.
|
||||
|
||||
To translate to a new locale you need to add a YAML file for that locale that contains the locale as the top key and at least one translation.
|
||||
|
||||
Please note that there are certain I18n keys that map to Array objects rather than strings and those are currently not dealt with by the translation UI. This means that Rails built in keys such as date.day_names need to be translated manually directly in the YAML file.
|
||||
|
||||
To get the translation UI to write the YAML files in UTF8 you need to install the ya2yaml gem.
|
||||
|
||||
The translation UI finds all I18n keys by extracting them from I18n lookups in your application source code. In addition it adds all :en and default locale keys from the I18n backend.
|
||||
|
||||
- Updated: Each string in the UI now has an "Auto Translate" link which will send the original text to Google Translate and will input the returned translation into the form field for further clean up and review prior to saving.
|
||||
|
||||
|
||||
Rake Tasks
|
||||
=========
|
||||
|
||||
In addition to the web UI this plugin adds the following rake tasks:
|
||||
|
||||
translate:untranslated
|
||||
translate:missing
|
||||
translate:remove_obsolete_keys
|
||||
translate:merge_keys
|
||||
translate:google
|
||||
translate:changed
|
||||
|
||||
The missing task shows you any I18n keys in your code that do not have translations in the YAML file for your default locale, i.e. config/locales/sv.yml.
|
||||
|
||||
The merge_keys task is supposed to be used in conjunction with Sven Fuch's Rails I18n TextMate bundle (http://github.com/svenfuchs/rails-i18n/tree/master). Texts and keys extracted with the TextMate bundle end up in the temporary file log/translations.yml. When you run the merge_keys rake task the keys are moved over to the corresponding I18n locale file, i.e. config/locales/sv.yml. The merge_keys task also checks for overwrites of existing keys by warning you that one of your extracted keys already exists with a different translation.
|
||||
|
||||
The google task is used for auto translating from one locale to another using Google Translate.
|
||||
|
||||
The changed rake task can show you between one YAML file to another which keys have had their texts changed.
|
||||
|
||||
Installation
|
||||
=========
|
||||
Obtain the source with:
|
||||
|
||||
./script/plugin install git://github.com/newsdesk/translate.git
|
||||
|
||||
To mount the plugin, add the following to your config/routes.rb file:
|
||||
|
||||
Translate::Routes.translation_ui(map) if RAILS_ENV != "production"
|
||||
|
||||
Now visit /translate in your web browser to start translating.
|
||||
|
||||
Dependencies
|
||||
=========
|
||||
|
||||
- Rails 2.2 or higher
|
||||
- The ya2yaml gem if you want your YAML files written in UTF8 encoding.
|
||||
|
||||
Authors
|
||||
=========
|
||||
|
||||
- Peter Marklund (programming)
|
||||
- Joakim Westerlund (web design)
|
||||
|
||||
Many thanks to http://newsdesk.se for sponsoring the development of this plugin.
|
||||
|
||||
Copyright (c) 2009 Peter Marklund, released under the MIT license
|
||||
11
backup.rails2.3/plugins/translate/Rakefile
Normal file
11
backup.rails2.3/plugins/translate/Rakefile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
require 'rake'
|
||||
require 'spec/rake/spectask'
|
||||
|
||||
desc 'Default: run specs.'
|
||||
task :default => :spec
|
||||
|
||||
desc 'Run the specs'
|
||||
Spec::Rake::SpecTask.new(:spec) do |t|
|
||||
t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
|
||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
||||
end
|
||||
8
backup.rails2.3/plugins/translate/init.rb
Normal file
8
backup.rails2.3/plugins/translate/init.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
require 'translate'
|
||||
|
||||
# TODO: Use new method available_locales once Rails is upgraded, see:
|
||||
# http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4
|
||||
def I18n.valid_locales
|
||||
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
||||
backend.send(:translations).keys.reject { |locale| locale == :root }
|
||||
end
|
||||
8
backup.rails2.3/plugins/translate/lib/translate.rb
Normal file
8
backup.rails2.3/plugins/translate/lib/translate.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module Translate
|
||||
end
|
||||
|
||||
require File.join(File.dirname(__FILE__), "translate_controller")
|
||||
require File.join(File.dirname(__FILE__), "translate_helper")
|
||||
Dir[File.join(File.dirname(__FILE__), "translate", "*.rb")].each do |file|
|
||||
require file
|
||||
end
|
||||
35
backup.rails2.3/plugins/translate/lib/translate/file.rb
Normal file
35
backup.rails2.3/plugins/translate/lib/translate/file.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
require 'fileutils'
|
||||
|
||||
class Translate::File
|
||||
attr_accessor :path
|
||||
|
||||
def initialize(path)
|
||||
self.path = path
|
||||
end
|
||||
|
||||
def write(keys)
|
||||
FileUtils.mkdir_p File.dirname(path)
|
||||
File.open(path, "w") do |file|
|
||||
file.puts keys_to_yaml(Translate::File.deep_stringify_keys(keys))
|
||||
end
|
||||
end
|
||||
|
||||
def read
|
||||
File.exists?(path) ? YAML::load(IO.read(path)) : {}
|
||||
end
|
||||
|
||||
# Stringifying keys for prettier YAML
|
||||
def self.deep_stringify_keys(hash)
|
||||
hash.inject({}) { |result, (key, value)|
|
||||
value = deep_stringify_keys(value) if value.is_a? Hash
|
||||
result[(key.to_s rescue key) || key] = value
|
||||
result
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
def keys_to_yaml(keys)
|
||||
# Using ya2yaml, if available, for UTF8 support
|
||||
keys.respond_to?(:ya2yaml) ? keys.ya2yaml(:escape_as_utf8 => true) : keys.to_yaml
|
||||
end
|
||||
end
|
||||
152
backup.rails2.3/plugins/translate/lib/translate/keys.rb
Normal file
152
backup.rails2.3/plugins/translate/lib/translate/keys.rb
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
require 'pathname'
|
||||
|
||||
class Translate::Keys
|
||||
# Allows keys extracted from lookups in files to be cached
|
||||
def self.files
|
||||
@@files ||= Translate::Keys.new.files
|
||||
end
|
||||
|
||||
# Allows flushing of the files cache
|
||||
def self.files=(files)
|
||||
@@files = files
|
||||
end
|
||||
|
||||
def files
|
||||
@files ||= extract_files
|
||||
end
|
||||
alias_method :to_hash, :files
|
||||
|
||||
def keys
|
||||
files.keys
|
||||
end
|
||||
alias_method :to_a, :keys
|
||||
|
||||
def i18n_keys(locale)
|
||||
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
||||
Translate::Keys.to_shallow_hash(I18n.backend.send(:translations)[locale.to_sym]).keys.sort
|
||||
end
|
||||
|
||||
def untranslated_keys
|
||||
Translate::Keys.translated_locales.inject({}) do |missing, locale|
|
||||
missing[locale] = i18n_keys(I18n.default_locale).map do |key|
|
||||
I18n.backend.send(:lookup, locale, key).nil? ? key : nil
|
||||
end.compact
|
||||
missing
|
||||
end
|
||||
end
|
||||
|
||||
def missing_keys
|
||||
locale = I18n.default_locale; yaml_keys = {}
|
||||
yaml_keys = Translate::Storage.file_paths(locale).inject({}) do |keys, path|
|
||||
keys = keys.deep_merge(Translate::File.new(path).read[locale.to_s])
|
||||
end
|
||||
files.reject { |key, file| self.class.contains_key?(yaml_keys, key) }
|
||||
end
|
||||
|
||||
def self.translated_locales
|
||||
I18n.available_locales.reject { |locale| [:root, I18n.default_locale.to_sym].include?(locale) }
|
||||
end
|
||||
|
||||
# Checks if a nested hash contains the keys in dot separated I18n key.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# hash = {
|
||||
# :foo => {
|
||||
# :bar => {
|
||||
# :baz => 1
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# contains_key?("foo", key) # => true
|
||||
# contains_key?("foo.bar", key) # => true
|
||||
# contains_key?("foo.bar.baz", key) # => true
|
||||
# contains_key?("foo.bar.baz.bla", key) # => false
|
||||
#
|
||||
def self.contains_key?(hash, key)
|
||||
keys = key.to_s.split(".")
|
||||
return false if keys.empty?
|
||||
!keys.inject(HashWithIndifferentAccess.new(hash)) do |memo, key|
|
||||
memo.is_a?(Hash) ? memo.try(:[], key) : nil
|
||||
end.nil?
|
||||
end
|
||||
|
||||
# Convert something like:
|
||||
#
|
||||
# {
|
||||
# :pressrelease => {
|
||||
# :label => {
|
||||
# :one => "Pressmeddelande"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# to:
|
||||
#
|
||||
# {'pressrelease.label.one' => "Pressmeddelande"}
|
||||
#
|
||||
def self.to_shallow_hash(hash)
|
||||
hash.inject({}) do |shallow_hash, (key, value)|
|
||||
if value.is_a?(Hash)
|
||||
to_shallow_hash(value).each do |sub_key, sub_value|
|
||||
shallow_hash[[key, sub_key].join(".")] = sub_value
|
||||
end
|
||||
else
|
||||
shallow_hash[key.to_s] = value
|
||||
end
|
||||
shallow_hash
|
||||
end
|
||||
end
|
||||
|
||||
# Convert something like:
|
||||
#
|
||||
# {'pressrelease.label.one' => "Pressmeddelande"}
|
||||
#
|
||||
# to:
|
||||
#
|
||||
# {
|
||||
# :pressrelease => {
|
||||
# :label => {
|
||||
# :one => "Pressmeddelande"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
def self.to_deep_hash(hash)
|
||||
hash.inject({}) do |deep_hash, (key, value)|
|
||||
keys = key.to_s.split('.').reverse
|
||||
leaf_key = keys.shift
|
||||
key_hash = keys.inject({leaf_key.to_sym => value}) { |hash, key| {key.to_sym => hash} }
|
||||
deep_merge!(deep_hash, key_hash)
|
||||
deep_hash
|
||||
end
|
||||
end
|
||||
|
||||
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
||||
def self.deep_merge!(hash1, hash2)
|
||||
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
||||
hash1.merge!(hash2, &merger)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_files
|
||||
files_to_scan.inject(HashWithIndifferentAccess.new) do |files, file|
|
||||
IO.read(file).scan(i18n_lookup_pattern).flatten.map(&:to_sym).each do |key|
|
||||
files[key] ||= []
|
||||
path = Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(Rails.root)).to_s
|
||||
files[key] << path if !files[key].include?(path)
|
||||
end
|
||||
files
|
||||
end
|
||||
end
|
||||
|
||||
def i18n_lookup_pattern
|
||||
/\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?'([a-z0-9_]+.[a-z0-9_.]+)'\)?/
|
||||
end
|
||||
|
||||
def files_to_scan
|
||||
Dir.glob(File.join(Translate::Storage.root_dir, "{app,config,lib}", "**","*.{rb,erb,rhtml}")) +
|
||||
Dir.glob(File.join(Translate::Storage.root_dir, "public", "javascripts", "**","*.js"))
|
||||
end
|
||||
end
|
||||
35
backup.rails2.3/plugins/translate/lib/translate/log.rb
Normal file
35
backup.rails2.3/plugins/translate/lib/translate/log.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
class Translate::Log
|
||||
attr_accessor :from_locale, :to_locale, :keys
|
||||
|
||||
def initialize(from_locale, to_locale, keys)
|
||||
self.from_locale = from_locale
|
||||
self.to_locale = to_locale
|
||||
self.keys = keys
|
||||
end
|
||||
|
||||
def write_to_file
|
||||
current_texts = File.exists?(file_path) ? file.read : {}
|
||||
current_texts.merge!(from_texts)
|
||||
file.write(current_texts)
|
||||
end
|
||||
|
||||
def read
|
||||
file.read
|
||||
end
|
||||
|
||||
private
|
||||
def file
|
||||
@file ||= Translate::File.new(file_path)
|
||||
end
|
||||
|
||||
def from_texts
|
||||
Translate::File.deep_stringify_keys(Translate::Keys.to_deep_hash(keys.inject({}) do |hash, key|
|
||||
hash[key] = I18n.backend.send(:lookup, from_locale, key)
|
||||
hash
|
||||
end))
|
||||
end
|
||||
|
||||
def file_path
|
||||
File.join(Rails.root, "config", "locales", "log", "from_#{from_locale}_to_#{to_locale}.yml")
|
||||
end
|
||||
end
|
||||
11
backup.rails2.3/plugins/translate/lib/translate/routes.rb
Normal file
11
backup.rails2.3/plugins/translate/lib/translate/routes.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module Translate
|
||||
class Routes
|
||||
def self.translation_ui(map)
|
||||
map.with_options(:controller => 'translate') do |t|
|
||||
t.translate_list 'translate'
|
||||
t.translate 'translate/translate', :action => 'translate'
|
||||
t.translate_reload 'translate/reload', :action => 'reload'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
28
backup.rails2.3/plugins/translate/lib/translate/storage.rb
Normal file
28
backup.rails2.3/plugins/translate/lib/translate/storage.rb
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
class Translate::Storage
|
||||
attr_accessor :locale
|
||||
|
||||
def initialize(locale)
|
||||
self.locale = locale.to_sym
|
||||
end
|
||||
|
||||
def write_to_file
|
||||
Translate::File.new(file_path).write(keys)
|
||||
end
|
||||
|
||||
def self.file_paths(locale)
|
||||
Dir.glob(File.join(root_dir, "config", "locales", "**","#{locale}.yml"))
|
||||
end
|
||||
|
||||
def self.root_dir
|
||||
Rails.root
|
||||
end
|
||||
|
||||
private
|
||||
def keys
|
||||
{locale => I18n.backend.send(:translations)[locale]}
|
||||
end
|
||||
|
||||
def file_path
|
||||
File.join(Translate::Storage.root_dir, "config", "locales", "#{locale}.yml")
|
||||
end
|
||||
end
|
||||
165
backup.rails2.3/plugins/translate/lib/translate_controller.rb
Normal file
165
backup.rails2.3/plugins/translate/lib/translate_controller.rb
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
class TranslateController < ActionController::Base
|
||||
# It seems users with active_record_store may get a "no :secret given" error if we don't disable csrf protection,
|
||||
skip_before_filter :verify_authenticity_token
|
||||
|
||||
prepend_view_path(File.join(File.dirname(__FILE__), "..", "views"))
|
||||
layout 'translate'
|
||||
|
||||
before_filter :init_translations
|
||||
before_filter :set_locale
|
||||
|
||||
def index
|
||||
initialize_keys
|
||||
filter_by_key_pattern
|
||||
filter_by_text_pattern
|
||||
filter_by_translated_or_changed
|
||||
sort_keys
|
||||
paginate_keys
|
||||
@total_entries = @keys.size
|
||||
end
|
||||
|
||||
def translate
|
||||
I18n.backend.store_translations(@to_locale, Translate::Keys.to_deep_hash(params[:key]))
|
||||
Translate::Storage.new(@to_locale).write_to_file
|
||||
Translate::Log.new(@from_locale, @to_locale, params[:key].keys).write_to_file
|
||||
force_init_translations # Force reload from YAML file
|
||||
flash[:notice] = "Translations stored"
|
||||
redirect_to params.slice(:filter, :sort_by, :key_type, :key_pattern, :text_type, :text_pattern).merge({:action => :index})
|
||||
end
|
||||
|
||||
def reload
|
||||
Translate::Keys.files = nil
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
private
|
||||
def initialize_keys
|
||||
@files = Translate::Keys.files
|
||||
@keys = (@files.keys.map(&:to_s) + Translate::Keys.new.i18n_keys(@from_locale)).uniq
|
||||
@keys.reject! do |key|
|
||||
from_text = lookup(@from_locale, key)
|
||||
# When translating from one language to another, make sure there is a text to translate from.
|
||||
# Always exclude non string translation objects as we don't support editing them in the UI.
|
||||
(@from_locale != @to_locale && !from_text.present?) || (from_text.present? && !from_text.is_a?(String))
|
||||
end
|
||||
end
|
||||
|
||||
def lookup(locale, key)
|
||||
I18n.backend.send(:lookup, locale, key)
|
||||
end
|
||||
helper_method :lookup
|
||||
|
||||
def filter_by_translated_or_changed
|
||||
params[:filter] ||= 'all'
|
||||
return if params[:filter] == 'all'
|
||||
@keys.reject! do |key|
|
||||
case params[:filter]
|
||||
when 'untranslated'
|
||||
lookup(@to_locale, key).present?
|
||||
when 'translated'
|
||||
lookup(@to_locale, key).blank?
|
||||
when 'changed'
|
||||
old_from_text(key).blank? || lookup(@from_locale, key) == old_from_text(key)
|
||||
else
|
||||
raise "Unknown filter '#{params[:filter]}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filter_by_key_pattern
|
||||
return if params[:key_pattern].blank?
|
||||
@keys.reject! do |key|
|
||||
case params[:key_type]
|
||||
when "starts_with"
|
||||
!key.starts_with?(params[:key_pattern])
|
||||
when "contains"
|
||||
key.index(params[:key_pattern]).nil?
|
||||
else
|
||||
raise "Unknown key_type '#{params[:key_type]}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filter_by_text_pattern
|
||||
return if params[:text_pattern].blank?
|
||||
@keys.reject! do |key|
|
||||
case params[:text_type]
|
||||
when 'contains'
|
||||
!lookup(@from_locale, key).present? || !lookup(@from_locale, key).to_s.downcase.index(params[:text_pattern].downcase)
|
||||
when 'equals'
|
||||
!lookup(@from_locale, key).present? || lookup(@from_locale, key).to_s.downcase != params[:text_pattern].downcase
|
||||
else
|
||||
raise "Unknown text_type '#{params[:text_type]}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sort_keys
|
||||
params[:sort_by] ||= "key"
|
||||
case params[:sort_by]
|
||||
when "key"
|
||||
@keys.sort!
|
||||
when "text"
|
||||
@keys.sort! do |key1, key2|
|
||||
if lookup(@from_locale, key1).present? && lookup(@from_locale, key2).present?
|
||||
lookup(@from_locale, key1).to_s.downcase <=> lookup(@from_locale, key2).to_s.downcase
|
||||
elsif lookup(@from_locale, key1).present?
|
||||
-1
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
else
|
||||
raise "Unknown sort_by '#{params[:sort_by]}'"
|
||||
end
|
||||
end
|
||||
|
||||
def paginate_keys
|
||||
params[:page] ||= 1
|
||||
@paginated_keys = @keys[offset, per_page]
|
||||
end
|
||||
|
||||
def offset
|
||||
(params[:page].to_i - 1) * per_page
|
||||
end
|
||||
|
||||
def per_page
|
||||
50
|
||||
end
|
||||
helper_method :per_page
|
||||
|
||||
def init_translations
|
||||
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
||||
end
|
||||
|
||||
def force_init_translations
|
||||
I18n.backend.send(:init_translations)
|
||||
end
|
||||
|
||||
def default_locale
|
||||
I18n.default_locale
|
||||
end
|
||||
|
||||
def set_locale
|
||||
session[:from_locale] ||= default_locale
|
||||
session[:to_locale] ||= :en
|
||||
session[:from_locale] = params[:from_locale] if params[:from_locale].present?
|
||||
session[:to_locale] = params[:to_locale] if params[:to_locale].present?
|
||||
@from_locale = session[:from_locale].to_sym
|
||||
@to_locale = session[:to_locale].to_sym
|
||||
end
|
||||
|
||||
def old_from_text(key)
|
||||
return @old_from_text[key] if @old_from_text && @old_from_text[key]
|
||||
@old_from_text = {}
|
||||
text = key.split(".").inject(log_hash) do |hash, k|
|
||||
hash ? hash[k] : nil
|
||||
end
|
||||
@old_from_text[key] = text
|
||||
end
|
||||
helper_method :old_from_text
|
||||
|
||||
def log_hash
|
||||
@log_hash ||= Translate::Log.new(@from_locale, @to_locale, {}).read
|
||||
end
|
||||
end
|
||||
45
backup.rails2.3/plugins/translate/lib/translate_helper.rb
Normal file
45
backup.rails2.3/plugins/translate/lib/translate_helper.rb
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
module TranslateHelper
|
||||
def simple_filter(labels, param_name = 'filter', selected_value = nil)
|
||||
selected_value ||= params[param_name]
|
||||
filter = []
|
||||
labels.each do |item|
|
||||
if item.is_a?(Array)
|
||||
type, label = item
|
||||
else
|
||||
type = label = item
|
||||
end
|
||||
if type.to_s == selected_value.to_s
|
||||
filter << "<i>#{label}</i>"
|
||||
else
|
||||
link_params = params.merge({param_name.to_s => type})
|
||||
link_params.merge!({"page" => nil}) if param_name.to_s != "page"
|
||||
filter << link_to(label, link_params)
|
||||
end
|
||||
end
|
||||
filter.join(" | ")
|
||||
end
|
||||
|
||||
def n_lines(text, line_size)
|
||||
n_lines = 1
|
||||
if text.present?
|
||||
n_lines = text.split("\n").size
|
||||
if n_lines == 1 && text.length > line_size
|
||||
n_lines = text.length / line_size + 1
|
||||
end
|
||||
end
|
||||
n_lines
|
||||
end
|
||||
|
||||
def translate_javascript_includes
|
||||
sources = []
|
||||
if File.exists?(File.join(Rails.root, "public", "javascripts", "prototype.js"))
|
||||
sources << "/javascripts/prototype.js"
|
||||
else
|
||||
sources << "http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js"
|
||||
end
|
||||
sources << "http://www.google.com/jsapi"
|
||||
sources.map do |src|
|
||||
%Q{<script src="#{src}" type="text/javascript"></script>}
|
||||
end.join("\n")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe TranslateController do
|
||||
describe "index" do
|
||||
before(:each) do
|
||||
controller.stub!(:per_page).and_return(1)
|
||||
I18n.backend.stub!(:translations).and_return(i18n_translations)
|
||||
I18n.backend.instance_eval { @initialized = true }
|
||||
keys = mock(:keys)
|
||||
keys.stub!(:i18n_keys).and_return(['vendor.foobar'])
|
||||
Translate::Keys.should_receive(:new).and_return(keys)
|
||||
Translate::Keys.should_receive(:files).and_return(files)
|
||||
I18n.stub!(:valid_locales).and_return([:en, :sv])
|
||||
I18n.stub!(:default_locale).and_return(:sv)
|
||||
end
|
||||
|
||||
it "shows sorted paginated keys from the translate from locale and extracted keys by default" do
|
||||
get_page :index
|
||||
assigns(:from_locale).should == :sv
|
||||
assigns(:to_locale).should == :en
|
||||
assigns(:files).should == files
|
||||
assigns(:keys).sort.should == ['articles.new.page_title', 'home.page_title', 'vendor.foobar']
|
||||
assigns(:paginated_keys).should == ['articles.new.page_title']
|
||||
end
|
||||
|
||||
it "can be paginated with the page param" do
|
||||
get_page :index, :page => 2
|
||||
assigns(:files).should == files
|
||||
assigns(:paginated_keys).should == ['home.page_title']
|
||||
end
|
||||
|
||||
it "accepts a key_pattern param with key_type=starts_with" do
|
||||
get_page :index, :key_pattern => 'articles', :key_type => 'starts_with'
|
||||
assigns(:files).should == files
|
||||
assigns(:paginated_keys).should == ['articles.new.page_title']
|
||||
assigns(:total_entries).should == 1
|
||||
end
|
||||
|
||||
it "accepts a key_pattern param with key_type=contains" do
|
||||
get_page :index, :key_pattern => 'page_', :key_type => 'contains'
|
||||
assigns(:files).should == files
|
||||
assigns(:total_entries).should == 2
|
||||
assigns(:paginated_keys).should == ['articles.new.page_title']
|
||||
end
|
||||
|
||||
it "accepts a filter=untranslated param" do
|
||||
get_page :index, :filter => 'untranslated'
|
||||
assigns(:total_entries).should == 2
|
||||
assigns(:paginated_keys).should == ['articles.new.page_title']
|
||||
end
|
||||
|
||||
it "accepts a filter=translated param" do
|
||||
get_page :index, :filter => 'translated'
|
||||
assigns(:total_entries).should == 1
|
||||
assigns(:paginated_keys).should == ['vendor.foobar']
|
||||
end
|
||||
|
||||
it "accepts a filter=changed param" do
|
||||
log = mock(:log)
|
||||
old_translations = {:home => {:page_title => "Skapar ny artikel"}}
|
||||
log.should_receive(:read).and_return(Translate::File.deep_stringify_keys(old_translations))
|
||||
Translate::Log.should_receive(:new).with(:sv, :en, {}).and_return(log)
|
||||
get_page :index, :filter => 'changed'
|
||||
assigns(:total_entries).should == 1
|
||||
assigns(:keys).should == ["home.page_title"]
|
||||
end
|
||||
|
||||
def i18n_translations
|
||||
HashWithIndifferentAccess.new({
|
||||
:en => {
|
||||
:vendor => {
|
||||
:foobar => "Foo Baar"
|
||||
}
|
||||
},
|
||||
:sv => {
|
||||
:articles => {
|
||||
:new => {
|
||||
:page_title => "Skapa ny artikel"
|
||||
}
|
||||
},
|
||||
:home => {
|
||||
:page_title => "Välkommen till I18n"
|
||||
},
|
||||
:vendor => {
|
||||
:foobar => "Fobar"
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def files
|
||||
HashWithIndifferentAccess.new({
|
||||
:'home.page_title' => ["app/views/home/index.rhtml"],
|
||||
:'general.back' => ["app/views/articles/new.rhtml", "app/views/categories/new.rhtml"],
|
||||
:'articles.new.page_title' => ["app/views/articles/new.rhtml"]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
describe "translate" do
|
||||
it "should store translations to I18n backend and then write them to a YAML file" do
|
||||
session[:from_locale] = :sv
|
||||
session[:to_locale] = :en
|
||||
translations = {
|
||||
:articles => {
|
||||
:new => {
|
||||
:title => "New Article"
|
||||
}
|
||||
},
|
||||
:category => "Category"
|
||||
}
|
||||
key_param = {'articles.new.title' => "New Article", "category" => "Category"}
|
||||
I18n.backend.should_receive(:store_translations).with(:en, translations)
|
||||
storage = mock(:storage)
|
||||
storage.should_receive(:write_to_file)
|
||||
Translate::Storage.should_receive(:new).with(:en).and_return(storage)
|
||||
log = mock(:log)
|
||||
log.should_receive(:write_to_file)
|
||||
Translate::Log.should_receive(:new).with(:sv, :en, key_param.keys).and_return(log)
|
||||
post :translate, "key" => key_param
|
||||
response.should be_redirect
|
||||
end
|
||||
end
|
||||
|
||||
def get_page(*args)
|
||||
get(*args)
|
||||
response.should be_success
|
||||
end
|
||||
end
|
||||
54
backup.rails2.3/plugins/translate/spec/file_spec.rb
Normal file
54
backup.rails2.3/plugins/translate/spec/file_spec.rb
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
require 'fileutils'
|
||||
require File.dirname(__FILE__) + '/spec_helper'
|
||||
|
||||
describe Translate::File do
|
||||
describe "write" do
|
||||
before(:each) do
|
||||
@file = Translate::File.new(file_path)
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
FileUtils.rm(file_path)
|
||||
end
|
||||
|
||||
it "writes all I18n messages for a locale to YAML file" do
|
||||
@file.write(translations)
|
||||
@file.read.should == Translate::File.deep_stringify_keys(translations)
|
||||
end
|
||||
|
||||
def translations
|
||||
{
|
||||
:en => {
|
||||
:article => {
|
||||
:title => "One Article"
|
||||
},
|
||||
:category => "Category"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "deep_stringify_keys" do
|
||||
it "should convert all keys in a hash to strings" do
|
||||
Translate::File.deep_stringify_keys({
|
||||
:en => {
|
||||
:article => {
|
||||
:title => "One Article"
|
||||
},
|
||||
:category => "Category"
|
||||
}
|
||||
}).should == {
|
||||
"en" => {
|
||||
"article" => {
|
||||
"title" => "One Article"
|
||||
},
|
||||
"category" => "Category"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def file_path
|
||||
File.join(File.dirname(__FILE__), "files", "en.yml")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
class Article < ActiveRecord::Base
|
||||
def validate
|
||||
# t('li')
|
||||
errors.add_to_base([t(:'article.key1') + "#{t('article.key2')}"])
|
||||
I18n.t 'article.key3'
|
||||
I18n.t 'article.key3'
|
||||
I18n.t :'article.key4'
|
||||
I18n.translate :'article.key5'
|
||||
'bla bla t' + "blubba bla" + ' foobar'
|
||||
'bla bla t ' + "blubba bla" + ' foobar'
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue