parent
8058096f7e
commit
796c1a279e
@ -1,107 +0,0 @@
|
||||
--- !ruby/object:Gem::Specification
|
||||
name: dr_nic_magic_models
|
||||
version: !ruby/object:Gem::Version
|
||||
version: 0.9.2
|
||||
platform: ruby
|
||||
authors:
|
||||
- nicwilliams
|
||||
autorequire:
|
||||
bindir: bin
|
||||
cert_chain:
|
||||
date: 2007-04-29 00:00:00 -03:00
|
||||
default_executable:
|
||||
dependencies: []
|
||||
|
||||
description: Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!
|
||||
email: drnicwilliams@gmail.com
|
||||
executables: []
|
||||
|
||||
extensions: []
|
||||
|
||||
extra_rdoc_files: []
|
||||
|
||||
files:
|
||||
- CHANGELOG
|
||||
- History.txt
|
||||
- Manifest.txt
|
||||
- README
|
||||
- Rakefile
|
||||
- install.rb
|
||||
- lib/base.rb
|
||||
- lib/connection_adapters/abstract/schema_statements.rb
|
||||
- lib/connection_adapters/abstract_adapter.rb
|
||||
- lib/connection_adapters/mysql_adapter.rb
|
||||
- lib/connection_adapters/postgresql_adapter.rb
|
||||
- lib/dr_nic_magic_models.rb
|
||||
- lib/dr_nic_magic_models/inflector.rb
|
||||
- lib/dr_nic_magic_models/magic_model.rb
|
||||
- lib/dr_nic_magic_models/schema.rb
|
||||
- lib/dr_nic_magic_models/validations.rb
|
||||
- lib/dr_nic_magic_models/version.rb
|
||||
- lib/module.rb
|
||||
- lib/rails.rb
|
||||
- scripts/txt2html
|
||||
- scripts/txt2js
|
||||
- test.db
|
||||
- test/abstract_unit.rb
|
||||
- test/connections/native_mysql/connection.rb
|
||||
- test/connections/native_postgresql/connection.rb
|
||||
- test/connections/native_sqlite/connection.rb
|
||||
- test/dummy_test.rb
|
||||
- test/env_test.rb
|
||||
- test/fixtures/.DS_Store
|
||||
- test/fixtures/adjectives.yml
|
||||
- test/fixtures/adjectives_fun_users.yml
|
||||
- test/fixtures/db_definitions/mysql.drop.sql
|
||||
- test/fixtures/db_definitions/mysql.sql
|
||||
- test/fixtures/db_definitions/postgresql.sql
|
||||
- test/fixtures/db_definitions/sqlite.sql
|
||||
- test/fixtures/fun_users.yml
|
||||
- test/fixtures/group_memberships.yml
|
||||
- test/fixtures/group_tag.yml
|
||||
- test/fixtures/groups.yml
|
||||
- test/foreign_keys_test.rb
|
||||
- test/fun_user_plus.rb
|
||||
- test/invisible_model_access_test.rb
|
||||
- test/invisible_model_assoc_test.rb
|
||||
- test/invisible_model_classes_test.rb
|
||||
- test/magic_module_test.rb
|
||||
- test/test_existing_model.rb
|
||||
- website/index.html
|
||||
- website/index.txt
|
||||
- website/javascripts/rounded_corners_lite.inc.js
|
||||
- website/stylesheets/screen.css
|
||||
- website/template.js
|
||||
- website/template.rhtml
|
||||
- website/version-raw.js
|
||||
- website/version-raw.txt
|
||||
- website/version.js
|
||||
- website/version.txt
|
||||
has_rdoc: true
|
||||
homepage: http://magicmodels.rubyforge.org
|
||||
post_install_message:
|
||||
rdoc_options: []
|
||||
|
||||
require_paths:
|
||||
- lib
|
||||
required_ruby_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">"
|
||||
- !ruby/object:Gem::Version
|
||||
version: 0.0.0
|
||||
version:
|
||||
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: "0"
|
||||
version:
|
||||
requirements: []
|
||||
|
||||
rubyforge_project: magicmodels
|
||||
rubygems_version: 1.3.1
|
||||
signing_key:
|
||||
specification_version: 1
|
||||
summary: Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!
|
||||
test_files:
|
||||
- test/test_existing_model.rb
|
@ -1,9 +0,0 @@
|
||||
* 0.2.5 * - Initial public release
|
||||
- ActiveRecords can now be auto-created in memory when first referenced
|
||||
from their table name, without an explicit class definition.
|
||||
- ActiveRecords will automatically include validates_presence_of on
|
||||
each field with :null => false
|
||||
- ActiveRecords will automatically generate simple has_many, has_one,
|
||||
belongs_to assocations based upon assumed foreign keys. E.g.
|
||||
foreign key to products table is assumed to be product_id.
|
||||
|
@ -1,28 +0,0 @@
|
||||
*** 0.9.2 / 2007-4-30
|
||||
|
||||
+ 1 major buxfix:
|
||||
+ #generate_validations now works if you haven't already created a connection to the database; previously
|
||||
validations wouldn't get created until you had already established the connection; now it does it for
|
||||
you if its not already established
|
||||
+ Associations can be generated via the assignment methods, e.g. @membership.group= will generate the "belongs_to :group" association now. This allows the website tutorial to work correctly! Yay. That is, you can now do: Membership.create(:person => person, :group => group)
|
||||
+ has_many's should work now
|
||||
|
||||
*** 0.9.1 / 2007-4-11
|
||||
|
||||
+ 1 minor enhancement:
|
||||
+ ActiveRecord::Base includes all the magic model functionality via the MagicModel module
|
||||
+ Existing ARs can get magic validation via #generate_validations call
|
||||
+ Website tutorial works :D
|
||||
|
||||
*** 0.9.0 / 2007-4-9
|
||||
|
||||
+ 1 major enhancement:
|
||||
+ Support for dynamic loading of classes again
|
||||
+ 2 new DB supported:
|
||||
+ Tests run on sqlite (no fk support)
|
||||
+ Tests run on postgresql (fk support)
|
||||
+ Including FK bug fix
|
||||
+ Many fixes that I've lost track of
|
||||
+ History.txt to keep track of changes like these
|
||||
+ Using Hoe for Rakefile
|
||||
+ Use modules to specify common table prefixes
|
@ -1,56 +0,0 @@
|
||||
CHANGELOG
|
||||
History.txt
|
||||
Manifest.txt
|
||||
README
|
||||
Rakefile
|
||||
install.rb
|
||||
lib/base.rb
|
||||
lib/connection_adapters/abstract/schema_statements.rb
|
||||
lib/connection_adapters/abstract_adapter.rb
|
||||
lib/connection_adapters/mysql_adapter.rb
|
||||
lib/connection_adapters/postgresql_adapter.rb
|
||||
lib/dr_nic_magic_models.rb
|
||||
lib/dr_nic_magic_models/inflector.rb
|
||||
lib/dr_nic_magic_models/magic_model.rb
|
||||
lib/dr_nic_magic_models/schema.rb
|
||||
lib/dr_nic_magic_models/validations.rb
|
||||
lib/dr_nic_magic_models/version.rb
|
||||
lib/module.rb
|
||||
lib/rails.rb
|
||||
scripts/txt2html
|
||||
scripts/txt2js
|
||||
test.db
|
||||
test/abstract_unit.rb
|
||||
test/connections/native_mysql/connection.rb
|
||||
test/connections/native_postgresql/connection.rb
|
||||
test/connections/native_sqlite/connection.rb
|
||||
test/dummy_test.rb
|
||||
test/env_test.rb
|
||||
test/fixtures/.DS_Store
|
||||
test/fixtures/adjectives.yml
|
||||
test/fixtures/adjectives_fun_users.yml
|
||||
test/fixtures/db_definitions/mysql.drop.sql
|
||||
test/fixtures/db_definitions/mysql.sql
|
||||
test/fixtures/db_definitions/postgresql.sql
|
||||
test/fixtures/db_definitions/sqlite.sql
|
||||
test/fixtures/fun_users.yml
|
||||
test/fixtures/group_memberships.yml
|
||||
test/fixtures/group_tag.yml
|
||||
test/fixtures/groups.yml
|
||||
test/foreign_keys_test.rb
|
||||
test/fun_user_plus.rb
|
||||
test/invisible_model_access_test.rb
|
||||
test/invisible_model_assoc_test.rb
|
||||
test/invisible_model_classes_test.rb
|
||||
test/magic_module_test.rb
|
||||
test/test_existing_model.rb
|
||||
website/index.html
|
||||
website/index.txt
|
||||
website/javascripts/rounded_corners_lite.inc.js
|
||||
website/stylesheets/screen.css
|
||||
website/template.js
|
||||
website/template.rhtml
|
||||
website/version-raw.js
|
||||
website/version-raw.txt
|
||||
website/version.js
|
||||
website/version.txt
|
@ -1,294 +0,0 @@
|
||||
See http://magicmodels.rubyforge.org/dr_nic_magic_models for pretty README
|
||||
|
||||
Ugly README (from website/index.txt in Textile format):
|
||||
h1. Dr Nic's Magic Models
|
||||
|
||||
If you've used Ruby on Rails you'll have written at least one model class like this:
|
||||
|
||||
<pre syntax='ruby'>
|
||||
class Person < ActiveRecord::Base
|
||||
has_many :memberships
|
||||
has_many :groups, :through => :memberships
|
||||
belongs_to :family
|
||||
validates_presence_of :firstname, :lastname, :email
|
||||
end
|
||||
</pre>
|
||||
|
||||
A few minutes later you'll have wondered to yourself,
|
||||
|
||||
<blockquote>
|
||||
Why do I have write my own <code>has_many</code>, <code>belongs_to</code>, and <code>validates_presence_of</code>
|
||||
commands if all the data is in the database schema?
|
||||
</blockquote>
|
||||
|
||||
Now, for the very first time, your classes can look like this:
|
||||
|
||||
<pre syntax='ruby'>
|
||||
class Person < ActiveRecord::Base
|
||||
end
|
||||
</pre>
|
||||
|
||||
or, if you are lazy...
|
||||
|
||||
<pre syntax='ruby'>
|
||||
class Person < ActiveRecord::Base; end
|
||||
</pre>
|
||||
|
||||
or, if you read right to the end of this page, this...
|
||||
|
||||
<pre syntax='ruby'>
|
||||
# Go fish.
|
||||
</pre>
|
||||
|
||||
Magic and mystery abound. All for you. Impress your friends, amaze your mother.
|
||||
|
||||
NOTE: The gratuitous use of *Dr Nic's* in the name should only enhance the mystical magikery,
|
||||
for magic needs a magician; and I love magic. I always wanted to create my own magic trick.
|
||||
So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too,
|
||||
if Harry were 32 and better dressed.
|
||||
|
||||
h2. Installation
|
||||
|
||||
To install the Dr Nic's Magic Models gem you can run the following command to
|
||||
fetch the gem remotely from RubyForge:
|
||||
<pre>
|
||||
gem install dr_nic_magic_models
|
||||
</pre>
|
||||
|
||||
or "download the gem manually":http://rubyforge.org/projects/magicmodels and
|
||||
run the above command in the download directory.
|
||||
|
||||
Now you need to <code>require</code> the gem into your Ruby/Rails app. Insert the following
|
||||
line into your script (use <code>config/environment.rb</code> for your Rails apps):
|
||||
|
||||
<pre>
|
||||
require 'dr_nic_magic_models'
|
||||
</pre>
|
||||
|
||||
Your application is now blessed with magical mystery.
|
||||
|
||||
h2. David Copperfield eat your Ruby-crusted heart out
|
||||
|
||||
Let's demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):
|
||||
|
||||
<pre syntax="ruby">
|
||||
rails magic_show -d sqlite3
|
||||
cd magic_show
|
||||
ruby script/generate model Person
|
||||
ruby script/generate model Group
|
||||
ruby script/generate model Membership
|
||||
</pre>
|
||||
|
||||
Update the migration <code>001_create_people.rb</code> with:
|
||||
<pre syntax="ruby">
|
||||
class CreatePeople < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :people do |t|
|
||||
t.column :firstname, :string, :null => false
|
||||
t.column :lastname, :string, :null => false
|
||||
t.column :email, :string, :null => false
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :people
|
||||
end
|
||||
end
|
||||
</pre>
|
||||
|
||||
Similarly, update the <code>def self.up</code> method of <code>002_create_groups.rb</code>
|
||||
with:
|
||||
<pre syntax="ruby">
|
||||
create_table :groups do |t|
|
||||
t.column :name, :string, :null => false
|
||||
t.column :description, :string
|
||||
end
|
||||
</pre>
|
||||
|
||||
and <code>003_create_memberships.rb</code> with:
|
||||
<pre syntax="ruby">
|
||||
create_table :memberships do |t|
|
||||
t.column :person_id, :integer, :null => false
|
||||
t.column :group_id, :integer, :null => false
|
||||
end
|
||||
</pre>
|
||||
|
||||
And run your migrations to create the three tables:
|
||||
<pre>
|
||||
rake db:migrate
|
||||
</pre>
|
||||
|
||||
h3. And now for some "woofle dust":http://en.wikipedia.org/wiki/List_of_conjuring_terms ...
|
||||
|
||||
At the end of <code>config/environment.rb</code> add the following line:
|
||||
|
||||
<pre>
|
||||
require 'dr_nic_magic_models'
|
||||
</pre>
|
||||
|
||||
Now, let's do a magic trick. First, let's check our model classes (<code>app/models/person.rb</code> etc):
|
||||
|
||||
<pre syntax="ruby">
|
||||
class Person < ActiveRecord::Base
|
||||
end
|
||||
class Group < ActiveRecord::Base
|
||||
end
|
||||
class Membership < ActiveRecord::Base
|
||||
end
|
||||
</pre>
|
||||
|
||||
Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.
|
||||
|
||||
UPDATE: To turn on magic validations, you now need to invoke <code>generate_validations</code> on defined classes. So, update your model classes:
|
||||
|
||||
<pre syntax="ruby">
|
||||
class Person < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
class Group < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
class Membership < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
</pre>
|
||||
|
||||
For this trick, we'll need an ordinary console session. Any old one lying around the house will do.
|
||||
|
||||
<pre>
|
||||
ruby script/console
|
||||
</pre>
|
||||
|
||||
Now a normal model class is valid until you explicitly add <code>validates_xxx</code> commands.
|
||||
With Dr Nic's Magic Models:
|
||||
|
||||
<pre syntax="ruby">
|
||||
person = Person.new
|
||||
=> #<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true>
|
||||
person.valid?
|
||||
=> false
|
||||
person.errors
|
||||
=> #<ActiveRecord::Errors:0x3537b38 @errors={
|
||||
"firstname"=>["can't be blank", "is too long (maximum is 255 characters)"],
|
||||
"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"],
|
||||
"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
|
||||
@base=#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true,
|
||||
@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
|
||||
</pre>
|
||||
|
||||
*Kapoow!* Instant validation! (NOTE: not as instant as it used to be - remember - you need to call <code>generate_validations</code> on each class as required)
|
||||
|
||||
Because you specified the three columns as <code>:null => false</code>,
|
||||
your ActiveRecord models will now automagically generated <code>validates_presence_of</code>
|
||||
for each non-null field, plus several other validations (since version 0.8.0).
|
||||
|
||||
Ok, we're just warming up.
|
||||
|
||||
Your models normally require association commands (<code>has_many</code>, <code>belongs_to</code>, etc, as
|
||||
demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.
|
||||
|
||||
Let's just watch what Dr Nic's Magic Models can do without any effort at all...
|
||||
|
||||
<pre syntax="ruby">
|
||||
person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
|
||||
group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
|
||||
membership = Membership.create(:person => person, :group => group)
|
||||
person.memberships.length
|
||||
=> 1
|
||||
membership.person
|
||||
=> <Person:0x38898e8 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
|
||||
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
|
||||
group.memberships
|
||||
=> [<Membership:0x3c8cd70 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
|
||||
</pre>
|
||||
|
||||
|
||||
The final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations...
|
||||
|
||||
<pre syntax="ruby">
|
||||
>> person.groups
|
||||
=> [<Group:0x39047e0 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
|
||||
>> group.people
|
||||
=> [<Person:0x3c33580 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
|
||||
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
|
||||
</pre>
|
||||
|
||||
h3. Drum roll...
|
||||
|
||||
Ladies and gentlemen. For my final feat of magical mastery, I'll ask you to do
|
||||
something you've never done before. This illusion is akin to the "floating lady":http://www.toytent.com/Posters/985.html
|
||||
illusion that has been passed down through generations of magicians.
|
||||
|
||||
Exit your console session.
|
||||
|
||||
DELETE your three model classes: <code>person.rb, group.rb, and membership.rb</code> from the
|
||||
<code>app/models</code> folder. (You can always get them back via the model generator... be fearless!)
|
||||
|
||||
<pre>rm app/models/*.rb</pre>
|
||||
|
||||
Re-launch your console.
|
||||
|
||||
*drums are still rolling...*
|
||||
|
||||
Be prepared to applaud loudly...
|
||||
|
||||
<pre syntax="ruby">
|
||||
>> Person
|
||||
=> Person
|
||||
</pre>
|
||||
|
||||
You applaud loudly, but watch for more...
|
||||
|
||||
<pre syntax="ruby">
|
||||
>> Person.new.valid?
|
||||
=> false
|
||||
>> person = Person.find(1)
|
||||
=> <Person:0x3958930 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
|
||||
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
|
||||
>> person.valid?
|
||||
=> true
|
||||
>> person.memberships
|
||||
=> [<Membership:0x393a000 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
|
||||
>> person.groups
|
||||
=> [<Group:0x390df60 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
|
||||
</pre>
|
||||
|
||||
h3. Tada!
|
||||
|
||||
The end.
|
||||
|
||||
h3. Use modules to scope your magic
|
||||
|
||||
Only want to pick up tables starting with <code>blog_</code>?
|
||||
|
||||
<pre syntax="ruby">module Blog
|
||||
magic_module :table_name_prefix => 'blog_'
|
||||
end
|
||||
|
||||
Blog::Post.table_name # => 'blog_posts'
|
||||
</pre>
|
||||
|
||||
h2. Dr Nic's Blog
|
||||
|
||||
"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and
|
||||
other stories and things.
|
||||
|
||||
h2. Articles about Magic Models
|
||||
|
||||
* "Announcement":http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/
|
||||
* "BTS - Class creation":http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/
|
||||
|
||||
|
||||
h2. Forum
|
||||
|
||||
"http://groups.google.com/group/magicmodels":http://groups.google.com/group/magicmodels
|
||||
|
||||
h2. Licence
|
||||
|
||||
This code is free to use under the terms of the MIT licence.
|
||||
|
||||
h2. Contact
|
||||
|
||||
Comments are welcome. Send an email to "Dr Nic Williams":mailto:drnicwilliams@gmail.com
|
||||
or via his blog at "http://www.drnicwilliams.com":http://www.drnicwilliams.com
|
||||
|
@ -1,134 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/clean'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require 'hoe'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models', 'version')
|
||||
|
||||
AUTHOR = "nicwilliams" # can also be an array of Authors
|
||||
EMAIL = "drnicwilliams@gmail.com"
|
||||
DESCRIPTION = "Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!"
|
||||
GEM_NAME = "dr_nic_magic_models" # what ppl will type to install your gem
|
||||
RUBYFORGE_PROJECT = "magicmodels" # The unix name for your project
|
||||
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
||||
|
||||
|
||||
NAME = "magic_multi_connections"
|
||||
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
||||
VERS = ENV['VERSION'] || (DrNicMagicModels::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
||||
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
||||
RDOC_OPTS = ['--quiet', '--title', "dr_nic_magic_models documentation",
|
||||
"--opname", "index.html",
|
||||
"--line-numbers",
|
||||
"--main", "README",
|
||||
"--inline-source"]
|
||||
|
||||
class Hoe
|
||||
def extra_deps
|
||||
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
||||
end
|
||||
end
|
||||
|
||||
# Generate all the Rake tasks
|
||||
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
||||
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
||||
p.author = AUTHOR
|
||||
p.description = DESCRIPTION
|
||||
p.email = EMAIL
|
||||
p.summary = DESCRIPTION
|
||||
p.url = HOMEPATH
|
||||
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
||||
p.test_globs = ["test/**/test_*.rb"]
|
||||
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
||||
|
||||
# == Optional
|
||||
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
||||
|
||||
#p.extra_deps - An array of rubygem dependencies.
|
||||
#p.spec_extras - A hash of extra values to set in the gemspec.
|
||||
end
|
||||
|
||||
# Run the unit tests
|
||||
|
||||
for adapter in %w( sqlite mysql postgresql ) # UNTESTED - postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase )
|
||||
Rake::TestTask.new("test_#{adapter}") { |t|
|
||||
t.libs << "test" << "test/connections/native_#{adapter}"
|
||||
t.pattern = "test/*_test{,_#{adapter}}.rb"
|
||||
t.verbose = true
|
||||
}
|
||||
end
|
||||
|
||||
SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
|
||||
|
||||
desc 'Build the MySQL test databases'
|
||||
task :build_mysql_databases do
|
||||
puts File.join(SCHEMA_PATH, 'mysql.sql')
|
||||
%x( mysqladmin -u root create "#{GEM_NAME}_unittest" )
|
||||
cmd = "mysql -u root #{GEM_NAME}_unittest < \"#{File.join(SCHEMA_PATH, 'mysql.sql')}\""
|
||||
puts "#{cmd}\n"
|
||||
%x( #{cmd} )
|
||||
end
|
||||
|
||||
desc 'Drop the MySQL test databases'
|
||||
task :drop_mysql_databases do
|
||||
%x( mysqladmin -u root -f drop "#{GEM_NAME}_unittest" )
|
||||
end
|
||||
|
||||
desc 'Rebuild the MySQL test databases'
|
||||
task :rebuild_mysql_databases => [:drop_mysql_databases, :build_mysql_databases]
|
||||
|
||||
desc 'Build the sqlite test databases'
|
||||
task :build_sqlite_databases do
|
||||
# puts File.join(SCHEMA_PATH, 'sqlite.sql')
|
||||
# %x( sqlite3 test.db < test/fixtures/db_definitions/sqlite.sql )
|
||||
file = File.join(SCHEMA_PATH, 'sqlite.sql')
|
||||
cmd = "sqlite3 test.db < #{file}"
|
||||
puts cmd
|
||||
%x( #{cmd} )
|
||||
end
|
||||
|
||||
desc 'Drop the sqlite test databases'
|
||||
task :drop_sqlite_databases do
|
||||
%x( rm -f test.db )
|
||||
end
|
||||
|
||||
desc 'Rebuild the sqlite test databases'
|
||||
task :rebuild_sqlite_databases => [:drop_sqlite_databases, :build_sqlite_databases]
|
||||
|
||||
desc 'Build the PostgreSQL test databases'
|
||||
task :build_postgresql_databases do
|
||||
%x( createdb "#{GEM_NAME}_unittest" )
|
||||
%x( psql "#{GEM_NAME}_unittest" -f "#{File.join(SCHEMA_PATH, 'postgresql.sql')}" )
|
||||
end
|
||||
|
||||
desc 'Drop the PostgreSQL test databases'
|
||||
task :drop_postgresql_databases do
|
||||
%x( dropdb "#{GEM_NAME}_unittest" )
|
||||
end
|
||||
|
||||
desc 'Rebuild the PostgreSQL test databases'
|
||||
task :rebuild_postgresql_databases => [:drop_postgresql_databases, :build_postgresql_databases]
|
||||
|
||||
|
||||
desc 'Generate website files'
|
||||
task :website_generate do
|
||||
sh %{ ruby scripts/txt2html website/index.txt > website/index.html }
|
||||
sh %{ ruby scripts/txt2js website/version.txt > website/version.js }
|
||||
sh %{ ruby scripts/txt2js website/version-raw.txt > website/version-raw.js }
|
||||
end
|
||||
|
||||
desc 'Upload website files to rubyforge'
|
||||
task :website_upload do
|
||||
config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
|
||||
host = "#{config["username"]}@rubyforge.org"
|
||||
remote_dir = "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
||||
local_dir = 'website'
|
||||
sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
|
||||
end
|
||||
|
||||
desc 'Generate and upload website files'
|
||||
task :website => [:website_generate, :website_upload]
|
@ -1,3 +0,0 @@
|
||||
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models')
|
||||
|
@ -1,30 +0,0 @@
|
||||
require 'rbconfig'
|
||||
require 'find'
|
||||
require 'ftools'
|
||||
|
||||
include Config
|
||||
|
||||
# this was adapted from rdoc's install.rb by ways of Log4r
|
||||
|
||||
$sitedir = CONFIG["sitelibdir"]
|
||||
unless $sitedir
|
||||
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
|
||||
$libdir = File.join(CONFIG["libdir"], "ruby", version)
|
||||
$sitedir = $:.find {|x| x =~ /site_ruby/ }
|
||||
if !$sitedir
|
||||
$sitedir = File.join($libdir, "site_ruby")
|
||||
elsif $sitedir !~ Regexp.quote(version)
|
||||
$sitedir = File.join($sitedir, version)
|
||||
end
|
||||
end
|
||||
|
||||
# the acual gruntwork
|
||||
Dir.chdir("lib")
|
||||
|
||||
Find.find("dr_nic_magic_models", "dr_nic_magic_models.rb") { |f|
|
||||
if f[-3..-1] == ".rb"
|
||||
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
|
||||
else
|
||||
File::makedirs(File.join($sitedir, *f.split(/\//)))
|
||||
end
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#TODO: Use :dependent for FK cascade?
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
class << self
|
||||
public
|
||||
def get_unique_index_columns
|
||||
self.connection.indexes(self.table_name, "#{self.name} Indexes").select { |index| index.unique && index.columns.size == 1 }.map{ |index| index.columns.first }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,32 +0,0 @@
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
|
||||
# Generic holder for foreign key constraint meta-data from the database schema.
|
||||
class ForeignKeyConstraint < Struct.new(:name, :table, :foreign_key, :reference_table, :reference_column, :on_update, :on_delete)
|
||||
end
|
||||
|
||||
class AbstractAdapter
|
||||
|
||||
# Does this adapter support the ability to fetch foreign key information?
|
||||
# Backend specific, as the abstract adapter always returns +false+.
|
||||
def supports_fetch_foreign_keys?
|
||||
false
|
||||
end
|
||||
|
||||
def foreign_key_constraints(table, name = nil)
|
||||
raise NotImplementedError, "foreign_key_constraints is not implemented for #{self.class}"
|
||||
end
|
||||
|
||||
def remove_foreign_key_constraint(table_name, constraint_name)
|
||||
raise NotImplementedError, "rename_table is not implemented for #{self.class}"
|
||||
end
|
||||
|
||||
protected
|
||||
def symbolize_foreign_key_constraint_action(constraint_action)
|
||||
return nil if constraint_action.nil?
|
||||
constraint_action.downcase.gsub(/\s/, '_').to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,42 +0,0 @@
|
||||
# Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class MysqlAdapter < AbstractAdapter
|
||||
def supports_fetch_foreign_keys?
|
||||
true
|
||||
end
|
||||
|
||||
def foreign_key_constraints(table, name = nil)
|
||||
constraints = []
|
||||
execute("SHOW CREATE TABLE #{table}", name).each do |row|
|
||||
row[1].each do |create_line|
|
||||
if create_line.strip =~ /CONSTRAINT `([^`]+)` FOREIGN KEY \(`([^`]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)([^,]*)/
|
||||
constraint = ForeignKeyConstraint.new(Regexp.last_match(1), table, Regexp.last_match(2), Regexp.last_match(3), Regexp.last_match(4), nil, nil)
|
||||
|
||||
constraint_params = {}
|
||||
|
||||
unless Regexp.last_match(5).nil?
|
||||
Regexp.last_match(5).strip.split('ON ').each do |param|
|
||||
constraint_params[Regexp.last_match(1).upcase] = Regexp.last_match(2).strip.upcase if param.strip =~ /([^ ]+) (.+)/
|
||||
end
|
||||
end
|
||||
|
||||
constraint.on_update = symbolize_foreign_key_constraint_action(constraint_params['UPDATE']) if constraint_params.include? 'UPDATE'
|
||||
constraint.on_delete = symbolize_foreign_key_constraint_action(constraint_params['DELETE']) if constraint_params.include? 'DELETE'
|
||||
|
||||
constraints << constraint
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
constraints
|
||||
end
|
||||
|
||||
def remove_foreign_key_constraint(table_name, constraint_name)
|
||||
execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{constraint_name}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,45 +0,0 @@
|
||||
# Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class PostgreSQLAdapter < AbstractAdapter
|
||||
|
||||
def supports_fetch_foreign_keys?
|
||||
true
|
||||
end
|
||||
|
||||
def foreign_key_constraints(table, name = nil)
|
||||
|
||||
|
||||
sql = "SELECT conname, pg_catalog.pg_get_constraintdef(oid) AS consrc FROM pg_catalog.pg_constraint WHERE contype='f' "
|
||||
sql += "AND conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='#{table}')"
|
||||
|
||||
result = query(sql, name)
|
||||
|
||||
keys = []
|
||||
re = /(?i)^FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)(?: ON UPDATE (\w+))?(?: ON DELETE (\w+))?$/
|
||||
result.each do |row|
|
||||
# pg_catalog.pg_get_constraintdef returns a string like this:
|
||||
# FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
if match = re.match(row[1])
|
||||
|
||||
keys << ForeignKeyConstraint.new(row[0],
|
||||
table,
|
||||
match[1],
|
||||
match[2],
|
||||
match[3],
|
||||
symbolize_foreign_key_constraint_action(match[4]),
|
||||
symbolize_foreign_key_constraint_action(match[5]))
|
||||
end
|
||||
end
|
||||
|
||||
keys
|
||||
end
|
||||
|
||||
def remove_foreign_key_constraint(table_name, constraint_name)
|
||||
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,34 +0,0 @@
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
unless defined?(ActiveRecord)
|
||||
begin
|
||||
require 'active_record'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'activerecord'
|
||||
end
|
||||
end
|
||||
|
||||
module DrNicMagicModels
|
||||
Logger = RAILS_DEFAULT_LOGGER rescue Logger.new(STDERR)
|
||||
end
|
||||
|
||||
require 'dr_nic_magic_models/magic_model'
|
||||
require 'dr_nic_magic_models/schema'
|
||||
require 'dr_nic_magic_models/validations'
|
||||
require 'dr_nic_magic_models/inflector'
|
||||
require 'base'
|
||||
require 'module'
|
||||
require 'rails' rescue nil
|
||||
require 'connection_adapters/abstract_adapter'
|
||||
require 'connection_adapters/mysql_adapter'
|
||||
require 'connection_adapters/postgresql_adapter'
|
||||
|
||||
# load the schema
|
||||
# TODO - add this to README - DrNicMagicModels::Schema.load_schema(true)
|
||||
|
||||
class ActiveRecord::Base
|
||||
include DrNicMagicModels::MagicModel
|
||||
extend DrNicMagicModels::Validations
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
module DrNicMagicModels
|
||||
class Inflector
|
||||
def table_names ; DrNicMagicModels::Schema.table_names; end
|
||||
def tables ; DrNicMagicModels::Schema.tables; end
|
||||
def models ; DrNicMagicModels::Schema.model; end
|
||||
|
||||
def class_name(table_name)
|
||||
ActiveRecord::Base.class_name(table_name)
|
||||
end
|
||||
|
||||
def post_class_creation(klass)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,133 +0,0 @@
|
||||
# Mixed into a class that is dynamically created, unless
|
||||
# the class was created by the Schema.load_schema process
|
||||
# which builds the whole class, thus no magicalness is
|
||||
# needed
|
||||
module DrNicMagicModels::MagicModel
|
||||
def self.append_features(base)
|
||||
super
|
||||
base.send(:include, InstanceMethods)
|
||||
class << base
|
||||
# Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
|
||||
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
||||
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
||||
def reflect_on_association(association)
|
||||
unless reflections[association]
|
||||
# See if an assocation can be generated
|
||||
self.new.send(association) rescue nil
|
||||
end
|
||||
reflections[association].is_a?(ActiveRecord::Reflection::AssociationReflection) ? reflections[association] : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
begin
|
||||
super
|
||||
rescue
|
||||
if unknown_method? method
|
||||
result = find_belongs_to method, *args, &block
|
||||
result = find_has_some method, *args, &block if not result
|
||||
result = find_has_some_indirect method, *args, &block if not result
|
||||
return result if result
|
||||
end
|
||||
add_known_unknown method
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def add_known_unknown(method)
|
||||
@known_unknowns ||= {}
|
||||
@known_unknowns[method] = true
|
||||
end
|
||||
|
||||
def unknown_method?(method)
|
||||
@known_unknowns.nil? or @known_unknowns.include? method
|
||||
end
|
||||
|
||||
def find_belongs_to(method, *args, &block)
|
||||
method_clean = clean_method method
|
||||
fkc =
|
||||
begin
|
||||
self.class.connection.foreign_key_constraints(self.class.table_name, method_clean)
|
||||
rescue NotImplementedError
|
||||
nil
|
||||
end
|
||||
if !fkc.nil? && fkc.length > 0
|
||||
foreign_key = fkc.first.foreign_key
|
||||
options = {:dependent => :destroy,
|
||||
:foreign_key => fkc.first.foreign_key,
|
||||
:class_name => self.class.class_name(fkc.first.reference_table)}
|
||||
else
|
||||
foreign_key = self.class.columns.select {|column| column.name == method_clean.to_s.foreign_key}.first
|
||||
end
|
||||
options ||= {}
|
||||
return add_belongs_to(method, method_clean, options, *args, &block) if foreign_key
|
||||
end
|
||||
|
||||
def add_belongs_to(method, method_clean, options, *args, &block)
|
||||
self.class.send 'belongs_to', method_clean.to_sym, options rescue puts $!
|
||||
self.send(method, *args, &block)
|
||||
end
|
||||
|
||||
def find_has_some(method, *args, &block)
|
||||
method_clean = clean_method method
|
||||
fkc = [method_clean.to_s.pluralize, method_clean.to_s.singularize].inject({}) do |pair, table_name|
|
||||
fkc = begin
|
||||
self.class.connection.foreign_key_constraints(table_name)
|
||||
rescue NotImplementedError
|
||||
nil
|
||||
end
|
||||
pair[table_name] = fkc if not fkc.blank?
|
||||
pair
|
||||
end
|
||||
if not fkc.blank?
|
||||
# assumes there is only one table found - that schema doesn't have a singular and plural table of same name
|
||||
foreign_key = fkc.values.first.find {|fk| fk.reference_table == self.class.table_name}
|
||||
if foreign_key
|
||||
foreign_key = foreign_key.foreign_key
|
||||
table_name = fkc.keys.first
|
||||
klass = Module.const_get table_name.singularize.camelize rescue nil
|
||||
options = {:foreign_key => foreign_key, :class_name => klass.name}
|
||||
end
|
||||
end
|
||||
unless foreign_key
|
||||
klass = Module.const_get method_clean.to_s.downcase.singularize.camelize rescue nil
|
||||
foreign_key = klass.columns.select {|column| column.name == self.class.name.foreign_key}.first if klass
|
||||
end
|
||||
options ||= {}
|
||||
return add_has_some(method, method_clean, options, *args, &block) if foreign_key
|
||||
end
|
||||
|
||||
def add_has_some(method, method_clean, options, *args, &block)
|
||||
association = method_clean.singularize == method_clean ? 'has_one' : 'has_many'
|
||||
self.class.send association, method_clean.to_sym, options rescue puts $!
|
||||
self.send(method, *args, &block)
|
||||
end
|
||||
|
||||
def find_has_some_indirect(method, *args, &block)
|
||||
klass = Module.const_get method.to_s.downcase.singularize.camelize rescue return
|
||||
join_table = nil
|
||||
self.connection.tables.each do |table|
|
||||
unless [self.class.table_name, klass.table_name].include? table
|
||||
columns = self.connection.columns(table).map(&:name)
|
||||
join_table = table if columns.include?(self.class.to_s.foreign_key) and columns.include?(klass.to_s.foreign_key)
|
||||
end
|
||||
break if join_table
|
||||
end
|
||||
return add_has_some_through(join_table, method, *args, &block) if join_table
|
||||
end
|
||||
|
||||
def add_has_some_through(join_table, method, *args, &block)
|
||||
self.class.send 'has_many', method, :through => join_table.to_sym
|
||||
self.send(method, *args, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_method(method)
|
||||
method.to_s.gsub(/=$/,'') # remove any = from the end of the method name
|
||||
end
|
||||
end
|
||||
end
|
@ -1,270 +0,0 @@
|
||||
module DrNicMagicModels
|
||||
|
||||
# ONE Schema per namespace module
|
||||
# Person, Company, etc share the Object namespace module, ie. ::Person, ::Company
|
||||
# Blog::Post, Blog::Comment, share the Blog namespace module
|
||||
class Schema
|
||||
attr_reader :modul
|
||||
|
||||
def initialize(modul)
|
||||
@modul = modul
|
||||
@table_name_prefix = modul.instance_variable_get("@table_name_prefix") rescue ''
|
||||
logger.info "Create Schema for #{@modul}, table_name_prefix '#{@table_name_prefix}'"
|
||||
end
|
||||
|
||||
cattr_accessor :inflector
|
||||
cattr_accessor :superklass
|
||||
|
||||
# Need to store models etc per-module, not in @ @models
|
||||
def inflector
|
||||
@inflector ||= Inflector.new
|
||||
end
|
||||
|
||||
# all in lower case please
|
||||
ReservedTables = [:schema_info, :sessions]
|
||||
@models = nil
|
||||
|
||||
def logger
|
||||
@logger ||= DrNicMagicModels::Logger
|
||||
end
|
||||
|
||||
def models
|
||||
load_schema if @models.nil?
|
||||
@models
|
||||
end
|
||||
|
||||
def tables
|
||||
load_schema if @tables.nil?
|
||||
@tables
|
||||
end
|
||||
|
||||
def table_names
|
||||
load_schema if @table_names.nil?
|
||||
@table_names
|
||||
end
|
||||
|
||||
def fks_on_table(table_name)
|
||||
load_schema if @models.nil?
|
||||
@fks_by_table[table_name.to_s] || []
|
||||
end
|
||||
|
||||
# active record only support 2 column link tables, otherwise use a model table, has_many and through
|
||||
def is_link_table?(table_name)
|
||||
load_schema if @models.nil?
|
||||
return @link_tables[table_name] if ! @link_tables[table_name].nil?
|
||||
column_names = @conn.columns(table_name).map{|x| x.name }
|
||||
@link_tables[table_name] = ! column_names.include?("id") && column_names.length == 2 && column_names.select { |x| x =~ /_id$/ } == column_names
|
||||
return @link_tables[table_name]
|
||||
end
|
||||
|
||||
def link_tables_for_class(klass)
|
||||
load_schema if @models.nil?
|
||||
end
|
||||
|
||||
def load_schema(preload = false)
|
||||
return if !@models.nil?
|
||||
|
||||
@superklass ||= ActiveRecord::Base
|
||||
raise "No database connection" if !(@conn = @superklass.connection)
|
||||
|
||||
@models = ModelHash.new
|
||||
@tables = Hash.new
|
||||
@fks_by_table = Hash.new
|
||||
@link_tables = Hash.new
|
||||
|
||||
@table_names = @conn.tables
|
||||
@table_names = @table_names.grep(/^#{@table_name_prefix}/) if @table_name_prefix
|
||||
@table_names = @table_names.sort
|
||||
|
||||
logger.info "For #{modul} tables are #{@table_names.inspect}"
|
||||
|
||||
# Work out which tables are in the model and which aren't
|
||||
@table_names.each do |table_name|
|
||||
|
||||
# deal with reserved tables & link_tables && other stray id-less tables
|
||||
#key = 'id'
|
||||
#case ActiveRecord::Base.primary_key_prefix_type
|
||||
# when :table_name
|
||||
# key = Inflector.foreign_key(table_name, false)
|
||||
# when :table_name_with_underscore
|
||||
# key = Inflector.foreign_key(table_name)
|
||||
#end
|
||||
#next if ReservedTables.include?(table_name.downcase.to_sym) ||
|
||||
# is_link_table?(table_name) ||
|
||||
# ! @conn.columns(table_name).map{ |x| x.name}.include?(key)
|
||||
|
||||
table_name_clean = table_name.gsub(/^#{@table_name_prefix}/,'')
|
||||
|
||||
# a model table then...
|
||||
model_class_name = inflector.class_name(table_name_clean)
|
||||
|
||||
logger.debug "Got a model table: #{table_name} => class #{model_class_name}"
|
||||
|
||||
@models[model_class_name] = table_name
|
||||
@tables[table_name] = model_class_name
|
||||
|
||||
if preload
|
||||
# create by MAGIC!
|
||||
klass = model_class_name.constantize
|
||||
|
||||
# Process FKs?
|
||||
if @conn.supports_fetch_foreign_keys?
|
||||
|
||||
tables.each do |table_name|
|
||||
logger.debug "Getting FKs for #{table_name}"
|
||||
@fks_by_table[table_name] = Array.new
|
||||
@conn.foreign_key_constraints(table_name).each do |fk|
|
||||
logger.debug "Got one: #{fk}"
|
||||
@fks_by_table[table_name].push(fk)
|
||||
end # do each fk
|
||||
|
||||
end # each table
|
||||
end
|
||||
|
||||
# Try to work out our link tables now...
|
||||
@models.keys.sort.each{|klass| process_table(@models[klass.to_s])}
|
||||
@link_tables.keys.sort.each{|table_name| process_link_table(table_name) if @link_tables[table_name]}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def process_table(table_name)
|
||||
|
||||
logger.debug "Processing model table #{table_name}"
|
||||
|
||||
# ok, so let's look at the foreign keys on the table...
|
||||
belongs_to_klass = @tables[table_name].constantize rescue return
|
||||
|
||||
processed_columns = Hash.new
|
||||
|
||||
fks_on_table(table_name).each do |fk|
|
||||
logger.debug "Found FK column by suffix _id [#{fk.foreign_key}]"
|
||||
has_some_klass = Inflector.classify(fk.reference_table).constantize rescue next
|
||||
processed_columns[fk.foreign_key] = { :has_some_klass => has_some_klass }
|
||||
processed_columns[fk.foreign_key].merge! add_has_some_belongs_to(belongs_to_klass, fk.foreign_key, has_some_klass) rescue next
|
||||
end
|
||||
|
||||
column_names = @conn.columns(table_name).map{ |x| x.name}
|
||||
column_names.each do |column_name|
|
||||
next if not column_name =~ /_id$/
|
||||
logger.debug "Found FK column by suffix _id [#{column_name}]"
|
||||
if processed_columns.key?(column_name)
|
||||
logger.debug "Skipping, already processed"
|
||||
next
|
||||
end
|
||||
has_some_klass = Inflector.classify(column_name.sub(/_id$/,"")).constantize rescue next
|
||||
processed_columns[column_name] = { :has_some_klass => has_some_klass }
|
||||
processed_columns[column_name].merge! add_has_some_belongs_to(belongs_to_klass, column_name, has_some_klass) rescue next
|
||||
end
|
||||
|
||||
#TODO: what if same classes in table?
|
||||
|
||||
# is this a link table with attributes? (has_many through?)
|
||||
return if processed_columns.keys.length < 2
|
||||
|
||||
processed_columns.keys.each do |key1|
|
||||
processed_columns.keys.each do |key2|
|
||||
next if key1 == key2
|
||||
logger.debug "\n*** #{processed_columns[key1][:has_some_class]}.send 'has_many', #{processed_columns[key2][:belongs_to_name].to_s.pluralize.to_sym}, :through => #{processed_columns[key2][:has_some_name]}\n\n"
|
||||
processed_columns[key1][:has_some_class].send 'has_many', processed_columns[key2][:belongs_to_name].to_s.pluralize.to_sym, :through => processed_columns[key2][:has_some_name].to_sym
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def add_has_some_belongs_to(belongs_to_klass, belongs_to_fk, has_some_klass)
|
||||
|
||||
logger.debug "Trying to add a #{belongs_to_klass} belongs_to #{has_some_klass}..."
|
||||
|
||||
# so this is a belongs_to & has_some style relationship...
|
||||
# is it a has_many, or a has_one? Well, let's assume a has_one has a unique index on the column please... good db design, haha!
|
||||
unique = belongs_to_klass.get_unique_index_columns.include?(belongs_to_fk)
|
||||
belongs_to_name = belongs_to_fk.sub(/_id$/, '').to_sym
|
||||
|
||||
logger.debug "\n*** #{belongs_to_klass}.send 'belongs_to', #{belongs_to_name}, :class_name => #{has_some_klass}, :foreign_key => #{belongs_to_fk}\n"
|
||||
belongs_to_klass.send(:belongs_to, belongs_to_name, :class_name => has_some_klass.to_s, :foreign_key => belongs_to_fk.to_sym)
|
||||
|
||||
# work out if we need a prefix
|
||||
has_some_name = (
|
||||
(unique ? belongs_to_klass.table_name.singularize : belongs_to_klass.table_name) +
|
||||
(belongs_to_name.to_s == has_some_klass.table_name.singularize ? "" : "_as_"+belongs_to_name.to_s)
|
||||
).downcase.to_sym
|
||||
method = unique ? :has_one : :has_many
|
||||
logger.debug "\n*** #{has_some_klass}.send(#{method}, #{has_some_name}, :class_name => #{belongs_to_klass.to_s}, :foreign_key => #{belongs_to_fk.to_sym})\n\n"
|
||||
has_some_klass.send(method, has_some_name, :class_name => belongs_to_klass.to_s, :foreign_key => belongs_to_fk.to_sym)
|
||||
|
||||
return { :method => method, :belongs_to_name => belongs_to_name, :has_some_name => has_some_name, :has_some_class => has_some_klass }
|
||||
|
||||
end
|
||||
|
||||
def process_link_table(table_name)
|
||||
|
||||
logger.debug "Processing link table #{table_name}"
|
||||
|
||||
classes_map = Hash.new
|
||||
column_names = @conn.columns(table_name).map{ |x| x.name}
|
||||
|
||||
# use foreign keys first
|
||||
fks_on_table(table_name).each do |fk|
|
||||
logger.debug "Processing fk: #{fk}"
|
||||
klass = Inflector.classify(fk.reference_table).constantize rescue logger.debug("Cannot find model #{class_name} for table #{fk.reference_table}") && return
|
||||
classes_map[fk.foreign_key] = klass
|
||||
end
|
||||
|
||||
logger.debug "Got #{classes_map.keys.length} references from FKs"
|
||||
|
||||
if classes_map.keys.length < 2
|
||||
|
||||
#Fall back on good ol _id recognition
|
||||
|
||||
column_names.each do |column_name|
|
||||
|
||||
# check we haven't processed by fks already
|
||||
next if ! classes_map[column_name].nil?
|
||||
referenced_table = column_name.sub(/_id$/, '')
|
||||
|
||||
begin
|
||||
klass = Inflector.classify(referenced_table).constantize
|
||||
# fall back on FKs here
|
||||
if ! klass.nil?
|
||||
classes_map[column_name] = klass
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# not detected the link table?
|
||||
logger.debug "Got #{classes_map.keys.length} references"
|
||||
logger.debug "Cannot detect both tables referenced in link table" && return if classes_map.keys.length != 2
|
||||
|
||||
logger.debug "Adding habtm relationship"
|
||||
|
||||
logger.debug "\n*** #{classes_map[column_names[0]]}.send 'has_and_belongs_to_many', #{column_names[1].sub(/_id$/,'').pluralize.to_sym}, :class_name => #{classes_map[column_names[1]].to_s}, :join_table => #{table_name.to_sym}\n"
|
||||
logger.debug "\n*** #{classes_map[column_names[1]]}.send 'has_and_belongs_to_many', #{column_names[0].sub(/_id$/,'').pluralize.to_sym}, :class_name => #{classes_map[column_names[0]].to_s}, :join_table => #{table_name.to_sym}\n\n"
|
||||
|
||||
classes_map[column_names[0]].send 'has_and_belongs_to_many', column_names[1].sub(/_id$/,'').pluralize.to_sym, :class_name => classes_map[column_names[1]].to_s, :join_table => table_name.to_sym
|
||||
classes_map[column_names[1]].send 'has_and_belongs_to_many', column_names[0].sub(/_id$/,'').pluralize.to_sym, :class_name => classes_map[column_names[0]].to_s, :join_table => table_name.to_sym
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
class ModelHash < Hash
|
||||
def unenquire(class_id)
|
||||
@enquired ||= {}
|
||||
@enquired[class_id = class_id.to_s] = false
|
||||
end
|
||||
|
||||
def enquired?(class_id)
|
||||
@enquired ||= {}
|
||||
@enquired[class_id.to_s]
|
||||
end
|
||||
|
||||
def [](class_id)
|
||||
enquired?(class_id = class_id.to_s)
|
||||
@enquired[class_id] = true
|
||||
super(class_id)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,46 +0,0 @@
|
||||
module DrNicMagicModels
|
||||
module Validations
|
||||
|
||||
def generate_validations
|
||||
|
||||
logger = DrNicMagicModels::Logger
|
||||
|
||||
# Ensure that the connection to db is established, else validations don't get created.
|
||||
ActiveRecord::Base.connection
|
||||
|
||||
# Code reworked from http://www.redhillconsulting.com.au/rails_plugins.html
|
||||
# Thanks Red Hill Consulting for using an MIT licence :o)
|
||||
|
||||
# NOT NULL constraints
|
||||
self.columns.reject { |column| column.name =~ /(?i)^(((created|updated)_(at|on))|position|type|id)$/ }.each do |column|
|
||||
|
||||
if column.type == :integer
|
||||
logger.debug "validates_numericality_of #{column.name}, :allow_nil => #{column.null.inspect}, :only_integer => true"
|
||||
self.validates_numericality_of column.name, :allow_nil => column.null, :only_integer => true
|
||||
elsif column.number?
|
||||
logger.debug "validates_numericality_of #{column.name}, :allow_nil => #{column.null.inspect}"
|
||||
self.validates_numericality_of column.name, :allow_nil => column.null
|
||||
elsif column.text? && column.limit
|
||||
logger.debug "validates_length_of #{column.name}, :allow_nil => #{column.null.inspect}, :maximum => #{column.limit}"
|
||||
self.validates_length_of column.name, :allow_nil => column.null, :maximum => column.limit
|
||||
end
|
||||
|
||||
# Active record seems to interpolate booleans anyway to either true, false or nil...
|
||||
if column.type == :boolean
|
||||
logger.debug "validates_inclusion_of #{column.name}, :in => [true, false], :allow_nil => #{column.null}, :message => ActiveRecord::Errors.default_error_messages[:blank]"
|
||||
self.validates_inclusion_of column.name, :in => [true, false], :allow_nil => column.null, :message => ActiveRecord::Errors.default_error_messages[:blank]
|
||||
elsif !column.null
|
||||
logger.debug "validates_presence_of #{column.name}"
|
||||
self.validates_presence_of column.name
|
||||
end
|
||||
end
|
||||
|
||||
# Single-column UNIQUE indexes
|
||||
get_unique_index_columns.each do |col|
|
||||
logger.debug "validates_uniqueness_of #{col}"
|
||||
self.validates_uniqueness_of col
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
module DrNicMagicModels #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 0
|
||||
MINOR = 9
|
||||
TINY = 2
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
@ -1,33 +0,0 @@
|
||||
class Module
|
||||
alias :normal_const_missing :const_missing
|
||||
|
||||
def const_missing(class_id)
|
||||
begin
|
||||
return normal_const_missing(class_id)
|
||||
rescue
|
||||
end
|
||||
@magic_schema ||= DrNicMagicModels::Schema.new self
|
||||
unless table_name = @magic_schema.models[class_id]
|
||||
raise NameError.new("uninitialized constant #{class_id}") if @magic_schema.models.enquired? class_id
|
||||
end
|
||||
superklass = @magic_schema.superklass || ActiveRecord::Base
|
||||
klass = create_class(class_id, superklass) do
|
||||
set_table_name table_name
|
||||
# include DrNicMagicModels::MagicModel
|
||||
# extend DrNicMagicModels::Validations
|
||||
end
|
||||
klass.generate_validations # need to call this AFTER the class name has been assigned
|
||||
@magic_schema.inflector.post_class_creation klass
|
||||
klass
|
||||
end
|
||||
|
||||
def magic_module(options)
|
||||
self.instance_variable_set "@table_name_prefix", options[:table_name_prefix] if options[:table_name_prefix]
|
||||
end
|
||||
|
||||
private
|
||||
def create_class(class_name, superclass, &block)
|
||||
klass = Class.new superclass, &block
|
||||
self.const_set class_name, klass
|
||||
end
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
module Dependencies #:nodoc:#
|
||||
|
||||
@@models_dir = File.expand_path(File.join(RAILS_ROOT,'app','models'))
|
||||
|
||||
# don't reload models... it doesn't work anyway, not sure why they haven't done this?
|
||||
# submit as patch?
|
||||
alias require_or_load_old require_or_load
|
||||
def require_or_load(file_name, *args)
|
||||
file_name = $1 if file_name =~ /^(.*)\.rb$/
|
||||
expanded = File.expand_path(file_name)
|
||||
old_mechanism = Dependencies.mechanism
|
||||
if expanded =~ /^#{@@models_dir}/
|
||||
RAILS_DEFAULT_LOGGER.debug "*** Not reloading #{file_name}"
|
||||
Dependencies.mechanism = :require
|
||||
end
|
||||
require_or_load_old(file_name, *args)
|
||||
Dependencies.mechanism = old_mechanism
|
||||
end
|
||||
end
|
@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'redcloth'
|
||||
require 'syntax/convertors/html'
|
||||
require 'erb'
|
||||
require File.dirname(__FILE__) + '/../lib/dr_nic_magic_models/version.rb'
|
||||
|
||||
version = DrNicMagicModels::VERSION::STRING
|
||||
download = 'http://rubyforge.org/projects/magicmodels'
|
||||
|
||||
class Fixnum
|
||||
def ordinal
|
||||
# teens
|
||||
return 'th' if (10..19).include?(self % 100)
|
||||
# others
|
||||
case self % 10
|
||||
when 1: return 'st'
|
||||
when 2: return 'nd'
|
||||
when 3: return 'rd'
|
||||
else return 'th'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Time
|
||||
def pretty
|
||||
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
||||
end
|
||||
end
|
||||
|
||||
def convert_syntax(syntax, source)
|
||||
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
||||
end
|
||||
|
||||
if ARGV.length >= 1
|
||||
src, template = ARGV
|
||||
template ||= File.dirname(__FILE__) + '/../website/template.rhtml'
|
||||
else
|
||||
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
||||
exit!
|
||||
end
|
||||
|
||||
template = ERB.new(File.open(template).read)
|
||||
|
||||
title = nil
|
||||
body = nil
|
||||
File.open(src) do |fsrc|
|
||||
title_text = fsrc.readline
|
||||
body_text = fsrc.read
|
||||
syntax_items = []
|
||||
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
|
||||
ident = syntax_items.length
|
||||
element, syntax, source = $1, $2, $3
|
||||
syntax_items << "<#{element} class=\"syntax\">#{convert_syntax(syntax, source)}</#{element}>"
|
||||
"syntax-temp-#{ident}"
|
||||
}
|
||||
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
||||
body = RedCloth.new(body_text).to_html
|
||||
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
||||
end
|
||||
stat = File.stat(src)
|
||||
created = stat.ctime
|
||||
modified = stat.mtime
|
||||
|
||||
$stdout << template.result(binding)
|
@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'redcloth'
|
||||
require 'syntax/convertors/html'
|
||||
require 'erb'
|
||||
require 'active_support'
|
||||
require File.dirname(__FILE__) + '/../lib/dr_nic_magic_models/version.rb'
|
||||
|
||||
version = DrNicMagicModels::VERSION::STRING
|
||||
download = 'http://rubyforge.org/projects/magicmodels'
|
||||
|
||||
class Fixnum
|
||||
def ordinal
|
||||
# teens
|
||||
return 'th' if (10..19).include?(self % 100)
|
||||
# others
|
||||
case self % 10
|
||||
when 1: return 'st'
|
||||
when 2: return 'nd'
|
||||
when 3: return 'rd'
|
||||
else return 'th'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Time
|
||||
def pretty
|
||||
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
||||
end
|
||||
end
|
||||
|
||||
def convert_syntax(syntax, source)
|
||||
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
||||
end
|
||||
|
||||
if ARGV.length >= 1
|
||||
src, template = ARGV
|
||||
template ||= File.dirname(__FILE__) + '/../website/template.js'
|
||||
else
|
||||
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
||||
exit!
|
||||
end
|
||||
|
||||
template = ERB.new(File.open(template).read)
|
||||
|
||||
title = nil
|
||||
body = nil
|
||||
File.open(src) do |fsrc|
|
||||
title_text = fsrc.readline
|
||||
body = fsrc.read
|
||||
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
||||
end
|
||||
stat = File.stat(src)
|
||||
created = stat.ctime
|
||||
modified = stat.mtime
|
||||
|
||||
$stdout << template.result(binding)
|
Binary file not shown.
@ -1,72 +0,0 @@
|
||||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
require 'active_support/binding_of_caller'
|
||||
require 'active_support/breakpoint'
|
||||
require 'connection'
|
||||
require 'dr_nic_magic_models'
|
||||
|
||||
ActiveSupport::Deprecation.debug = true
|
||||
|
||||
|
||||
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
|
||||
|
||||
class Test::Unit::TestCase #:nodoc:
|
||||
self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
||||
self.use_instantiated_fixtures = false
|
||||
self.use_transactional_fixtures = true #(ENV['AR_NO_TX_FIXTURES'] != "yes")
|
||||
|
||||
def create_fixtures(*table_names, &block)
|
||||
Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
|
||||
end
|
||||
|
||||
def assert_date_from_db(expected, actual, message = nil)
|
||||
# SQL Server doesn't have a separate column type just for dates,
|
||||
# so the time is in the string and incorrectly formatted
|
||||
if current_adapter?(:SQLServerAdapter)
|
||||
assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
|
||||
elsif current_adapter?(:SybaseAdapter)
|
||||
assert_equal expected.to_s, actual.to_date.to_s, message
|
||||
else
|
||||
assert_equal expected.to_s, actual.to_s, message
|
||||
end
|
||||
end
|
||||
|
||||
def assert_queries(num = 1)
|
||||
ActiveRecord::Base.connection.class.class_eval do
|
||||
self.query_count = 0
|
||||
alias_method :execute, :execute_with_query_counting
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
ActiveRecord::Base.connection.class.class_eval do
|
||||
alias_method :execute, :execute_without_query_counting
|
||||
end
|
||||
assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
|
||||
end
|
||||
|
||||
def assert_no_queries(&block)
|
||||
assert_queries(0, &block)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def current_adapter?(type)
|
||||
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
|
||||
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
|
||||
end
|
||||
|
||||
ActiveRecord::Base.connection.class.class_eval do
|
||||
cattr_accessor :query_count
|
||||
alias_method :execute_without_query_counting, :execute
|
||||
def execute_with_query_counting(sql, name = nil)
|
||||
self.query_count += 1
|
||||
execute_without_query_counting(sql, name)
|
||||
end
|
||||
end
|
||||
|
||||
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
#ActiveRecord::Base.colorize_logging = false
|
@ -1,13 +0,0 @@
|
||||
print "Using native MySQL\n"
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
db1 = "dr_nic_magic_models_unittest"
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => "mysql",
|
||||
:username => "root",
|
||||
:encoding => "utf8",
|
||||
:database => db1
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
print "Using Postgres\n"
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
db1 = "dr_nic_magic_models_unittest"
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => "postgresql",
|
||||
:encoding => "utf8",
|
||||
:database => db1
|
||||
)
|
@ -1,10 +0,0 @@
|
||||
print "Using native Sqlite3\n"
|
||||
require 'logger'
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new("debug.log")
|
||||
|
||||
ActiveRecord::Base.establish_connection(
|
||||
:adapter => "sqlite3",
|
||||
:dbfile => "test.db"
|
||||
)
|
||||
|
@ -1,13 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
#require 'fixtures/user'
|
||||
#require 'fixtures/group'
|
||||
#require 'fixtures/membership'
|
||||
|
||||
class DummyTest < Test::Unit::TestCase
|
||||
def setup
|
||||
end
|
||||
|
||||
def test_truth
|
||||
assert true
|
||||
end
|
||||
end
|
@ -1,10 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class EnvTest < Test::Unit::TestCase
|
||||
|
||||
def test_modules
|
||||
assert_not_nil DrNicMagicModels
|
||||
assert_not_nil DrNicMagicModels::Validations
|
||||
assert_not_nil DrNicMagicModels::Schema
|
||||
end
|
||||
end
|
Binary file not shown.
@ -1,3 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
name: kind
|
@ -1,3 +0,0 @@
|
||||
first:
|
||||
fun_user_id: 1
|
||||
adjective_id: 1
|
@ -1,4 +0,0 @@
|
||||
DROP TABLE fun_users;
|
||||
DROP TABLE groups;
|
||||
DROP TABLE group_memberships;
|
||||
DROP TABLE group_tag;
|
@ -1,56 +0,0 @@
|
||||
CREATE TABLE `fun_users` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`type` varchar(255) NOT NULL,
|
||||
`firstname` varchar(50) NOT NULL,
|
||||
`lastname` varchar(50) NOT NULL,
|
||||
`login` varchar(50) NOT NULL,
|
||||
`email` varchar(50) NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
CREATE TABLE `groups` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`name` varchar(50) NOT NULL UNIQUE,
|
||||
`description` varchar(50) default NULL,
|
||||
`some_int` integer default NULL,
|
||||
`some_float` float default NULL,
|
||||
`some_bool` boolean default NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
CREATE TABLE `group_memberships` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`fun_user_id` int(11) NOT NULL,
|
||||
`group_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
CREATE TABLE `adjectives` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`name` varchar(255),
|
||||
PRIMARY KEY (`id`)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
CREATE TABLE `adjectives_fun_users` (
|
||||
`fun_user_id` int(11) NOT NULL,
|
||||
`adjective_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`fun_user_id`,`adjective_id`)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
|
||||
CREATE TABLE `group_tag` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`name` varchar(50) NOT NULL,
|
||||
`group_id` int(11) NOT NULL,
|
||||
`referenced_group_id` int(11) NULL UNIQUE,
|
||||
PRIMARY KEY (`id`)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
ALTER TABLE `group_tag`
|
||||
ADD FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE `group_tag`
|
||||
ADD FOREIGN KEY (`referenced_group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE `adjectives_fun_users`
|
||||
ADD FOREIGN KEY (`adjective_id`) REFERENCES `adjectives` (`id`) ON DELETE CASCADE;
|
@ -1,55 +0,0 @@
|
||||
CREATE TABLE "fun_users" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"type" varchar(255) NOT NULL,
|
||||
"firstname" varchar(50) NOT NULL,
|
||||
"lastname" varchar(50) NOT NULL,
|
||||
"login" varchar(50) NOT NULL,
|
||||
"email" varchar(50) NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "groups" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" varchar(50) NOT NULL UNIQUE,
|
||||
"description" varchar(50) default NULL,
|
||||
"some_int" integer default NULL,
|
||||
"some_float" float default NULL,
|
||||
"some_bool" boolean default NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "group_memberships" (
|
||||
"id" SERIAL,
|
||||
"fun_user_id" int NOT NULL,
|
||||
"group_id" int NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "adjectives" (
|
||||
"id" SERIAL,
|
||||
"name" varchar(255),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "adjectives_fun_users" (
|
||||
"fun_user_id" int NOT NULL,
|
||||
"adjective_id" int NOT NULL,
|
||||
PRIMARY KEY ("fun_user_id","adjective_id")
|
||||
);
|
||||
|
||||
CREATE TABLE "group_tag" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" varchar(50) NOT NULL,
|
||||
"group_id" int NOT NULL,
|
||||
"referenced_group_id" int NULL UNIQUE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "group_tag"
|
||||
ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id") ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE "group_tag"
|
||||
ADD FOREIGN KEY ("referenced_group_id") REFERENCES "groups" ("id") ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE "adjectives_fun_users"
|
||||
ADD FOREIGN KEY ("adjective_id") REFERENCES "adjectives" ("id") ON DELETE CASCADE;
|
@ -1,49 +0,0 @@
|
||||
CREATE TABLE `fun_users` (
|
||||
`id` int(11) NOT NULL,
|
||||
`type` varchar(255) NOT NULL,
|
||||
`firstname` varchar(50) NOT NULL,
|
||||
`lastname` varchar(50) NOT NULL,
|
||||
`login` varchar(50) NOT NULL,
|
||||
`email` varchar(50) NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `groups` (
|
||||
`id` int(11) NOT NULL ,
|
||||
`name` varchar(50) NOT NULL UNIQUE,
|
||||
`description` varchar(50) default NULL,
|
||||
`some_int` integer default NULL,
|
||||
`some_float` float default NULL,
|
||||
`some_bool` boolean default NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `group_memberships` (
|
||||
`id` int(11) NOT NULL,
|
||||
`fun_user_id` int(11) NOT NULL,
|
||||
`group_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `adjectives` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(255),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `adjectives_fun_users` (
|
||||
`fun_user_id` int(11) NOT NULL,
|
||||
`adjective_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`fun_user_id`,`adjective_id`)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE `group_tag` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` varchar(50) NOT NULL,
|
||||
`group_id` int(11) NOT NULL,
|
||||
`referenced_group_id` int(11) NULL UNIQUE,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
firstname: First
|
||||
lastname: Person
|
||||
login: first
|
||||
email: first@person.com
|
||||
type: FunUser
|
||||
second:
|
||||
id: 2
|
||||
firstname: Second
|
||||
lastname: Person
|
||||
login: sec
|
||||
email: sec@person.com
|
||||
type: FunUserPlus
|
@ -1,4 +0,0 @@
|
||||
first_first:
|
||||
id: 1
|
||||
group_id: 1
|
||||
fun_user_id: 1
|
@ -1,11 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
name: Test
|
||||
group_id: 1
|
||||
referenced_group_id: 1
|
||||
second:
|
||||
id: 2
|
||||
name: Also Test
|
||||
group_id: 1
|
||||
referenced_group_id: NULL
|
||||
|
@ -1,12 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
name: Group One
|
||||
description: First group
|
||||
other:
|
||||
id: 2
|
||||
name: Group Plus
|
||||
description: Extended Group
|
||||
other:
|
||||
id: 2
|
||||
name: Group Plus
|
||||
description: Extended Group
|
@ -1,2 +0,0 @@
|
||||
class FunUserPlus < FunUser
|
||||
end
|
@ -1,71 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require 'pp'
|
||||
|
||||
class InvisibleModelAccessTest < Test::Unit::TestCase
|
||||
# fixtures :fun_users, :groups, :group_memberships, :group_tag
|
||||
|
||||
def setup
|
||||
create_fixtures :fun_users, :groups, :group_memberships, :group_tag
|
||||
@classes = [FunUser, Group, GroupMembership, GroupTag]
|
||||
@group = Group.find(:first)
|
||||
end
|
||||
|
||||
def test_attributes
|
||||
assert_not_nil @group.name
|
||||
end
|
||||
|
||||
def test_find
|
||||
@classes.each do |klass|
|
||||
assert_not_nil obj = klass.find(1)
|
||||
assert_equal klass, obj.class
|
||||
end
|
||||
end
|
||||
|
||||
def test_sti
|
||||
require 'fun_user_plus'
|
||||
x = FunUserPlus.find(:all)
|
||||
assert x.inject {|n,v| n &= v.class == FunUserPlus}, "Wrong object class in FunUserPlus.find(:all)"
|
||||
plus = x.first
|
||||
assert_not_nil plus
|
||||
assert plus.is_a?(FunUser)
|
||||
assert plus.class == FunUserPlus
|
||||
end
|
||||
|
||||
def test_new
|
||||
assert group = Group.new(:name => 'New Group')
|
||||
assert_equal Group, group.class
|
||||
end
|
||||
|
||||
def test_update
|
||||
assert @group.update_attributes(:name => 'Group 1'), "Couldn't update:\n#{str=""; @group.errors.each_full { |msg| str += "#{msg}\n" }; str }"
|
||||
end
|
||||
|
||||
def test_delete
|
||||
assert @group.destroy
|
||||
end
|
||||
|
||||
def test_validations
|
||||
group = Group.new
|
||||
group.description = "x"*100
|
||||
group.some_int = 99.9
|
||||
group.some_float = "bah"
|
||||
# Active record seems to interpolate booleans anyway to either true, false or nil...
|
||||
# group.some_bool = "xxx" => false (!)
|
||||
|
||||
assert !group.valid?, "Group should not be valid"
|
||||
[:name, :description, :some_int, :some_float].each do |x|
|
||||
assert_not_nil group.errors[x], "Failed on #{x}=[#{group.send(x)}], it should be invalid"
|
||||
end
|
||||
|
||||
group = Group.new
|
||||
group.name = "name"
|
||||
group.description = "x"*49
|
||||
group.some_int = 99
|
||||
group.some_float = 99.9
|
||||
group.some_bool = true
|
||||
assert group.valid?, "Group should be valid"
|
||||
|
||||
group.name = @group.name
|
||||
assert !group.valid?, "Groups should have unique names"
|
||||
end
|
||||
end
|
@ -1,61 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class InvisibleModelAssocTest < Test::Unit::TestCase
|
||||
# fixtures :fun_users, :groups, :group_memberships, :group_tag, :adjectives, :adjectives_fun_users
|
||||
|
||||
def setup
|
||||
create_fixtures :fun_users, :groups, :group_memberships, :group_tag, :adjectives, :adjectives_fun_users
|
||||
@group = Group.find(1)
|
||||
@group_tag = GroupTag.find(1)
|
||||
@user = FunUser.find(1)
|
||||
@membership = GroupMembership.find(1)
|
||||
end
|
||||
|
||||
def test_hatbm
|
||||
assert_equal([Adjective.find(1)], @user.adjectives)
|
||||
end
|
||||
|
||||
def test_fk
|
||||
|
||||
gt = GroupTag.find(1)
|
||||
|
||||
# Not using FKs
|
||||
g = gt.group
|
||||
assert g.class == Group
|
||||
assert g.id == 1
|
||||
|
||||
# Using FKs
|
||||
if g.connection.supports_fetch_foreign_keys?
|
||||
g = gt.referenced_group
|
||||
assert g.class == Group
|
||||
assert g.id == 1
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_many
|
||||
assert_equal [@membership], @group.group_memberships
|
||||
assert_equal @group, @membership.group
|
||||
end
|
||||
|
||||
def test_has_one
|
||||
if @group_tag.connection.supports_fetch_foreign_keys?
|
||||
assert_equal @group, @group_tag.referenced_group
|
||||
# assert_equal @group_tag, @group.group_tag_as_referenced_group
|
||||
end
|
||||
end
|
||||
|
||||
def test_belongs_to
|
||||
assert_equal @user, @membership.fun_user
|
||||
assert_equal @group, @membership.group
|
||||
manual_result = GroupTag.find(:all, :conditions => ['group_tag.group_id = ?', @group.id]) #.sort{|a,b| a.id <=> b.id}
|
||||
auto_result = @group.group_tags #.sort{|a,b| a.id <=> b.id}
|
||||
assert manual_result == auto_result, "[#{manual_result.join(',')}] != [#{auto_result.join(',')}]"
|
||||
|
||||
end
|
||||
|
||||
def test_indirect
|
||||
assert_equal [@user], @group.fun_users
|
||||
assert_equal [@group], @user.groups
|
||||
end
|
||||
|
||||
end
|
@ -1,23 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class InvisibleModelClassesTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
|
||||
end
|
||||
|
||||
def test_available
|
||||
assert_not_nil Group
|
||||
assert_not_nil FunUser
|
||||
assert_not_nil GroupMembership
|
||||
assert_not_nil GroupTag, "Could not find GroupTag with singularized table name 'GroupTag'"
|
||||
end
|
||||
|
||||
def test_table_names
|
||||
assert_equal 'groups', Group.table_name
|
||||
assert_equal 'fun_users', FunUser.table_name
|
||||
assert_equal 'group_memberships', GroupMembership.table_name
|
||||
assert_equal 'group_tag', GroupTag.table_name
|
||||
end
|
||||
|
||||
end
|
@ -1,20 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
module MagicGroup
|
||||
magic_module :table_name_prefix => 'group_'
|
||||
end
|
||||
|
||||
class MagicModuleTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
|
||||
end
|
||||
|
||||
def test_table_prefix
|
||||
assert_nothing_thrown { MagicGroup::Membership }
|
||||
assert_equal('group_memberships', MagicGroup::Membership.table_name)
|
||||
assert_nothing_thrown { MagicGroup::Tag }
|
||||
assert_equal('group_tag', MagicGroup::Tag.table_name)
|
||||
end
|
||||
|
||||
end
|
@ -1,20 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require 'pp'
|
||||
|
||||
module TestBed
|
||||
class Group < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
end
|
||||
|
||||
class TestExistingModel < Test::Unit::TestCase
|
||||
# fixtures :fun_users, :groups, :group_memberships, :group_tag
|
||||
|
||||
def setup
|
||||
create_fixtures :fun_users, :groups, :group_memberships, :group_tag
|
||||
end
|
||||
|
||||
def test_valid
|
||||
assert(!TestBed::Group.new.valid?)
|
||||
end
|
||||
end
|
@ -1,404 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>
|
||||
Dr Nic’s Magic Models
|
||||
</title>
|
||||
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<script type="text/javascript" src="version-raw.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
settings = {
|
||||
tl: { radius: 10 },
|
||||
tr: { radius: 10 },
|
||||
bl: { radius: 10 },
|
||||
br: { radius: 10 },
|
||||
antiAlias: true,
|
||||
autoPad: true,
|
||||
validTags: ["div"]
|
||||
}
|
||||
var versionBox = new curvyCorners(settings, document.getElementById("version"));
|
||||
versionBox.applyCornersToAll();
|
||||
|
||||
document.getElementById("version_num").innerHTML = version;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<p><a href="/">↩ More Magic</a></p>
|
||||
|
||||
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/magicmodels"; return false'>
|
||||
Get Version
|
||||
<a id="version_num" href="http://rubyforge.org/projects/magicmodels" class="numbers"></a>
|
||||
</div>
|
||||
<h1>Dr Nic’s Magic Models</h1>
|
||||
<p>If you’ve used Ruby on Rails you’ll have written at least one model class like this:</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="keyword">class </span><span class="class">Person</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="ident">has_many</span> <span class="symbol">:memberships</span>
|
||||
<span class="ident">has_many</span> <span class="symbol">:groups</span><span class="punct">,</span> <span class="symbol">:through</span> <span class="punct">=></span> <span class="symbol">:memberships</span>
|
||||
<span class="ident">belongs_to</span> <span class="symbol">:family</span>
|
||||
<span class="ident">validates_presence_of</span> <span class="symbol">:firstname</span><span class="punct">,</span> <span class="symbol">:lastname</span><span class="punct">,</span> <span class="symbol">:email</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>A few minutes later you’ll have wondered to yourself,</p>
|
||||
|
||||
|
||||
<blockquote>
|
||||
Why do I have write my own <code>has_many</code>, <code>belongs_to</code>, and <code>validates_presence_of</code>
|
||||
commands if all the data is in the database schema?
|
||||
</blockquote>
|
||||
|
||||
<p>Now, for the very first time, your classes can look like this:</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="keyword">class </span><span class="class">Person</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>or, if you are lazy…</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="keyword">class </span><span class="class">Person</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">;</span> <span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>or, if you read right to the end of this page, this…</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="comment"># Go fish.</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>Magic and mystery abound. All for you. Impress your friends, amaze your mother.</p>
|
||||
|
||||
|
||||
<p><span class="caps">NOTE</span>: The gratuitous use of <strong>Dr Nic’s</strong> in the name should only enhance the mystical magikery,
|
||||
for magic needs a magician; and I love magic. I always wanted to create my own magic trick.
|
||||
So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too,
|
||||
if Harry were 32 and better dressed.</p>
|
||||
|
||||
|
||||
<h2>Installation</h2>
|
||||
|
||||
|
||||
To install the Dr Nic’s Magic Models gem you can run the following command to
|
||||
fetch the gem remotely from RubyForge:
|
||||
<pre>
|
||||
gem install dr_nic_magic_models
|
||||
</pre>
|
||||
|
||||
<p>or <a href="http://rubyforge.org/projects/magicmodels">download the gem manually</a> and
|
||||
run the above command in the download directory.</p>
|
||||
|
||||
|
||||
<p>Now you need to <code>require</code> the gem into your Ruby/Rails app. Insert the following
|
||||
line into your script (use <code>config/environment.rb</code> for your Rails apps):</p>
|
||||
|
||||
|
||||
<pre>
|
||||
require 'dr_nic_magic_models'
|
||||
</pre>
|
||||
|
||||
<p>Your application is now blessed with magical mystery.</p>
|
||||
|
||||
|
||||
<h2>David Copperfield eat your Ruby-crusted heart out</h2>
|
||||
|
||||
|
||||
<p>Let’s demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="ident">rails</span> <span class="ident">magic_show</span> <span class="punct">-</span><span class="ident">d</span> <span class="ident">sqlite3</span>
|
||||
<span class="ident">cd</span> <span class="ident">magic_show</span>
|
||||
<span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Person</span>
|
||||
<span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Group</span>
|
||||
<span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Membership</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>Update the migration <code>001_create_people.rb</code> with:
|
||||
<pre class="syntax">
|
||||
<span class="keyword">class </span><span class="class">CreatePeople</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Migration</span>
|
||||
<span class="keyword">def </span><span class="method">self.up</span>
|
||||
<span class="ident">create_table</span> <span class="symbol">:people</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:firstname</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:lastname</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:email</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="keyword">end</span>
|
||||
<span class="keyword">end</span>
|
||||
|
||||
<span class="keyword">def </span><span class="method">self.down</span>
|
||||
<span class="ident">drop_table</span> <span class="symbol">:people</span>
|
||||
<span class="keyword">end</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>Similarly, update the <code>def self.up</code> method of <code>002_create_groups.rb</code>
|
||||
with:
|
||||
<pre class="syntax">
|
||||
<span class="ident">create_table</span> <span class="symbol">:groups</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:name</span><span class="punct">,</span> <span class="symbol">:string</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:description</span><span class="punct">,</span> <span class="symbol">:string</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>and <code>003_create_memberships.rb</code> with:
|
||||
<pre class="syntax">
|
||||
<span class="ident">create_table</span> <span class="symbol">:memberships</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">t</span><span class="punct">|</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:person_id</span><span class="punct">,</span> <span class="symbol">:integer</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="ident">t</span><span class="punct">.</span><span class="ident">column</span> <span class="symbol">:group_id</span><span class="punct">,</span> <span class="symbol">:integer</span><span class="punct">,</span> <span class="symbol">:null</span> <span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
And run your migrations to create the three tables:
|
||||
<pre>
|
||||
rake db:migrate
|
||||
</pre>
|
||||
|
||||
<h3>And now for some <a href="http://en.wikipedia.org/wiki/List_of_conjuring_terms">woofle dust</a> ...</h3>
|
||||
|
||||
|
||||
<p>At the end of <code>config/environment.rb</code> add the following line:</p>
|
||||
|
||||
|
||||
<pre>
|
||||
require 'dr_nic_magic_models'
|
||||
</pre>
|
||||
|
||||
<p>Now, let’s do a magic trick. First, let’s check our model classes (<code>app/models/person.rb</code> etc):</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="keyword">class </span><span class="class">Person</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="keyword">end</span>
|
||||
<span class="keyword">class </span><span class="class">Group</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="keyword">end</span>
|
||||
<span class="keyword">class </span><span class="class">Membership</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.</p>
|
||||
|
||||
|
||||
<p><span class="caps">UPDATE</span>: To turn on magic validations, you now need to invoke <code>generate_validations</code> on defined classes. So, update your model classes:</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="keyword">class </span><span class="class">Person</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="ident">generate_validations</span>
|
||||
<span class="keyword">end</span>
|
||||
<span class="keyword">class </span><span class="class">Group</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="ident">generate_validations</span>
|
||||
<span class="keyword">end</span>
|
||||
<span class="keyword">class </span><span class="class">Membership</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
|
||||
<span class="ident">generate_validations</span>
|
||||
<span class="keyword">end</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>For this trick, we’ll need an ordinary console session. Any old one lying around the house will do.</p>
|
||||
|
||||
|
||||
<pre>
|
||||
ruby script/console
|
||||
</pre>
|
||||
|
||||
<p>Now a normal model class is valid until you explicitly add <code>validates_xxx</code> commands.
|
||||
With Dr Nic’s Magic Models:</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="ident">person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">new</span>
|
||||
<span class="punct">=></span> <span class="comment">#<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true></span>
|
||||
<span class="ident">person</span><span class="punct">.</span><span class="ident">valid?</span>
|
||||
<span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="ident">person</span><span class="punct">.</span><span class="ident">errors</span>
|
||||
<span class="punct">=></span> <span class="comment">#<ActiveRecord::Errors:0x3537b38 @errors={</span>
|
||||
<span class="punct">"</span><span class="string">firstname</span><span class="punct">"=>["</span><span class="string">can't be blank</span><span class="punct">",</span> <span class="punct">"</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">"],</span>
|
||||
<span class="punct">"</span><span class="string">lastname</span><span class="punct">"=>["</span><span class="string">can't be blank</span><span class="punct">",</span> <span class="punct">"</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">"],</span>
|
||||
<span class="punct">"</span><span class="string">email</span><span class="punct">"=>["</span><span class="string">can't be blank</span><span class="punct">",</span> <span class="punct">"</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">"]},</span>
|
||||
<span class="attribute">@base</span><span class="punct">=</span><span class="comment">#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true, </span>
|
||||
<span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">lastname</span><span class="punct">"=></span><span class="constant">nil</span><span class="punct">,</span> <span class="punct">"</span><span class="string">firstname</span><span class="punct">"=></span><span class="constant">nil</span><span class="punct">,</span> <span class="punct">"</span><span class="string">email</span><span class="punct">"=></span><span class="constant">nil</span><span class="punct">}>></span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p><strong>Kapoow!</strong> Instant validation! (NOTE: not as instant as it used to be – remember – you need to call <code>generate_validations</code> on each class as required)</p>
|
||||
|
||||
|
||||
<p>Because you specified the three columns as <code>:null => false</code>,
|
||||
your ActiveRecord models will now automagically generated <code>validates_presence_of</code>
|
||||
for each non-null field, plus several other validations (since version 0.8.0).</p>
|
||||
|
||||
|
||||
<p>Ok, we’re just warming up.</p>
|
||||
|
||||
|
||||
<p>Your models normally require association commands (<code>has_many</code>, <code>belongs_to</code>, etc, as
|
||||
demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.</p>
|
||||
|
||||
|
||||
<p>Let’s just watch what Dr Nic’s Magic Models can do without any effort at all…</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="ident">person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:firstname</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">Nic</span><span class="punct">",</span> <span class="symbol">:lastname</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">Williams</span><span class="punct">",</span> <span class="symbol">:email</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">")</span>
|
||||
<span class="ident">group</span> <span class="punct">=</span> <span class="constant">Group</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:name</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">Magic Models Forum</span><span class="punct">",</span> <span class="symbol">:description</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">http://groups.google.com/magicmodels</span><span class="punct">")</span>
|
||||
<span class="ident">membership</span> <span class="punct">=</span> <span class="constant">Membership</span><span class="punct">.</span><span class="ident">create</span><span class="punct">(</span><span class="symbol">:person</span> <span class="punct">=></span> <span class="ident">person</span><span class="punct">,</span> <span class="symbol">:group</span> <span class="punct">=></span> <span class="ident">group</span><span class="punct">)</span>
|
||||
<span class="ident">person</span><span class="punct">.</span><span class="ident">memberships</span><span class="punct">.</span><span class="ident">length</span>
|
||||
<span class="punct">=></span> <span class="number">1</span>
|
||||
<span class="ident">membership</span><span class="punct">.</span><span class="ident">person</span>
|
||||
<span class="punct">=></span> <span class="punct"><</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x38898e8</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">lastname</span><span class="punct">"=>"</span><span class="string">Williams</span><span class="punct">",</span> <span class="punct">"</span><span class="string">firstname</span><span class="punct">"=>"</span><span class="string">Nic</span><span class="punct">",</span>
|
||||
<span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">email</span><span class="punct">"=>"</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">"}></span>
|
||||
<span class="ident">group</span><span class="punct">.</span><span class="ident">memberships</span>
|
||||
<span class="punct">=></span> <span class="punct">[<</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x3c8cd70</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">group_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">person_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">"}>]</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>That final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations…</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="punct">>></span> <span class="ident">person</span><span class="punct">.</span><span class="ident">groups</span>
|
||||
<span class="punct">=></span> <span class="punct">[<</span><span class="constant">Group</span><span class="punct">:</span><span class="number">0x39047e0</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">name</span><span class="punct">"=>"</span><span class="string">Magic Models Forum</span><span class="punct">",</span> <span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">description</span><span class="punct">"=></span><span class="constant">nil</span><span class="punct">}>]</span>
|
||||
<span class="punct">>></span> <span class="ident">group</span><span class="punct">.</span><span class="ident">people</span>
|
||||
<span class="punct">=></span> <span class="punct">[<</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x3c33580</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">lastname</span><span class="punct">"=>"</span><span class="string">Williams</span><span class="punct">",</span> <span class="punct">"</span><span class="string">firstname</span><span class="punct">"=>"</span><span class="string">Nic</span><span class="punct">",</span>
|
||||
<span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">email</span><span class="punct">"=>"</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">"}>]</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<h3>Drum roll…</h3>
|
||||
|
||||
|
||||
<p>Ladies and gentlemen. For my final feat of magical mastery, I’ll ask you to do
|
||||
something you’ve never done before. This illusion is akin to the <a href="http://www.toytent.com/Posters/985.html">floating lady</a>
|
||||
illusion that has been passed down through generations of magicians.</p>
|
||||
|
||||
|
||||
<p>Exit your console session.</p>
|
||||
|
||||
|
||||
<span class="caps">DELETE</span> your three model classes: <code>person.rb, group.rb, and membership.rb</code> from the
|
||||
<code>app/models</code> folder. (You can always get them back via the model generator… be fearless!)
|
||||
|
||||
<pre>rm app/models/*.rb</pre>
|
||||
|
||||
<p>Re-launch your console.</p>
|
||||
|
||||
|
||||
<p><strong>drums are still rolling…</strong></p>
|
||||
|
||||
|
||||
<p>Be prepared to applaud loudly…</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="punct">>></span> <span class="constant">Person</span>
|
||||
<span class="punct">=></span> <span class="constant">Person</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p>You applaud loudly, but watch for more…</p>
|
||||
|
||||
|
||||
<p><pre class="syntax">
|
||||
<span class="punct">>></span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">new</span><span class="punct">.</span><span class="ident">valid?</span>
|
||||
<span class="punct">=></span> <span class="constant">false</span>
|
||||
<span class="punct">>></span> <span class="ident">person</span> <span class="punct">=</span> <span class="constant">Person</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="number">1</span><span class="punct">)</span>
|
||||
<span class="punct">=></span> <span class="punct"><</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x3958930</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">lastname</span><span class="punct">"=>"</span><span class="string">Williams</span><span class="punct">",</span> <span class="punct">"</span><span class="string">firstname</span><span class="punct">"=>"</span><span class="string">Nic</span><span class="punct">",</span>
|
||||
<span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">email</span><span class="punct">"=>"</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">"}></span>
|
||||
<span class="punct">>></span> <span class="ident">person</span><span class="punct">.</span><span class="ident">valid?</span>
|
||||
<span class="punct">=></span> <span class="constant">true</span>
|
||||
<span class="punct">>></span> <span class="ident">person</span><span class="punct">.</span><span class="ident">memberships</span>
|
||||
<span class="punct">=></span> <span class="punct">[<</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x393a000</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">group_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">person_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">"}>]</span>
|
||||
<span class="punct">>></span> <span class="ident">person</span><span class="punct">.</span><span class="ident">groups</span>
|
||||
<span class="punct">=></span> <span class="punct">[<</span><span class="constant">Group</span><span class="punct">:</span><span class="number">0x390df60</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">name</span><span class="punct">"=>"</span><span class="string">Magic Models Forum</span><span class="punct">",</span> <span class="punct">"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">description</span><span class="punct">"=></span><span class="constant">nil</span><span class="punct">}>]</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<h3>Tada!</h3>
|
||||
|
||||
|
||||
<p>The end.</p>
|
||||
|
||||
|
||||
<h3>Use modules to scope your magic</h3>
|
||||
|
||||
|
||||
<p>Only want to pick up tables starting with <code>blog_</code>?</p>
|
||||
|
||||
|
||||
<p><pre class="syntax"><span class="keyword">module </span><span class="module">Blog</span>
|
||||
<span class="ident">magic_module</span> <span class="symbol">:table_name_prefix</span> <span class="punct">=></span> <span class="punct">'</span><span class="string">blog_</span><span class="punct">'</span>
|
||||
<span class="keyword">end</span>
|
||||
|
||||
<span class="constant">Blog</span><span class="punct">::</span><span class="constant">Post</span><span class="punct">.</span><span class="ident">table_name</span> <span class="comment"># => 'blog_posts'</span>
|
||||
</pre></p>
|
||||
|
||||
|
||||
<h2>Dr Nic’s Blog</h2>
|
||||
|
||||
|
||||
<p><a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a> – for future announcements and
|
||||
other stories and things.</p>
|
||||
|
||||
|
||||
<h2>Articles about Magic Models</h2>
|
||||
|
||||
|
||||
<ul>
|
||||
<li><a href="http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/">Announcement</a></li>
|
||||
<li><a href="http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/"><span class="caps">BTS</span> – Class creation</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2>Forum</h2>
|
||||
|
||||
|
||||
<p><a href="http://groups.google.com/group/magicmodels">http://groups.google.com/group/magicmodels</a></p>
|
||||
|
||||
|
||||
<h2>Licence</h2>
|
||||
|
||||
|
||||
<p>This code is free to use under the terms of the <span class="caps">MIT</span> licence.</p>
|
||||
|
||||
|
||||
<h2>Contact</h2>
|
||||
|
||||
|
||||
<p>Comments are welcome. Send an email to <a href="mailto:drnicwilliams@gmail.com">Dr Nic Williams</a>
|
||||
or via his blog at <a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a></p>
|
||||
<p class="coda">
|
||||
<a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 30th April 2007<br>
|
||||
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
|
||||
</p>
|
||||
</div>
|
||||
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
_uacct = "UA-567811-3";
|
||||
urchinTracker();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,291 +0,0 @@
|
||||
h1. Dr Nic's Magic Models
|
||||
|
||||
If you've used Ruby on Rails you'll have written at least one model class like this:
|
||||
|
||||
<pre syntax='ruby'>
|
||||
class Person < ActiveRecord::Base
|
||||
has_many :memberships
|
||||
has_many :groups, :through => :memberships
|
||||
belongs_to :family
|
||||
validates_presence_of :firstname, :lastname, :email
|
||||
end
|
||||
</pre>
|
||||
|
||||
A few minutes later you'll have wondered to yourself,
|
||||
|
||||
<blockquote>
|
||||
Why do I have write my own <code>has_many</code>, <code>belongs_to</code>, and <code>validates_presence_of</code>
|
||||
commands if all the data is in the database schema?
|
||||
</blockquote>
|
||||
|
||||
Now, for the very first time, your classes can look like this:
|
||||
|
||||
<pre syntax='ruby'>
|
||||
class Person < ActiveRecord::Base
|
||||
end
|
||||
</pre>
|
||||
|
||||
or, if you are lazy...
|
||||
|
||||
<pre syntax='ruby'>
|
||||
class Person < ActiveRecord::Base; end
|
||||
</pre>
|
||||
|
||||
or, if you read right to the end of this page, this...
|
||||
|
||||
<pre syntax='ruby'>
|
||||
# Go fish.
|
||||
</pre>
|
||||
|
||||
Magic and mystery abound. All for you. Impress your friends, amaze your mother.
|
||||
|
||||
NOTE: The gratuitous use of *Dr Nic's* in the name should only enhance the mystical magikery,
|
||||
for magic needs a magician; and I love magic. I always wanted to create my own magic trick.
|
||||
So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too,
|
||||
if Harry were 32 and better dressed.
|
||||
|
||||
h2. Installation
|
||||
|
||||
To install the Dr Nic's Magic Models gem you can run the following command to
|
||||
fetch the gem remotely from RubyForge:
|
||||
<pre>
|
||||
gem install dr_nic_magic_models
|
||||
</pre>
|
||||
|
||||
or "download the gem manually":http://rubyforge.org/projects/magicmodels and
|
||||
run the above command in the download directory.
|
||||
|
||||
Now you need to <code>require</code> the gem into your Ruby/Rails app. Insert the following
|
||||
line into your script (use <code>config/environment.rb</code> for your Rails apps):
|
||||
|
||||
<pre>
|
||||
require 'dr_nic_magic_models'
|
||||
</pre>
|
||||
|
||||
Your application is now blessed with magical mystery.
|
||||
|
||||
h2. David Copperfield eat your Ruby-crusted heart out
|
||||
|
||||
Let's demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):
|
||||
|
||||
<pre syntax="ruby">
|
||||
rails magic_show -d sqlite3
|
||||
cd magic_show
|
||||
ruby script/generate model Person
|
||||
ruby script/generate model Group
|
||||
ruby script/generate model Membership
|
||||
</pre>
|
||||
|
||||
Update the migration <code>001_create_people.rb</code> with:
|
||||
<pre syntax="ruby">
|
||||
class CreatePeople < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :people do |t|
|
||||
t.column :firstname, :string, :null => false
|
||||
t.column :lastname, :string, :null => false
|
||||
t.column :email, :string, :null => false
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :people
|
||||
end
|
||||
end
|
||||
</pre>
|
||||
|
||||
Similarly, update the <code>def self.up</code> method of <code>002_create_groups.rb</code>
|
||||
with:
|
||||
<pre syntax="ruby">
|
||||
create_table :groups do |t|
|
||||
t.column :name, :string, :null => false
|
||||
t.column :description, :string
|
||||
end
|
||||
</pre>
|
||||
|
||||
and <code>003_create_memberships.rb</code> with:
|
||||
<pre syntax="ruby">
|
||||
create_table :memberships do |t|
|
||||
t.column :person_id, :integer, :null => false
|
||||
t.column :group_id, :integer, :null => false
|
||||
end
|
||||
</pre>
|
||||
|
||||
And run your migrations to create the three tables:
|
||||
<pre>
|
||||
rake db:migrate
|
||||
</pre>
|
||||
|
||||
h3. And now for some "woofle dust":http://en.wikipedia.org/wiki/List_of_conjuring_terms ...
|
||||
|
||||
At the end of <code>config/environment.rb</code> add the following line:
|
||||
|
||||
<pre>
|
||||
require 'dr_nic_magic_models'
|
||||
</pre>
|
||||
|
||||
Now, let's do a magic trick. First, let's check our model classes (<code>app/models/person.rb</code> etc):
|
||||
|
||||
<pre syntax="ruby">
|
||||
class Person < ActiveRecord::Base
|
||||
end
|
||||
class Group < ActiveRecord::Base
|
||||
end
|
||||
class Membership < ActiveRecord::Base
|
||||
end
|
||||
</pre>
|
||||
|
||||
Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.
|
||||
|
||||
UPDATE: To turn on magic validations, you now need to invoke <code>generate_validations</code> on defined classes. So, update your model classes:
|
||||
|
||||
<pre syntax="ruby">
|
||||
class Person < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
class Group < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
class Membership < ActiveRecord::Base
|
||||
generate_validations
|
||||
end
|
||||
</pre>
|
||||
|
||||
For this trick, we'll need an ordinary console session. Any old one lying around the house will do.
|
||||
|
||||
<pre>
|
||||
ruby script/console
|
||||
</pre>
|
||||
|
||||
Now a normal model class is valid until you explicitly add <code>validates_xxx</code> commands.
|
||||
With Dr Nic's Magic Models:
|
||||
|
||||
<pre syntax="ruby">
|
||||
person = Person.new
|
||||
=> #<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true>
|
||||
person.valid?
|
||||
=> false
|
||||
person.errors
|
||||
=> #<ActiveRecord::Errors:0x3537b38 @errors={
|
||||
"firstname"=>["can't be blank", "is too long (maximum is 255 characters)"],
|
||||
"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"],
|
||||
"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
|
||||
@base=#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true,
|
||||
@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
|
||||
</pre>
|
||||
|
||||
*Kapoow!* Instant validation! (NOTE: not as instant as it used to be - remember - you need to call <code>generate_validations</code> on each class as required)
|
||||
|
||||
Because you specified the three columns as <code>:null => false</code>,
|
||||
your ActiveRecord models will now automagically generated <code>validates_presence_of</code>
|
||||
for each non-null field, plus several other validations (since version 0.8.0).
|
||||
|
||||
Ok, we're just warming up.
|
||||
|
||||
Your models normally require association commands (<code>has_many</code>, <code>belongs_to</code>, etc, as
|
||||
demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.
|
||||
|
||||
Let's just watch what Dr Nic's Magic Models can do without any effort at all...
|
||||
|
||||
<pre syntax="ruby">
|
||||
person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
|
||||
group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
|
||||
membership = Membership.create(:person => person, :group => group)
|
||||
person.memberships.length
|
||||
=> 1
|
||||
membership.person
|
||||
=> <Person:0x38898e8 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
|
||||
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
|
||||
group.memberships
|
||||
=> [<Membership:0x3c8cd70 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
|
||||
</pre>
|
||||
|
||||
|
||||
That final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations...
|
||||
|
||||
<pre syntax="ruby">
|
||||
>> person.groups
|
||||
=> [<Group:0x39047e0 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
|
||||
>> group.people
|
||||
=> [<Person:0x3c33580 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
|
||||
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
|
||||
</pre>
|
||||
|
||||
h3. Drum roll...
|
||||
|
||||
Ladies and gentlemen. For my final feat of magical mastery, I'll ask you to do
|
||||
something you've never done before. This illusion is akin to the "floating lady":http://www.toytent.com/Posters/985.html
|
||||
illusion that has been passed down through generations of magicians.
|
||||
|
||||
Exit your console session.
|
||||
|
||||
DELETE your three model classes: <code>person.rb, group.rb, and membership.rb</code> from the
|
||||
<code>app/models</code> folder. (You can always get them back via the model generator... be fearless!)
|
||||
|
||||
<pre>rm app/models/*.rb</pre>
|
||||
|
||||
Re-launch your console.
|
||||
|
||||
*drums are still rolling...*
|
||||
|
||||
Be prepared to applaud loudly...
|
||||
|
||||
<pre syntax="ruby">
|
||||
>> Person
|
||||
=> Person
|
||||
</pre>
|
||||
|
||||
You applaud loudly, but watch for more...
|
||||
|
||||
<pre syntax="ruby">
|
||||
>> Person.new.valid?
|
||||
=> false
|
||||
>> person = Person.find(1)
|
||||
=> <Person:0x3958930 @attributes={"lastname"=>"Williams", "firstname"=>"Nic",
|
||||
"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
|
||||
>> person.valid?
|
||||
=> true
|
||||
>> person.memberships
|
||||
=> [<Membership:0x393a000 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
|
||||
>> person.groups
|
||||
=> [<Group:0x390df60 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
|
||||
</pre>
|
||||
|
||||
h3. Tada!
|
||||
|
||||
The end.
|
||||
|
||||
h3. Use modules to scope your magic
|
||||
|
||||
Only want to pick up tables starting with <code>blog_</code>?
|
||||
|
||||
<pre syntax="ruby">module Blog
|
||||
magic_module :table_name_prefix => 'blog_'
|
||||
end
|
||||
|
||||
Blog::Post.table_name # => 'blog_posts'
|
||||
</pre>
|
||||
|
||||
h2. Dr Nic's Blog
|
||||
|
||||
"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and
|
||||
other stories and things.
|
||||
|
||||
h2. Articles about Magic Models
|
||||
|
||||
* "Announcement":http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/
|
||||
* "BTS - Class creation":http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/
|
||||
|
||||
|
||||
h2. Forum
|
||||
|
||||
"http://groups.google.com/group/magicmodels":http://groups.google.com/group/magicmodels
|
||||
|
||||
h2. Licence
|
||||
|
||||
This code is free to use under the terms of the MIT licence.
|
||||
|
||||
h2. Contact
|
||||
|
||||
Comments are welcome. Send an email to "Dr Nic Williams":mailto:drnicwilliams@gmail.com
|
||||
or via his blog at "http://www.drnicwilliams.com":http://www.drnicwilliams.com
|
||||
|
@ -1,285 +0,0 @@
|
||||
|
||||
/****************************************************************
|
||||
* *
|
||||
* curvyCorners *
|
||||
* ------------ *
|
||||
* *
|
||||
* This script generates rounded corners for your divs. *
|
||||
* *
|
||||
* Version 1.2.9 *
|
||||
* Copyright (c) 2006 Cameron Cooke *
|
||||
* By: Cameron Cooke and Tim Hutchison. *
|
||||
* *
|
||||
* *
|
||||
* Website: http://www.curvycorners.net *
|
||||
* Email: info@totalinfinity.com *
|
||||
* Forum: http://www.curvycorners.net/forum/ *
|
||||
* *
|
||||
* *
|
||||
* This library is free software; you can redistribute *
|
||||
* it and/or modify it under the terms of the GNU *
|
||||
* Lesser General Public License as published by the *
|
||||
* Free Software Foundation; either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will *
|
||||
* be useful, but WITHOUT ANY WARRANTY; without even the *
|
||||
* implied warranty of MERCHANTABILITY or FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public *
|
||||
* License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser *
|
||||
* General Public License along with this library; *
|
||||
* Inc., 59 Temple Place, Suite 330, Boston, *
|
||||
* MA 02111-1307 USA *
|
||||
* *
|
||||
****************************************************************/
|
||||
|
||||
var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners()
|
||||
{ if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string")
|
||||
{ var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);}
|
||||
else
|
||||
{ var startIndex = 1; var boxCol = arguments;}
|
||||
var curvyCornersCol = new Array(); if(arguments[0].validTags)
|
||||
var validElements = arguments[0].validTags; else
|
||||
var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++)
|
||||
{ var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false)
|
||||
{ curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);}
|
||||
}
|
||||
this.objects = curvyCornersCol; this.applyCornersToAll = function()
|
||||
{ for(var x = 0, k = this.objects.length; x < k; x++)
|
||||
{ this.objects[x].applyCorners();}
|
||||
}
|
||||
}
|
||||
function curvyObject()
|
||||
{ this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0)
|
||||
this.box.innerHTML = ""; this.applyCorners = function()
|
||||
{ for(var t = 0; t < 2; t++)
|
||||
{ switch(t)
|
||||
{ case 0:
|
||||
if(this.settings.tl || this.settings.tr)
|
||||
{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);}
|
||||
break; case 1:
|
||||
if(this.settings.bl || this.settings.br)
|
||||
{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);}
|
||||
break;}
|
||||
}
|
||||
if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners)
|
||||
{ if(i > -1 < 4)
|
||||
{ var cc = corners[i]; if(!this.settings[cc])
|
||||
{ if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null))
|
||||
{ var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "")
|
||||
newCorner.style.backgroundColor = this.boxColour; else
|
||||
newCorner.style.backgroundImage = this.backgroundImage; switch(cc)
|
||||
{ case "tl":
|
||||
newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr":
|
||||
newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl":
|
||||
newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br":
|
||||
newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px"
|
||||
newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ if(this.masterCorners[this.settings[cc].radius])
|
||||
{ var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);}
|
||||
else
|
||||
{ var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++)
|
||||
{ if((intx +1) >= borderRadius)
|
||||
var y1 = -1; else
|
||||
var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j)
|
||||
{ if((intx) >= borderRadius)
|
||||
var y2 = -1; else
|
||||
var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j)
|
||||
var y3 = -1; else
|
||||
var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);}
|
||||
if((intx) >= j)
|
||||
var y4 = -1; else
|
||||
var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j)
|
||||
{ for(var inty = (y1 + 1); inty < y2; inty++)
|
||||
{ if(this.settings.antiAlias)
|
||||
{ if(this.backgroundImage != "")
|
||||
{ var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30)
|
||||
{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);}
|
||||
else
|
||||
{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);}
|
||||
}
|
||||
else
|
||||
{ var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);}
|
||||
}
|
||||
}
|
||||
if(this.settings.antiAlias)
|
||||
{ if(y3 >= y2)
|
||||
{ if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);}
|
||||
}
|
||||
else
|
||||
{ if(y3 >= y1)
|
||||
{ this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);}
|
||||
}
|
||||
var outsideColour = this.borderColour;}
|
||||
else
|
||||
{ var outsideColour = this.boxColour; var y3 = y1;}
|
||||
if(this.settings.antiAlias)
|
||||
{ for(var inty = (y3 + 1); inty < y4; inty++)
|
||||
{ this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);}
|
||||
}
|
||||
}
|
||||
this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);}
|
||||
if(cc != "br")
|
||||
{ for(var t = 0, k = newCorner.childNodes.length; t < k; t++)
|
||||
{ var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";}
|
||||
if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";}
|
||||
switch(cc)
|
||||
{ case "tr":
|
||||
pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl":
|
||||
pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl":
|
||||
pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(newCorner)
|
||||
{ switch(cc)
|
||||
{ case "tl":
|
||||
if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr":
|
||||
if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl":
|
||||
if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br":
|
||||
if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;}
|
||||
}
|
||||
}
|
||||
}
|
||||
var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius)
|
||||
radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff)
|
||||
{ if(z == "t" || z == "b")
|
||||
{ if(radiusDiff[z])
|
||||
{ var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px"
|
||||
newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType)
|
||||
{ case "tl":
|
||||
newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr":
|
||||
newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl":
|
||||
newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br":
|
||||
newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;}
|
||||
}
|
||||
var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z)
|
||||
{ case "t":
|
||||
if(this.topContainer)
|
||||
{ if(this.settings.tl.radius && this.settings.tr.radius)
|
||||
{ newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "")
|
||||
newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);}
|
||||
this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";}
|
||||
break; case "b":
|
||||
if(this.bottomContainer)
|
||||
{ if(this.settings.bl.radius && this.settings.br.radius)
|
||||
{ newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "")
|
||||
newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);}
|
||||
}
|
||||
break;}
|
||||
}
|
||||
}
|
||||
if(this.settings.autoPad == true && this.boxPadding > 0)
|
||||
{ var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding)
|
||||
contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding)
|
||||
contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);}
|
||||
}
|
||||
this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius)
|
||||
{ var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "")
|
||||
{ pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";}
|
||||
else
|
||||
{ pixel.style.backgroundColor = colour;}
|
||||
if (transAmount != 100)
|
||||
setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);}
|
||||
}
|
||||
function insertAfter(parent, node, referenceNode)
|
||||
{ parent.insertBefore(node, referenceNode.nextSibling);}
|
||||
function BlendColour(Col1, Col2, Col1Fraction)
|
||||
{ var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);}
|
||||
function IntToHex(strNum)
|
||||
{ base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;}
|
||||
function MakeHex(x)
|
||||
{ if((x >= 0) && (x <= 9))
|
||||
{ return x;}
|
||||
else
|
||||
{ switch(x)
|
||||
{ case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";}
|
||||
}
|
||||
}
|
||||
function pixelFraction(x, y, r)
|
||||
{ var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1)))
|
||||
{ whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;}
|
||||
var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1)))
|
||||
{ whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;}
|
||||
var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1)))
|
||||
{ whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;}
|
||||
var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1)))
|
||||
{ whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;}
|
||||
switch (whatsides)
|
||||
{ case "LeftRight":
|
||||
pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight":
|
||||
pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom":
|
||||
pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom":
|
||||
pixelfraction = (yvalues[0]*xvalues[1])/2; break; default:
|
||||
pixelfraction = 1;}
|
||||
return pixelfraction;}
|
||||
function rgb2Hex(rgbColour)
|
||||
{ try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);}
|
||||
catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");}
|
||||
return hexColour;}
|
||||
function rgb2Array(rgbColour)
|
||||
{ var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;}
|
||||
function setOpacity(obj, opacity)
|
||||
{ opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME")
|
||||
{ var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";}
|
||||
else if(typeof(obj.style.opacity) != "undefined")
|
||||
{ obj.style.opacity = opacity/100;}
|
||||
else if(typeof(obj.style.MozOpacity) != "undefined")
|
||||
{ obj.style.MozOpacity = opacity/100;}
|
||||
else if(typeof(obj.style.filter) != "undefined")
|
||||
{ obj.style.filter = "alpha(opacity:" + opacity + ")";}
|
||||
else if(typeof(obj.style.KHTMLOpacity) != "undefined")
|
||||
{ obj.style.KHTMLOpacity = opacity/100;}
|
||||
}
|
||||
function inArray(array, value)
|
||||
{ for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;}
|
||||
return false;}
|
||||
function inArrayKey(array, value)
|
||||
{ for(key in array){ if(key === value) return true;}
|
||||
return false;}
|
||||
function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;}
|
||||
else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;}
|
||||
else { elm['on' + evType] = fn;}
|
||||
}
|
||||
function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");}
|
||||
}
|
||||
function format_colour(colour)
|
||||
{ var returnColour = "#ffffff"; if(colour != "" && colour != "transparent")
|
||||
{ if(colour.substr(0, 3) == "rgb")
|
||||
{ returnColour = rgb2Hex(colour);}
|
||||
else if(colour.length == 4)
|
||||
{ returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);}
|
||||
else
|
||||
{ returnColour = colour;}
|
||||
}
|
||||
return returnColour;}
|
||||
function get_style(obj, property, propertyNS)
|
||||
{ try
|
||||
{ if(obj.currentStyle)
|
||||
{ var returnVal = eval("obj.currentStyle." + property);}
|
||||
else
|
||||
{ if(isSafari && obj.style.display == "none")
|
||||
{ obj.style.display = ""; var wasHidden = true;}
|
||||
var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden)
|
||||
{ obj.style.display = "none";}
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{ }
|
||||
return returnVal;}
|
||||
function getElementsByClass(searchClass, node, tag)
|
||||
{ var classElements = new Array(); if(node == null)
|
||||
node = document; if(tag == null)
|
||||
tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++)
|
||||
{ if(pattern.test(els[i].className))
|
||||
{ classElements[j] = els[i]; j++;}
|
||||
}
|
||||
return classElements;}
|
||||
function newCurvyError(errorMessage)
|
||||
{ return new Error("curvyCorners Error:\n" + errorMessage)
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
body {
|
||||
background-color: #115;
|
||||
font-family: "Georgia", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6em;
|
||||
padding: 1.6em 0 0 0;
|
||||
color: #eee;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #77f;
|
||||
}
|
||||
h1 {
|
||||
font-family: sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 4em;
|
||||
line-height: 0.8em;
|
||||
letter-spacing: -0.1ex;
|
||||
}
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: square;
|
||||
}
|
||||
a {
|
||||
color: #99f;
|
||||
font-weight: normal;
|
||||
text-decoration: underline;
|
||||
}
|
||||
blockquote {
|
||||
font-size: 90%;
|
||||
font-style: italic;
|
||||
border-left: 1px solid #eee;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.caps {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
#main {
|
||||
width: 55em;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.coda {
|
||||
text-align: right;
|
||||
color: #77f;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font-family: monospace;
|
||||
font-size: 90%;
|
||||
line-height: 1.4em;
|
||||
color: #ff8;
|
||||
background-color: #111;
|
||||
padding: 2px 10px 2px 10px;
|
||||
}
|
||||
.comment { color: #aaa; font-style: italic; }
|
||||
.keyword { color: #eff; font-weight: bold; }
|
||||
.punct { color: #eee; font-weight: bold; }
|
||||
.symbol { color: #0bb; }
|
||||
.string { color: #6b4; }
|
||||
.ident { color: #ff8; }
|
||||
.constant { color: #66f; }
|
||||
.regex { color: #ec6; }
|
||||
.number { color: #F99; }
|
||||
.expr { color: #227; }
|
||||
|
||||
#version {
|
||||
float: right;
|
||||
text-align: right;
|
||||
font-family: sans-serif;
|
||||
font-weight: normal;
|
||||
background-color: #ff8;
|
||||
color: #66f;
|
||||
padding: 15px 20px 10px 20px;
|
||||
margin: 0 auto;
|
||||
border: 3px solid #66f;
|
||||
}
|
||||
|
||||
#version .numbers {
|
||||
display: block;
|
||||
font-size: 4em;
|
||||
line-height: 0.8em;
|
||||
letter-spacing: -0.1ex;
|
||||
}
|
||||
|
||||
#version a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
// <%= title %>
|
||||
var version = <%= version.to_json %>;
|
||||
<%= body %>
|
@ -1,55 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>
|
||||
<%= title %>
|
||||
</title>
|
||||
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<script type="text/javascript" src="version-raw.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
settings = {
|
||||
tl: { radius: 10 },
|
||||
tr: { radius: 10 },
|
||||
bl: { radius: 10 },
|
||||
br: { radius: 10 },
|
||||
antiAlias: true,
|
||||
autoPad: true,
|
||||
validTags: ["div"]
|
||||
}
|
||||
var versionBox = new curvyCorners(settings, document.getElementById("version"));
|
||||
versionBox.applyCornersToAll();
|
||||
|
||||
document.getElementById("version_num").innerHTML = version;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<p><a href="/">↩ More Magic</a></p>
|
||||
|
||||
<div id="version" class="clickable" onclick='document.location = "<%= download %>"; return false'>
|
||||
Get Version
|
||||
<a id="version_num" href="<%= download %>" class="numbers"></a>
|
||||
</div>
|
||||
<h1><%= title %></h1>
|
||||
<%= body %>
|
||||
<p class="coda">
|
||||
<a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, <%= modified.pretty %><br>
|
||||
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
|
||||
</p>
|
||||
</div>
|
||||
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
_uacct = "UA-567811-3";
|
||||
urchinTracker();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,3 +0,0 @@
|
||||
// Version JS file
|
||||
var version = "0.9.2";
|
||||
MagicAnnouncement.show('magicmodels', version);
|
@ -1,2 +0,0 @@
|
||||
h1. Version JS file
|
||||
MagicAnnouncement.show('magicmodels', version);
|
@ -1,4 +0,0 @@
|
||||
// Version JS file
|
||||
var version = "0.9.2";
|
||||
|
||||
document.write(" - " + version);
|
@ -1,3 +0,0 @@
|
||||
h1. Version JS file
|
||||
|
||||
document.write(" - " + version);
|
@ -1,258 +0,0 @@
|
||||
--- !ruby/object:Gem::Specification
|
||||
name: thoughtbot-shoulda
|
||||
version: !ruby/object:Gem::Version
|
||||
version: 2.10.1
|
||||
platform: ruby
|
||||
authors:
|
||||
- Tammer Saleh
|
||||
autorequire:
|
||||
bindir: bin
|
||||
cert_chain: []
|
||||
|
||||
date: 2009-03-05 00:00:00 -03:00
|
||||
default_executable: convert_to_should_syntax
|
||||
dependencies: []
|
||||
|
||||
description:
|
||||
email: tsaleh@thoughtbot.com
|
||||
executables:
|
||||
- convert_to_should_syntax
|
||||
extensions: []
|
||||
|
||||
extra_rdoc_files:
|
||||
- README.rdoc
|
||||
- CONTRIBUTION_GUIDELINES.rdoc
|
||||
files:
|
||||
- CONTRIBUTION_GUIDELINES.rdoc
|
||||
- MIT-LICENSE
|
||||
- Rakefile
|
||||
- README.rdoc
|
||||
- bin/convert_to_should_syntax
|
||||
- lib/shoulda
|
||||
- lib/shoulda/action_controller
|
||||
- lib/shoulda/action_controller/helpers.rb
|
||||
- lib/shoulda/action_controller/macros.rb
|
||||
- lib/shoulda/action_controller/matchers
|
||||
- lib/shoulda/action_controller/matchers/assign_to_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/filter_param_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/respond_with_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/route_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/set_session_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb
|
||||
- lib/shoulda/action_controller/matchers.rb
|
||||
- lib/shoulda/action_controller.rb
|
||||
- lib/shoulda/action_mailer
|
||||
- lib/shoulda/action_mailer/assertions.rb
|
||||
- lib/shoulda/action_mailer.rb
|
||||
- lib/shoulda/action_view
|
||||
- lib/shoulda/action_view/macros.rb
|
||||
- lib/shoulda/action_view.rb
|
||||
- lib/shoulda/active_record
|
||||
- lib/shoulda/active_record/assertions.rb
|
||||
- lib/shoulda/active_record/helpers.rb
|
||||
- lib/shoulda/active_record/macros.rb
|
||||
- lib/shoulda/active_record/matchers
|
||||
- lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/allow_value_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/association_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/have_db_column_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/have_index_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/have_named_scope_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb
|
||||
- lib/shoulda/active_record/matchers/validation_matcher.rb
|
||||
- lib/shoulda/active_record/matchers.rb
|
||||
- lib/shoulda/active_record.rb
|
||||
- lib/shoulda/assertions.rb
|
||||
- lib/shoulda/autoload_macros.rb
|
||||
- lib/shoulda/context.rb
|
||||
- lib/shoulda/helpers.rb
|
||||
- lib/shoulda/macros.rb
|
||||
- lib/shoulda/private_helpers.rb
|
||||
- lib/shoulda/proc_extensions.rb
|
||||
- lib/shoulda/rails.rb
|
||||
- lib/shoulda/rspec.rb
|
||||
- lib/shoulda/tasks
|
||||
- lib/shoulda/tasks/list_tests.rake
|
||||
- lib/shoulda/tasks/yaml_to_shoulda.rake
|
||||
- lib/shoulda/tasks.rb
|
||||
- lib/shoulda/test_unit.rb
|
||||
- lib/shoulda.rb
|
||||
- rails/init.rb
|
||||
- test/fail_macros.rb
|
||||
- test/fixtures
|
||||
- test/fixtures/addresses.yml
|
||||
- test/fixtures/friendships.yml
|
||||
- test/fixtures/posts.yml
|
||||
- test/fixtures/products.yml
|
||||
- test/fixtures/taggings.yml
|
||||
- test/fixtures/tags.yml
|
||||
- test/fixtures/users.yml
|
||||
- test/functional
|
||||
- test/functional/posts_controller_test.rb
|
||||
- test/functional/users_controller_test.rb
|
||||
- test/matchers
|
||||
- test/matchers/active_record
|
||||
- test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb
|
||||
- test/matchers/active_record/allow_value_matcher_test.rb
|
||||
- test/matchers/active_record/association_matcher_test.rb
|
||||
- test/matchers/active_record/ensure_inclusion_of_matcher_test.rb
|
||||
- test/matchers/active_record/ensure_length_of_matcher_test.rb
|
||||
- test/matchers/active_record/have_db_column_matcher_test.rb
|
||||
- test/matchers/active_record/have_index_matcher_test.rb
|
||||
- test/matchers/active_record/have_named_scope_matcher_test.rb
|
||||
- test/matchers/active_record/have_readonly_attributes_matcher_test.rb
|
||||
- test/matchers/active_record/validate_acceptance_of_matcher_test.rb
|
||||
- test/matchers/active_record/validate_numericality_of_matcher_test.rb
|
||||
- test/matchers/active_record/validate_presence_of_matcher_test.rb
|
||||
- test/matchers/active_record/validate_uniqueness_of_matcher_test.rb
|
||||
- test/matchers/controller
|
||||
- test/matchers/controller/assign_to_matcher_test.rb
|
||||
- test/matchers/controller/filter_param_matcher_test.rb
|
||||
- test/matchers/controller/render_with_layout_matcher_test.rb
|
||||
- test/matchers/controller/respond_with_content_type_matcher_test.rb
|
||||
- test/matchers/controller/respond_with_matcher_test.rb
|
||||
- test/matchers/controller/route_matcher_test.rb
|
||||
- test/matchers/controller/set_session_matcher_test.rb
|
||||
- test/matchers/controller/set_the_flash_matcher.rb
|
||||
- test/model_builder.rb
|
||||
- test/other
|
||||
- test/other/autoload_macro_test.rb
|
||||
- test/other/context_test.rb
|
||||
- test/other/convert_to_should_syntax_test.rb
|
||||
- test/other/helpers_test.rb
|
||||
- test/other/private_helpers_test.rb
|
||||
- test/other/should_test.rb
|
||||
- test/rails_root
|
||||
- test/rails_root/app
|
||||
- test/rails_root/app/controllers
|
||||
- test/rails_root/app/controllers/application.rb
|
||||
- test/rails_root/app/controllers/posts_controller.rb
|
||||
- test/rails_root/app/controllers/users_controller.rb
|
||||
- test/rails_root/app/helpers
|
||||
- test/rails_root/app/helpers/application_helper.rb
|
||||
- test/rails_root/app/helpers/posts_helper.rb
|
||||
- test/rails_root/app/helpers/users_helper.rb
|
||||
- test/rails_root/app/models
|
||||
- test/rails_root/app/models/address.rb
|
||||
- test/rails_root/app/models/flea.rb
|
||||
- test/rails_root/app/models/friendship.rb
|
||||
- test/rails_root/app/models/pets
|
||||
- test/rails_root/app/models/pets/dog.rb
|
||||
- test/rails_root/app/models/post.rb
|
||||
- test/rails_root/app/models/product.rb
|
||||
- test/rails_root/app/models/tag.rb
|
||||
- test/rails_root/app/models/tagging.rb
|
||||
- test/rails_root/app/models/treat.rb
|
||||
- test/rails_root/app/models/user.rb
|
||||
- test/rails_root/app/views
|
||||
- test/rails_root/app/views/layouts
|
||||
- test/rails_root/app/views/layouts/posts.rhtml
|
||||
- test/rails_root/app/views/layouts/users.rhtml
|
||||
- test/rails_root/app/views/layouts/wide.html.erb
|
||||
- test/rails_root/app/views/posts
|
||||
- test/rails_root/app/views/posts/edit.rhtml
|
||||
- test/rails_root/app/views/posts/index.rhtml
|
||||
- test/rails_root/app/views/posts/new.rhtml
|
||||
- test/rails_root/app/views/posts/show.rhtml
|
||||
- test/rails_root/app/views/users
|
||||
- test/rails_root/app/views/users/edit.rhtml
|
||||
- test/rails_root/app/views/users/index.rhtml
|
||||
- test/rails_root/app/views/users/new.rhtml
|
||||
- test/rails_root/app/views/users/show.rhtml
|
||||
- test/rails_root/config
|
||||
- test/rails_root/config/boot.rb
|
||||
- test/rails_root/config/database.yml
|
||||
- test/rails_root/config/environment.rb
|
||||
- test/rails_root/config/environments
|
||||
- test/rails_root/config/environments/test.rb
|
||||
- test/rails_root/config/initializers
|
||||
- test/rails_root/config/initializers/new_rails_defaults.rb
|
||||
- test/rails_root/config/initializers/shoulda.rb
|
||||
- test/rails_root/config/routes.rb
|
||||
- test/rails_root/db
|
||||
- test/rails_root/db/migrate
|
||||
- test/rails_root/db/migrate/001_create_users.rb
|
||||
- test/rails_root/db/migrate/002_create_posts.rb
|
||||
- test/rails_root/db/migrate/003_create_taggings.rb
|
||||
- test/rails_root/db/migrate/004_create_tags.rb
|
||||
- test/rails_root/db/migrate/005_create_dogs.rb
|
||||
- test/rails_root/db/migrate/006_create_addresses.rb
|
||||
- test/rails_root/db/migrate/007_create_fleas.rb
|
||||
- test/rails_root/db/migrate/008_create_dogs_fleas.rb
|
||||
- test/rails_root/db/migrate/009_create_products.rb
|
||||
- test/rails_root/db/migrate/010_create_friendships.rb
|
||||
- test/rails_root/db/migrate/011_create_treats.rb
|
||||
- test/rails_root/db/schema.rb
|
||||
- test/rails_root/log
|
||||
- test/rails_root/log/sqlite3.log
|
||||
- test/rails_root/log/test.log
|
||||
- test/rails_root/public
|
||||
- test/rails_root/public/404.html
|
||||
- test/rails_root/public/422.html
|
||||
- test/rails_root/public/500.html
|
||||
- test/rails_root/script
|
||||
- test/rails_root/script/console
|
||||
- test/rails_root/script/generate
|
||||
- test/rails_root/test
|
||||
- test/rails_root/test/shoulda_macros
|
||||
- test/rails_root/test/shoulda_macros/custom_macro.rb
|
||||
- test/rails_root/vendor
|
||||
- test/rails_root/vendor/gems
|
||||
- test/rails_root/vendor/gems/gem_with_macro-0.0.1
|
||||
- test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros
|
||||
- test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb
|
||||
- test/rails_root/vendor/plugins
|
||||
- test/rails_root/vendor/plugins/plugin_with_macro
|
||||
- test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros
|
||||
- test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb
|
||||
- test/README
|
||||
- test/rspec_test.rb
|
||||
- test/test_helper.rb
|
||||
- test/unit
|
||||
- test/unit/address_test.rb
|
||||
- test/unit/dog_test.rb
|
||||
- test/unit/flea_test.rb
|
||||
- test/unit/friendship_test.rb
|
||||
- test/unit/post_test.rb
|
||||
- test/unit/product_test.rb
|
||||
- test/unit/tag_test.rb
|
||||
- test/unit/tagging_test.rb
|
||||
- test/unit/user_test.rb
|
||||
has_rdoc: true
|
||||
homepage: http://thoughtbot.com/projects/shoulda
|
||||
post_install_message:
|
||||
rdoc_options:
|
||||
- --line-numbers
|
||||
- --main
|
||||
- README.rdoc
|
||||
require_paths:
|
||||
- lib
|
||||
required_ruby_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: "0"
|
||||
version:
|
||||
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: "0"
|
||||
version:
|
||||
requirements: []
|
||||
|
||||
rubyforge_project: shoulda
|
||||
rubygems_version: 1.3.1
|
||||
signing_key:
|
||||
specification_version: 2
|
||||
summary: Making tests easy on the fingers and eyes
|
||||
test_files: []
|
||||
|
@ -1,12 +0,0 @@
|
||||
We're using GitHub[http://github.com/thoughtbot/shoulda/tree/master] and Lighthouse[http://thoughtbot.lighthouseapp.com/projects/5807], and we've been getting any combination of github pull requests, Lighthouse tickets, patches, emails, etc. We need to normalize this workflow to make sure we don't miss any fixes.
|
||||
|
||||
* Make sure you're accessing the source from the {official repository}[http://github.com/thoughtbot/shoulda/tree/master].
|
||||
* We prefer git branches over patches, but we can take either.
|
||||
* If you're using git, please make a branch for each separate contribution. We can cherry pick your commits, but pulling from a branch is easier.
|
||||
* If you're submitting patches, please cut each fix or feature into a separate patch.
|
||||
* There should be a Lighthouse[http://thoughtbot.lighthouseapp.com/projects/5807] ticket for any submission. If you've found a bug and want to fix it, open a new ticket at the same time.
|
||||
* We've got github/lighthouse integration going, so you can update tickets when you commit. {This blog post}[http://hoth.entp.com/2008/4/11/github-and-lighthouse-sitting-in-a-tree] explains the commit options pretty well.
|
||||
* Please <b>don't send pull requests</b> Just update the lighthouse ticket with the url for your fix (or attach the patch) when it's ready. The github pull requests pretty much get dropped on the floor until someone with commit rights notices them in the mailbox.
|
||||
* Contributions without tests won't be accepted. The file <tt>/test/README</tt> explains the testing system pretty thoroughly.
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc.
|
||||
|
||||
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.
|
@ -1,169 +0,0 @@
|
||||
= Shoulda - Making tests easy on the fingers and eyes
|
||||
|
||||
Shoulda makes it easy to write elegant, understandable, and maintainable tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework. It's fully compatible with your existing tests, and requires no retooling to use.
|
||||
|
||||
Helpers:: #context and #should give you RSpec like test blocks.
|
||||
In addition, you get nested contexts and a much more readable syntax.
|
||||
Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros.
|
||||
They get you started quickly, and can help you ensure that your application is conforming to best practices.
|
||||
Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions.
|
||||
Matchers:: Rspec-compatible matchers providing the same tests as Shoulda macros.
|
||||
|
||||
= Usage
|
||||
|
||||
=== Context Helpers (Shoulda::Context)
|
||||
|
||||
Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
|
||||
|
||||
class UserTest < Test::Unit::TestCase
|
||||
context "A User instance" do
|
||||
setup do
|
||||
@user = User.find(:first)
|
||||
end
|
||||
|
||||
should "return its full name" do
|
||||
assert_equal 'John Doe', @user.full_name
|
||||
end
|
||||
|
||||
context "with a profile" do
|
||||
setup do
|
||||
@user.profile = Profile.find(:first)
|
||||
end
|
||||
|
||||
should "return true when sent #has_profile?" do
|
||||
assert @user.has_profile?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Produces the following test methods:
|
||||
|
||||
"test: A User instance should return its full name."
|
||||
"test: A User instance with a profile should return true when sent #has_profile?."
|
||||
|
||||
So readable!
|
||||
|
||||
=== ActiveRecord Tests (Shoulda::ActiveRecord::Macros)
|
||||
|
||||
Quick macro tests for your ActiveRecord associations and validations:
|
||||
|
||||
class PostTest < Test::Unit::TestCase
|
||||
fixtures :all
|
||||
|
||||
should_belong_to :user
|
||||
should_have_many :tags, :through => :taggings
|
||||
|
||||
should_validate_uniqueness_of :title
|
||||
should_validate_presence_of :body, :message => /wtf/
|
||||
should_validate_presence_of :title
|
||||
should_validate_numericality_of :user_id
|
||||
end
|
||||
|
||||
class UserTest < Test::Unit::TestCase
|
||||
should_have_many :posts
|
||||
|
||||
should_not_allow_values_for :email, "blah", "b lah"
|
||||
should_allow_values_for :email, "a@b.com", "asdf@asdf.com"
|
||||
should_ensure_length_in_range :email, 1..100
|
||||
should_ensure_value_in_range :age, 1..100
|
||||
should_not_allow_mass_assignment_of :password
|
||||
end
|
||||
|
||||
Makes TDD so much easier.
|
||||
|
||||
=== Controller Tests (Shoulda::Controller::Macros)
|
||||
|
||||
Macros to test the most common controller patterns...
|
||||
|
||||
context "on GET to :show for first record" do
|
||||
setup do
|
||||
get :show, :id => 1
|
||||
end
|
||||
|
||||
should_assign_to :user
|
||||
should_respond_with :success
|
||||
should_render_template :show
|
||||
should_not_set_the_flash
|
||||
|
||||
should "do something else really cool" do
|
||||
assert_equal 1, assigns(:user).id
|
||||
end
|
||||
end
|
||||
|
||||
=== Helpful Assertions (Shoulda::Assertions)
|
||||
|
||||
More to come here, but have fun with what's there.
|
||||
|
||||
assert_same_elements([:a, :b, :c], [:c, :a, :b])
|
||||
assert_contains(['a', '1'], /\d/)
|
||||
assert_contains(['a', '1'], 'a')
|
||||
|
||||
=== 3rd Party and Application Specific Macros
|
||||
|
||||
Any *.rb file under RAILS_ROOT/test/shoulda_macros/ or vendor/(plugins|gems)/gem_name/shoulda_macros/ will be automatically required when you run your tests. This allows you to distribute macros with your plugins, or to organize the macros inside your application. Remember to add your macro to Test::Unit::TestCase in the macro file:
|
||||
|
||||
# test/shoulda_macros/security.rb
|
||||
class Test::Unit::TestCase
|
||||
def self.should_be_denied(opts = {})
|
||||
should_set_the_flash_to(opts[:flash] || /Please log in/i)
|
||||
should_redirect_to(opts[:redirect] || 'login_url')
|
||||
end
|
||||
end
|
||||
|
||||
= Rails Installation (Test::Unit)
|
||||
|
||||
=== As a Gem
|
||||
|
||||
Use this if you prefer to use versioned releases of shoulda. Specify the gem dependency in your config/environment.rb file:
|
||||
|
||||
Rails::Initializer.run do |config|
|
||||
config.gem "thoughtbot-shoulda", :lib => "shoulda", :source => "http://gems.github.com"
|
||||
end
|
||||
|
||||
Then:
|
||||
|
||||
$ rake gems:install
|
||||
$ rake gems:unpack
|
||||
|
||||
=== As a Plugin
|
||||
|
||||
Use this if you prefer to use the edge version of shoulda:
|
||||
|
||||
$ script/plugin install git://github.com/thoughtbot/shoulda.git
|
||||
|
||||
=== As a Plugin (using git submodules)
|
||||
|
||||
Use this if you prefer the idea of being able to easily switch between using edge or a tagged version of shoulda:
|
||||
|
||||
$ git submodule add git://github.com/thoughtbot/shoulda.git vendor/plugins/shoulda
|
||||
|
||||
= Rails Installation (RSpec)
|
||||
|
||||
If you're using Shoulda with RSpec, we recommend that you add config.gem lines
|
||||
for RSpec and Shoulda in your config/environment/test.rb file, but do not ask
|
||||
Rails to load the RSpec and Shoulda libraries:
|
||||
|
||||
config.gem 'rspec', :lib => false
|
||||
config.gem 'rspec-rails', :lib => false
|
||||
config.gem 'thoughtbot-shoulda',
|
||||
:lib => false,
|
||||
:source => 'http://gems.github.com'
|
||||
|
||||
Then require shoulda from your spec/spec_helper.rb file, before Spec::Runner is
|
||||
configured:
|
||||
|
||||
# requires for RSpec
|
||||
require 'shoulda'
|
||||
Spec::Runner.configure do |config|
|
||||
# ...
|
||||
|
||||
You should not need to require anything besides the top-level shoulda library.
|
||||
|
||||
= Credits
|
||||
|
||||
Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc.
|
||||
|
||||
= License
|
||||
|
||||
Shoulda is Copyright © 2006-2008 Tammer Saleh, Thoughtbot. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
@ -1,72 +0,0 @@
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/gempackagetask'
|
||||
|
||||
$LOAD_PATH.unshift("lib")
|
||||
require 'shoulda'
|
||||
load 'tasks/shoulda.rake'
|
||||
|
||||
# Test::Unit::UI::VERBOSE
|
||||
test_files_pattern = 'test/{unit,functional,other,matchers}/**/*_test.rb'
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = test_files_pattern
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Shoulda -- Making tests easy on the fingers and eyes"
|
||||
rdoc.options << '--line-numbers'
|
||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
||||
rdoc.rdoc_files.include('README.rdoc', 'CONTRIBUTION_GUIDELINES.rdoc', 'lib/**/*.rb')
|
||||
}
|
||||
|
||||
desc "Run code-coverage analysis using rcov"
|
||||
task :coverage do
|
||||
rm_rf "coverage"
|
||||
files = Dir[test_files_pattern]
|
||||
system "rcov --rails --sort coverage -Ilib #{files.join(' ')}"
|
||||
end
|
||||
|
||||
desc 'Update documentation on website'
|
||||
task :sync_docs => 'rdoc' do
|
||||
`rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/shoulda`
|
||||
end
|
||||
|
||||
desc 'Default: run tests.'
|
||||
task :default => ['test']
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.name = "shoulda"
|
||||
s.version = Shoulda::VERSION
|
||||
s.summary = "Making tests easy on the fingers and eyes"
|
||||
s.homepage = "http://thoughtbot.com/projects/shoulda"
|
||||
s.rubyforge_project = "shoulda"
|
||||
|
||||
s.files = FileList["[A-Z]*", "{bin,lib,rails,test}/**/*"]
|
||||
s.executables = s.files.grep(/^bin/) { |f| File.basename(f) }
|
||||
|
||||
s.has_rdoc = true
|
||||
s.extra_rdoc_files = ["README.rdoc", "CONTRIBUTION_GUIDELINES.rdoc"]
|
||||
s.rdoc_options = ["--line-numbers", "--main", "README.rdoc"]
|
||||
|
||||
s.authors = ["Tammer Saleh"]
|
||||
s.email = "tsaleh@thoughtbot.com"
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new spec do |pkg|
|
||||
pkg.need_tar = true
|
||||
pkg.need_zip = true
|
||||
end
|
||||
|
||||
desc "Clean files generated by rake tasks"
|
||||
task :clobber => [:clobber_rdoc, :clobber_package]
|
||||
|
||||
desc "Generate a gemspec file for GitHub"
|
||||
task :gemspec do
|
||||
File.open("#{spec.name}.gemspec", 'w') do |f|
|
||||
f.write spec.to_ruby
|
||||
end
|
||||
end
|
@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'fileutils'
|
||||
require 'tmpdir'
|
||||
|
||||
TMP = Dir::tmpdir
|
||||
|
||||
def usage(msg = nil)
|
||||
puts "Error: #{msg}" if msg
|
||||
puts if msg
|
||||
puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
|
||||
puts
|
||||
puts "Will convert an existing test file with names like "
|
||||
puts
|
||||
puts " def test_should_do_stuff"
|
||||
puts " ..."
|
||||
puts " end"
|
||||
puts
|
||||
puts "to one using the new syntax: "
|
||||
puts
|
||||
puts " should \"be super cool\" do"
|
||||
puts " ..."
|
||||
puts " end"
|
||||
puts
|
||||
puts "A copy of the old file will be left under #{TMP} in case\nthis script just seriously screws up"
|
||||
puts
|
||||
exit (msg ? 2 : 0)
|
||||
end
|
||||
|
||||
usage("Wrong number of arguments.") unless ARGV.size == 1
|
||||
usage("Temp directory '#{TMP}' is not valid. Set TMPDIR environment variable to a writeable directory.") unless File.directory?(TMP) && File.writable?(TMP)
|
||||
|
||||
file = ARGV.shift
|
||||
tmpfile = File.join(TMP, File.basename(file))
|
||||
usage("File '#{file}' doesn't exist") unless File.exists?(file)
|
||||
|
||||
FileUtils.cp(file, tmpfile)
|
||||
contents = File.read(tmpfile)
|
||||
contents.gsub!(/def test_should_(\S+)/) {|line| "should \"#{$1.tr('_', ' ')}\" do"}
|
||||
contents.gsub!(/def test_(\S+)/) {|line| "should \"RENAME ME: test #{$1.tr('_', ' ')}\" do"}
|
||||
File.open(file, 'w') { |f| f.write(contents) }
|
||||
|
||||
puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"
|
@ -1,9 +0,0 @@
|
||||
module Shoulda
|
||||
VERSION = "2.10.1"
|
||||
end
|
||||
|
||||
if defined? Spec
|
||||
require 'shoulda/rspec'
|
||||
else
|
||||
require 'shoulda/test_unit'
|
||||
end
|
@ -1,32 +0,0 @@
|
||||
require 'shoulda'
|
||||
require 'shoulda/action_controller/helpers'
|
||||
require 'shoulda/action_controller/matchers'
|
||||
require 'shoulda/action_controller/macros'
|
||||
require 'shoulda/action_controller/resource_options'
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::ActionController::Matchers
|
||||
include Shoulda::ActionController::Helpers
|
||||
extend Shoulda::ActionController::Macros
|
||||
Shoulda::ActionController::VALID_FORMATS.each do |format|
|
||||
include "Shoulda::ActionController::#{format.to_s.upcase}".constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'shoulda/active_record/assertions'
|
||||
require 'shoulda/action_mailer/assertions'
|
||||
|
||||
module ActionController #:nodoc: all
|
||||
module Integration
|
||||
class Session
|
||||
include Shoulda::Assertions
|
||||
include Shoulda::Helpers
|
||||
include Shoulda::ActiveRecord::Assertions
|
||||
include Shoulda::ActionMailer::Assertions
|
||||
end
|
||||
end
|
||||
end
|
@ -1,199 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module HTML # :nodoc: all
|
||||
def self.included(other)
|
||||
other.class_eval do
|
||||
extend Shoulda::ActionController::HTML::ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def controller_name_from_class
|
||||
self.name.gsub(/Test/, '')
|
||||
end
|
||||
|
||||
def make_show_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#show" do
|
||||
setup do
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :show
|
||||
should_not_set_the_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#edit" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
get :edit, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:edit)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :edit
|
||||
should_not_set_the_flash
|
||||
should_render_a_form
|
||||
should "set @#{res.object} to requested instance" do
|
||||
assert_equal @record, assigns(res.object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_index_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#index" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
should_render_template :index
|
||||
should_not_set_the_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_new_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#new" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:new, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:new)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object
|
||||
should_not_set_the_flash
|
||||
should_render_template :new
|
||||
should_render_a_form
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_html_tests(res)
|
||||
context "on DELETE to #{controller_name_from_class}#destroy" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
|
||||
should "not destroy record" do
|
||||
assert_nothing_raised { assert @record.reload }
|
||||
end
|
||||
else
|
||||
should_set_the_flash_to res.destroy.flash
|
||||
if res.destroy.redirect.is_a? Symbol
|
||||
should_respond_with res.destroy.redirect
|
||||
else
|
||||
should_redirect_to res.destroy.redirect
|
||||
end
|
||||
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
|
||||
@record.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_html_tests(res)
|
||||
context "on POST to #{controller_name_from_class}#create with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to res.create.flash
|
||||
if res.create.redirect.is_a? Symbol
|
||||
should_respond_with res.create.redirect
|
||||
else
|
||||
should_redirect_to res.create.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_html_tests(res)
|
||||
context "on PUT to #{controller_name_from_class}#update with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to(res.update.flash)
|
||||
if res.update.redirect.is_a? Symbol
|
||||
should_respond_with res.update.redirect
|
||||
else
|
||||
should_redirect_to res.update.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,168 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module XML
|
||||
def self.included(other) #:nodoc:
|
||||
other.class_eval do
|
||||
extend Shoulda::ActionController::XML::ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Macro that creates a test asserting that the controller responded with an XML content-type
|
||||
# and that the XML contains +<name/>+ as the root element.
|
||||
def should_respond_with_xml_for(name = nil)
|
||||
should "have ContentType set to 'application/xml'" do
|
||||
assert_xml_response
|
||||
end
|
||||
|
||||
if name
|
||||
should "return <#{name}/> as the root element" do
|
||||
body = @response.body.first(100).map {|l| " #{l}"}
|
||||
assert_select name.to_s.dasherize, 1, "Body:\n#{body}...\nDoes not have <#{name}/> as the root element."
|
||||
end
|
||||
end
|
||||
end
|
||||
alias should_respond_with_xml should_respond_with_xml_for
|
||||
|
||||
protected
|
||||
|
||||
def make_show_xml_tests(res) # :nodoc:
|
||||
context "on GET to #{controller_name_from_class}#show as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need an :edit action
|
||||
end
|
||||
|
||||
def make_new_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need a :new action
|
||||
end
|
||||
|
||||
def make_index_xml_tests(res) # :nodoc:
|
||||
context "on GET to #{controller_name_from_class}#index as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_respond_with 401
|
||||
else
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object.to_s.pluralize
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_xml_tests(res) # :nodoc:
|
||||
context "on DELETE to #{controller_name_from_class}#destroy as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_respond_with 401
|
||||
|
||||
should "not destroy record" do
|
||||
assert @record.reload
|
||||
end
|
||||
else
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
|
||||
@record.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_xml_tests(res) # :nodoc:
|
||||
context "on POST to #{controller_name_from_class}#create as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_respond_with 401
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_xml_tests(res) # :nodoc:
|
||||
context "on PUT to #{controller_name_from_class}#update as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the next request's format to 'application/xml'
|
||||
def request_xml
|
||||
@request.accept = "application/xml"
|
||||
end
|
||||
|
||||
# Asserts that the controller's response was 'application/xml'
|
||||
def assert_xml_response
|
||||
content_type = (@response.headers["Content-Type"] || @response.headers["type"]).to_s
|
||||
regex = %r{\bapplication/xml\b}
|
||||
|
||||
msg = "Content Type '#{content_type.inspect}' doesn't match '#{regex.inspect}'\n"
|
||||
msg += "Body: #{@response.body.first(100).chomp} ..."
|
||||
|
||||
assert_match regex, content_type, msg
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,64 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Helpers # :nodoc:
|
||||
private # :enddoc:
|
||||
|
||||
SPECIAL_INSTANCE_VARIABLES = %w{
|
||||
_cookies
|
||||
_flash
|
||||
_headers
|
||||
_params
|
||||
_request
|
||||
_response
|
||||
_session
|
||||
action_name
|
||||
before_filter_chain_aborted
|
||||
cookies
|
||||
flash
|
||||
headers
|
||||
ignore_missing_templates
|
||||
logger
|
||||
params
|
||||
request
|
||||
request_origin
|
||||
response
|
||||
session
|
||||
template
|
||||
template_class
|
||||
template_root
|
||||
url
|
||||
variables_added
|
||||
}.map(&:to_s)
|
||||
|
||||
def instantiate_variables_from_assigns(*names, &blk)
|
||||
old = {}
|
||||
names = (@response.template.assigns.keys - SPECIAL_INSTANCE_VARIABLES) if names.empty?
|
||||
names.each do |name|
|
||||
old[name] = instance_variable_get("@#{name}")
|
||||
instance_variable_set("@#{name}", assigns(name.to_sym))
|
||||
end
|
||||
blk.call
|
||||
names.each do |name|
|
||||
instance_variable_set("@#{name}", old[name])
|
||||
end
|
||||
end
|
||||
|
||||
def get_existing_record(res) # :nodoc:
|
||||
returning(instance_variable_get("@#{res.object}")) do |record|
|
||||
assert(record, "This test requires you to set @#{res.object} in your setup block")
|
||||
end
|
||||
end
|
||||
|
||||
def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc:
|
||||
parent_names ||= resource.parents.reverse
|
||||
return {} if parent_names == [] # Base case
|
||||
parent_name = parent_names.shift
|
||||
parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first)
|
||||
|
||||
{ :"#{parent_name}_id" => parent.to_param }.merge(make_parent_params(resource, parent, parent_names))
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,296 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
# = Macro test helpers for your controllers
|
||||
#
|
||||
# By using the macro helpers you can quickly and easily create concise and easy to read test suites.
|
||||
#
|
||||
# This code segment:
|
||||
# context "on GET to :show for first record" do
|
||||
# setup do
|
||||
# get :show, :id => 1
|
||||
# end
|
||||
#
|
||||
# should_assign_to :user
|
||||
# should_respond_with :success
|
||||
# should_render_template :show
|
||||
# should_not_set_the_flash
|
||||
#
|
||||
# should "do something else really cool" do
|
||||
# assert_equal 1, assigns(:user).id
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the +show+ action
|
||||
module Macros
|
||||
include Matchers
|
||||
|
||||
def should_be_restful(&blk) # :yields: resource
|
||||
resource = ResourceOptions.new
|
||||
blk.call(resource)
|
||||
resource.normalize!(self)
|
||||
|
||||
resource.formats.each do |format|
|
||||
resource.actions.each do |action|
|
||||
if self.respond_to? :"make_#{action}_#{format}_tests"
|
||||
self.send(:"make_#{action}_#{format}_tests", resource)
|
||||
else
|
||||
should "test #{action} #{format}" do
|
||||
flunk "Test for #{action} as #{format} not implemented"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the flash contains the given value.
|
||||
# val can be a String, a Regex, or nil (indicating that the flash should not be set)
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_set_the_flash_to "Thank you for placing this order."
|
||||
# should_set_the_flash_to /created/i
|
||||
# should_set_the_flash_to nil
|
||||
def should_set_the_flash_to(val)
|
||||
matcher = set_the_flash.to(val)
|
||||
if val
|
||||
should matcher.description do
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
else
|
||||
should "not #{matcher.description}" do
|
||||
assert_rejects matcher, @controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the flash is empty. Same as
|
||||
# @should_set_the_flash_to nil@
|
||||
def should_not_set_the_flash
|
||||
should_set_the_flash_to nil
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that filter_parameter_logging
|
||||
#
|
||||
# is set for the specified keys
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_filter_params :password, :ssn
|
||||
def should_filter_params(*keys)
|
||||
keys.each do |key|
|
||||
matcher = filter_param(key)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller assigned to
|
||||
# each of the named instance variable(s).
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:class</tt> - The expected class of the instance variable being checked.
|
||||
# * <tt>:equals</tt> - A string which is evaluated and compared for equality with
|
||||
# the instance variable being checked.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_assign_to :user, :posts
|
||||
# should_assign_to :user, :class => User
|
||||
# should_assign_to(:user) { @user }
|
||||
def should_assign_to(*names, &block)
|
||||
opts = names.extract_options!
|
||||
if opts[:equals]
|
||||
warn "[DEPRECATION] should_assign_to :var, :equals => 'val' " <<
|
||||
"is deprecated. Use should_assign_to(:var) { 'val' } instead."
|
||||
end
|
||||
names.each do |name|
|
||||
matcher = assign_to(name).with_kind_of(opts[:class])
|
||||
test_name = matcher.description
|
||||
test_name << " which is equal to #{opts[:equals]}" if opts[:equals]
|
||||
should test_name do
|
||||
if opts[:equals]
|
||||
instantiate_variables_from_assigns do
|
||||
expected_value = eval(opts[:equals],
|
||||
self.send(:binding),
|
||||
__FILE__,
|
||||
__LINE__)
|
||||
matcher = matcher.with(expected_value)
|
||||
end
|
||||
elsif block
|
||||
expected_value = instance_eval(&block)
|
||||
matcher = matcher.with(expected_value)
|
||||
end
|
||||
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller did not assign to
|
||||
# any of the named instance variable(s).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_not_assign_to :user, :posts
|
||||
def should_not_assign_to(*names)
|
||||
names.each do |name|
|
||||
matcher = assign_to(name)
|
||||
should "not #{matcher.description}" do
|
||||
assert_rejects matcher, @controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller responded with a 'response' status code.
|
||||
# Example:
|
||||
#
|
||||
# should_respond_with :success
|
||||
def should_respond_with(response)
|
||||
should "respond with #{response}" do
|
||||
matcher = respond_with(response)
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the response content type was 'content_type'.
|
||||
# Example:
|
||||
#
|
||||
# should_respond_with_content_type 'application/rss+xml'
|
||||
# should_respond_with_content_type :rss
|
||||
# should_respond_with_content_type /rss/
|
||||
def should_respond_with_content_type(content_type)
|
||||
should "respond with content type of #{content_type}" do
|
||||
matcher = respond_with_content_type(content_type)
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that a value returned from the session is correct.
|
||||
# The given string is evaled to produce the resulting redirect path. All of the instance variables
|
||||
# set by the controller are available to the evaled string.
|
||||
# Example:
|
||||
#
|
||||
# should_set_session(:user_id) { '@user.id' }
|
||||
# should_set_session(:message) { "Free stuff" }
|
||||
def should_set_session(key, expected = nil, &block)
|
||||
matcher = set_session(key)
|
||||
if expected
|
||||
warn "[DEPRECATION] should_set_session :key, 'val' is deprecated. " <<
|
||||
"Use should_set_session(:key) { 'val' } instead."
|
||||
end
|
||||
should matcher.description do
|
||||
if expected
|
||||
instantiate_variables_from_assigns do
|
||||
expected_value = eval(expected,
|
||||
self.send(:binding),
|
||||
__FILE__,
|
||||
__LINE__)
|
||||
matcher = matcher.to(expected_value)
|
||||
end
|
||||
else
|
||||
expected_value = instance_eval(&block)
|
||||
matcher = matcher.to(expected_value)
|
||||
end
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecated. See should_set_session
|
||||
def should_return_from_session(key, expected)
|
||||
warn "[DEPRECATION] should_require_attributes is deprecated. " <<
|
||||
"Use should_set_session instead."
|
||||
should_set_session(key, expected)
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller rendered the given template.
|
||||
# Example:
|
||||
#
|
||||
# should_render_template :new
|
||||
def should_render_template(template)
|
||||
should "render template #{template.inspect}" do
|
||||
assert_template template.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller rendered with the given layout.
|
||||
# Example:
|
||||
#
|
||||
# should_render_with_layout 'special'
|
||||
def should_render_with_layout(expected_layout = 'application')
|
||||
matcher = render_with_layout(expected_layout)
|
||||
if expected_layout
|
||||
should matcher.description do
|
||||
assert_accepts matcher, @controller
|
||||
end
|
||||
else
|
||||
should "render without layout" do
|
||||
assert_rejects matcher, @controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller rendered without a layout.
|
||||
# Same as @should_render_with_layout false@
|
||||
def should_render_without_layout
|
||||
should_render_with_layout nil
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller returned a redirect to the given path.
|
||||
# The given string is evaled to produce the resulting redirect path. All of the instance variables
|
||||
# set by the controller are available to the evaled string.
|
||||
# Example:
|
||||
#
|
||||
# should_redirect_to("the user's profile") { user_url(@user) }
|
||||
def should_redirect_to(description, &block)
|
||||
unless block
|
||||
warn "[DEPRECATION] should_redirect_to without a block is " <<
|
||||
"deprecated. Use should_redirect_to('somewhere') { } instead."
|
||||
end
|
||||
should "redirect to #{description}" do
|
||||
if block
|
||||
url = instance_eval(&block)
|
||||
else
|
||||
instantiate_variables_from_assigns do
|
||||
url = eval(description, self.send(:binding), __FILE__, __LINE__)
|
||||
end
|
||||
end
|
||||
assert_redirected_to url
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a routing test. It tries to use the given HTTP
|
||||
# +method+ on the given +path+, and asserts that it routes to the
|
||||
# given +options+.
|
||||
#
|
||||
# If you don't specify a :controller, it will try to guess the controller
|
||||
# based on the current test.
|
||||
#
|
||||
# +to_param+ is called on the +options+ given.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# should_route :get, "/posts", :controller => :posts, :action => :index
|
||||
# should_route :get, "/posts/new", :action => :new
|
||||
# should_route :post, "/posts", :action => :create
|
||||
# should_route :get, "/posts/1", :action => :show, :id => 1
|
||||
# should_route :edit, "/posts/1", :action => :show, :id => 1
|
||||
# should_route :put, "/posts/1", :action => :update, :id => 1
|
||||
# should_route :delete, "/posts/1", :action => :destroy, :id => 1
|
||||
# should_route :get, "/users/1/posts/1",
|
||||
# :action => :show, :id => 1, :user_id => 1
|
||||
#
|
||||
def should_route(method, path, options)
|
||||
unless options[:controller]
|
||||
options[:controller] = self.name.gsub(/ControllerTest$/, '').tableize
|
||||
end
|
||||
|
||||
matcher = route(method, path).to(options)
|
||||
|
||||
should matcher.description do
|
||||
assert_accepts matcher.in_context(self), self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,37 +0,0 @@
|
||||
require 'shoulda/action_controller/matchers/assign_to_matcher'
|
||||
require 'shoulda/action_controller/matchers/filter_param_matcher'
|
||||
require 'shoulda/action_controller/matchers/set_the_flash_matcher'
|
||||
require 'shoulda/action_controller/matchers/render_with_layout_matcher'
|
||||
require 'shoulda/action_controller/matchers/respond_with_matcher'
|
||||
require 'shoulda/action_controller/matchers/respond_with_content_type_matcher'
|
||||
require 'shoulda/action_controller/matchers/set_session_matcher'
|
||||
require 'shoulda/action_controller/matchers/route_matcher'
|
||||
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# By using the macro helpers you can quickly and easily create concise and
|
||||
# easy to read test suites.
|
||||
#
|
||||
# This code segment:
|
||||
#
|
||||
# describe UsersController, "on GET to show with a valid id" do
|
||||
# before(:each) do
|
||||
# get :show, :id => User.first.to_param
|
||||
# end
|
||||
#
|
||||
# it { should assign_to(:user) }
|
||||
# it { should respond_with(:success) }
|
||||
# it { should render_template(:show) }
|
||||
# it { should not_set_the_flash) }
|
||||
#
|
||||
# it "should do something else really cool" do
|
||||
# assigns[:user].id.should == 1
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the show action
|
||||
module Matchers
|
||||
end
|
||||
end
|
||||
end
|
@ -1,109 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that the controller assigned to the named instance variable.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_kind_of</tt> - The expected class of the instance variable
|
||||
# being checked.
|
||||
# * <tt>with</tt> - The value that should be assigned.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should assign_to(:user) }
|
||||
# it { should_not assign_to(:user) }
|
||||
# it { should assign_to(:user).with_kind_of(User) }
|
||||
# it { should assign_to(:user).with(@user) }
|
||||
def assign_to(variable)
|
||||
AssignToMatcher.new(variable)
|
||||
end
|
||||
|
||||
class AssignToMatcher # :nodoc:
|
||||
|
||||
def initialize(variable)
|
||||
@variable = variable.to_s
|
||||
end
|
||||
|
||||
def with_kind_of(expected_class)
|
||||
@expected_class = expected_class
|
||||
self
|
||||
end
|
||||
|
||||
def with(expected_value)
|
||||
@expected_value = expected_value
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
assigned_value? && kind_of_expected_class? && equal_to_expected_value?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
description = "assign @#{@variable}"
|
||||
description << " with a kind of #{@expected_class}" if @expected_class
|
||||
description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assigned_value?
|
||||
if assigned_value.nil?
|
||||
@failure_message =
|
||||
"Expected action to assign a value for @#{@variable}"
|
||||
false
|
||||
else
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign a value for @#{@variable}, " <<
|
||||
"but it was assigned to #{assigned_value.inspect}"
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def kind_of_expected_class?
|
||||
return true unless @expected_class
|
||||
if assigned_value.kind_of?(@expected_class)
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign a kind of #{@expected_class} " <<
|
||||
"for #{@variable}, but got one anyway"
|
||||
true
|
||||
else
|
||||
@failure_message =
|
||||
"Expected action to assign a kind of #{@expected_class} " <<
|
||||
"for #{@variable}, but got #{@variable.inspect} " <<
|
||||
"(#{@variable.class.name})"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def equal_to_expected_value?
|
||||
return true unless @expected_value
|
||||
if @expected_value == assigned_value
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign #{@expected_value.inspect} " <<
|
||||
"for #{@variable}, but got it anyway"
|
||||
true
|
||||
else
|
||||
@failure_message =
|
||||
"Expected action to assign #{@expected_value.inspect} " <<
|
||||
"for #{@variable}, but got #{assigned_value.inspect}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_value
|
||||
assigns[@variable]
|
||||
end
|
||||
|
||||
def assigns
|
||||
@controller.response.template.assigns
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,57 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that filter_parameter_logging is set for the specified key.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should filter_param(:password) }
|
||||
def filter_param(key)
|
||||
FilterParamMatcher.new(key)
|
||||
end
|
||||
|
||||
class FilterParamMatcher # :nodoc:
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
filters_params? && filters_key?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{@key} to be filtered"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{@key} to be filtered"
|
||||
end
|
||||
|
||||
def description
|
||||
"filter #{@key}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filters_params?
|
||||
@controller.respond_to?(:filter_parameters)
|
||||
end
|
||||
|
||||
def filters_key?
|
||||
filtered_value == '[FILTERED]'
|
||||
end
|
||||
|
||||
def filtered_value
|
||||
filtered = @controller.send(:filter_parameters,
|
||||
@key.to_s => @key.to_s)
|
||||
filtered[@key.to_s]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,81 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that the controller rendered with the given layout.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should render_with_layout }
|
||||
# it { should render_with_layout(:special) }
|
||||
# it { should_not render_with_layout }
|
||||
def render_with_layout(layout = nil)
|
||||
RenderWithLayout.new(layout)
|
||||
end
|
||||
|
||||
class RenderWithLayout # :nodoc:
|
||||
|
||||
def initialize(layout)
|
||||
@layout = layout.to_s unless layout.nil?
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
rendered_with_layout? && rendered_with_expected_layout?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "render with "
|
||||
if @layout.nil?
|
||||
description << "a layout"
|
||||
else
|
||||
description << "the #{@layout.inspect} layout"
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rendered_with_layout?
|
||||
!layout.blank?
|
||||
end
|
||||
|
||||
def rendered_with_expected_layout?
|
||||
return true if @layout.nil?
|
||||
layout == @layout
|
||||
end
|
||||
|
||||
def layout
|
||||
layout = @controller.response.layout
|
||||
if layout.nil?
|
||||
nil
|
||||
else
|
||||
layout.split('/').last
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"to #{description}"
|
||||
end
|
||||
|
||||
def result
|
||||
if rendered_with_layout?
|
||||
"rendered with the #{layout.inspect} layout"
|
||||
else
|
||||
"rendered without a layout"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,70 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures a controller responded with expected 'response' content type.
|
||||
#
|
||||
# You can pass an explicit content type such as 'application/rss+xml'
|
||||
# or its symbolic equivalent :rss
|
||||
# or a regular expression such as /rss/
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should respond_with_content_type(:xml) }
|
||||
# it { should respond_with_content_type(:csv) }
|
||||
# it { should respond_with_content_type(:atom) }
|
||||
# it { should respond_with_content_type(:yaml) }
|
||||
# it { should respond_with_content_type(:text) }
|
||||
# it { should respond_with_content_type('application/rss+xml') }
|
||||
# it { should respond_with_content_type(/json/) }
|
||||
def respond_with_content_type(content_type)
|
||||
RespondWithContentTypeMatcher.new(content_type)
|
||||
end
|
||||
|
||||
class RespondWithContentTypeMatcher # :nodoc:
|
||||
|
||||
def initialize(content_type)
|
||||
@content_type = if content_type.is_a?(Symbol)
|
||||
lookup_by_extension(content_type)
|
||||
else
|
||||
content_type
|
||||
end
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
if @content_type.is_a?(Regexp)
|
||||
response_content_type =~ @content_type
|
||||
else
|
||||
response_content_type == @content_type
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def response_content_type
|
||||
@controller.response.content_type
|
||||
end
|
||||
|
||||
def lookup_by_extension(extension)
|
||||
Mime::Type.lookup_by_extension(extension.to_s).to_s
|
||||
end
|
||||
|
||||
def expectation
|
||||
"content type to be #{@content_type}, " <<
|
||||
"but was #{response_content_type}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,81 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures a controller responded with expected 'response' status code.
|
||||
#
|
||||
# You can pass an explicit status number like 200, 301, 404, 500
|
||||
# or its symbolic equivalent :success, :redirect, :missing, :error.
|
||||
# See ActionController::StatusCodes for a full list.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should respond_with(:success) }
|
||||
# it { should respond_with(:redirect) }
|
||||
# it { should respond_with(:missing) }
|
||||
# it { should respond_with(:error) }
|
||||
# it { should respond_with(501) }
|
||||
def respond_with(status)
|
||||
RespondWithMatcher.new(status)
|
||||
end
|
||||
|
||||
class RespondWithMatcher # :nodoc:
|
||||
|
||||
def initialize(status)
|
||||
@status = symbol_to_status_code(status)
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
correct_status_code? || correct_status_code_range?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"respond with #{@status}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def correct_status_code?
|
||||
response_code == @status
|
||||
end
|
||||
|
||||
def correct_status_code_range?
|
||||
@status.is_a?(Range) &&
|
||||
@status.include?(response_code)
|
||||
end
|
||||
|
||||
def response_code
|
||||
@controller.response.response_code
|
||||
end
|
||||
|
||||
def symbol_to_status_code(potential_symbol)
|
||||
case potential_symbol
|
||||
when :success then 200
|
||||
when :redirect then 300..399
|
||||
when :missing then 404
|
||||
when :error then 500..599
|
||||
when Symbol
|
||||
::ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[potential_symbol]
|
||||
else
|
||||
potential_symbol
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"response to be a #{@status}, but was #{response_code}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,93 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that requesting +path+ using +method+ routes to +options+.
|
||||
#
|
||||
# If you don't specify a controller, it will use the controller from the
|
||||
# example group.
|
||||
#
|
||||
# +to_param+ is called on the +options+ given.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# it { should route(:get, "/posts").
|
||||
# to(:controller => :posts, :action => :index) }
|
||||
# it { should route(:get, "/posts/new").to(:action => :new) }
|
||||
# it { should route(:post, "/posts").to(:action => :create) }
|
||||
# it { should route(:get, "/posts/1").to(:action => :show, :id => 1) }
|
||||
# it { should route(:edit, "/posts/1").to(:action => :show, :id => 1) }
|
||||
# it { should route(:put, "/posts/1").to(:action => :update, :id => 1) }
|
||||
# it { should route(:delete, "/posts/1").
|
||||
# to(:action => :destroy, :id => 1) }
|
||||
# it { should route(:get, "/users/1/posts/1").
|
||||
# to(:action => :show, :id => 1, :user_id => 1) }
|
||||
def route(method, path)
|
||||
RouteMatcher.new(method, path, self)
|
||||
end
|
||||
|
||||
class RouteMatcher # :nodoc:
|
||||
|
||||
def initialize(method, path, context)
|
||||
@method = method
|
||||
@path = path
|
||||
@context = context
|
||||
end
|
||||
|
||||
def to(params)
|
||||
@params = params
|
||||
self
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
guess_controller!
|
||||
stringify_params!
|
||||
route_recognized?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"route #{@method.to_s.upcase} #{@path} to/from #{@params.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def guess_controller!
|
||||
@params[:controller] ||= @controller.controller_path
|
||||
end
|
||||
|
||||
def stringify_params!
|
||||
@params.each do |key, value|
|
||||
@params[key] = value.to_param
|
||||
end
|
||||
end
|
||||
|
||||
def route_recognized?
|
||||
begin
|
||||
@context.send(:assert_routing,
|
||||
{ :method => @method, :path => @path },
|
||||
@params)
|
||||
|
||||
@negative_failure_message = "Didn't expect to #{description}"
|
||||
true
|
||||
rescue ::ActionController::RoutingError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,87 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that a session key was set to the expected value.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should set_session(:message) }
|
||||
# it { should set_session(:user_id).to(@user.id) }
|
||||
# it { should_not set_session(:user_id) }
|
||||
def set_session(key)
|
||||
SetSessionMatcher.new(key)
|
||||
end
|
||||
|
||||
class SetSessionMatcher # :nodoc:
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
end
|
||||
|
||||
def to(value)
|
||||
@value = value
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
(assigned_value? && assigned_correct_value?) || cleared_value?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Didn't expect #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "set session variable #{@key.inspect}"
|
||||
description << " to #{@value.inspect}" if defined?(@value)
|
||||
description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assigned_value?
|
||||
!assigned_value.blank?
|
||||
end
|
||||
|
||||
def cleared_value?
|
||||
defined?(@value) && @value.nil? && assigned_value.nil?
|
||||
end
|
||||
|
||||
def assigned_correct_value?
|
||||
return true if @value.nil?
|
||||
assigned_value == @value
|
||||
end
|
||||
|
||||
def assigned_value
|
||||
session[@key]
|
||||
end
|
||||
|
||||
def session
|
||||
@controller.response.session.data
|
||||
end
|
||||
|
||||
def expectation
|
||||
expectation = "session variable #{@key} to be set"
|
||||
expectation << " to #{@value.inspect}" if @value
|
||||
expectation
|
||||
end
|
||||
|
||||
def result
|
||||
if session.empty?
|
||||
"no session variables were set"
|
||||
else
|
||||
"the session was #{session.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,85 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that the flash contains the given value. Can be a String, a
|
||||
# Regexp, or nil (indicating that the flash should not be set).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should set_the_flash }
|
||||
# it { should set_the_flash.to("Thank you for placing this order.") }
|
||||
# it { should set_the_flash.to(/created/i) }
|
||||
# it { should_not set_the_flash }
|
||||
def set_the_flash
|
||||
SetTheFlashMatcher.new
|
||||
end
|
||||
|
||||
class SetTheFlashMatcher # :nodoc:
|
||||
|
||||
def to(value)
|
||||
@value = value
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
sets_the_flash? && string_value_matches? && regexp_value_matches?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
description = "set the flash"
|
||||
description << " to #{@value.inspect}" unless @value.nil?
|
||||
description
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sets_the_flash?
|
||||
!flash.blank?
|
||||
end
|
||||
|
||||
def string_value_matches?
|
||||
return true unless String === @value
|
||||
flash.values.any? {|value| value == @value }
|
||||
end
|
||||
|
||||
def regexp_value_matches?
|
||||
return true unless Regexp === @value
|
||||
flash.values.any? {|value| value =~ @value }
|
||||
end
|
||||
|
||||
def flash
|
||||
@controller.response.session['flash']
|
||||
end
|
||||
|
||||
def expectation
|
||||
expectation = "the flash to be set"
|
||||
expectation << " to #{@value.inspect}" unless @value.nil?
|
||||
expectation << ", but #{flash_description}"
|
||||
expectation
|
||||
end
|
||||
|
||||
def flash_description
|
||||
if flash.blank?
|
||||
"no flash was set"
|
||||
else
|
||||
"was #{flash.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,233 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionController
|
||||
# Formats tested by #should_be_restful. Defaults to [:html, :xml]
|
||||
VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
|
||||
VALID_FORMATS.each {|f| require "shoulda/action_controller/formats/#{f}"}
|
||||
|
||||
# Actions tested by #should_be_restful
|
||||
VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
|
||||
|
||||
# A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
|
||||
#
|
||||
# Example:
|
||||
# class UsersControllerTest < Test::Unit::TestCase
|
||||
# fixtures :all
|
||||
#
|
||||
# def setup
|
||||
# ...normal setup code...
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.identifier = :id
|
||||
# resource.klass = User
|
||||
# resource.object = :user
|
||||
# resource.parent = []
|
||||
# resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
|
||||
# resource.formats = [:html, :xml]
|
||||
#
|
||||
# resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
|
||||
# resource.update.params = { :name => "sue" }
|
||||
#
|
||||
# resource.create.redirect = "user_url(@user)"
|
||||
# resource.update.redirect = "user_url(@user)"
|
||||
# resource.destroy.redirect = "users_url"
|
||||
#
|
||||
# resource.create.flash = /created/i
|
||||
# resource.update.flash = /updated/i
|
||||
# resource.destroy.flash = /removed/i
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Whenever possible, the resource attributes will be set to sensible defaults.
|
||||
#
|
||||
class ResourceOptions
|
||||
# Configuration options for the create, update, destroy actions under should_be_restful
|
||||
class ActionOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
#
|
||||
# Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Defaults:
|
||||
# destroy:: /removed/
|
||||
# create:: /created/
|
||||
# update:: /updated/
|
||||
attr_accessor :flash
|
||||
|
||||
# Hash describing the params that should be sent in with this action.
|
||||
attr_accessor :params
|
||||
end
|
||||
|
||||
# Configuration options for the denied actions under should_be_restful
|
||||
#
|
||||
# Example:
|
||||
# context "The public" do
|
||||
# setup do
|
||||
# @request.session[:logged_in] = false
|
||||
# end
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
|
||||
# resource.denied.flash = /get outta here/i
|
||||
# resource.denied.redirect = 'new_session_url'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class DeniedOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.flash = /created/
|
||||
attr_accessor :flash
|
||||
|
||||
# Actions that should be denied (only used by resource.denied). <i>Note that these actions will
|
||||
# only be tested if they are also listed in +resource.actions+</i>
|
||||
# The special value of :all will deny all of the REST actions.
|
||||
attr_accessor :actions
|
||||
end
|
||||
|
||||
# Name of key in params that references the primary key.
|
||||
# Will almost always be :id (default), unless you are using a plugin or have patched rails.
|
||||
attr_accessor :identifier
|
||||
|
||||
# Name of the ActiveRecord class this resource is responsible for. Automatically determined from
|
||||
# test class if not explicitly set. UserTest => "User"
|
||||
attr_accessor :klass
|
||||
|
||||
# Name of the instantiated ActiveRecord object that should be used by some of the tests.
|
||||
# Defaults to the underscored name of the AR class. CompanyManager => :company_manager
|
||||
attr_accessor :object
|
||||
|
||||
# Name of the parent AR objects. Can be set as parent= or parents=, and can take either
|
||||
# the name of the parent resource (if there's only one), or an array of names (if there's
|
||||
# more than one).
|
||||
#
|
||||
# Example:
|
||||
# # in the routes...
|
||||
# map.resources :companies do
|
||||
# map.resources :people do
|
||||
# map.resources :limbs
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # in the tests...
|
||||
# class PeopleControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :companies
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class LimbsControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parents = [:companies, :people]
|
||||
# end
|
||||
# end
|
||||
attr_accessor :parent
|
||||
alias parents parent
|
||||
alias parents= parent=
|
||||
|
||||
# Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
|
||||
# Tests for each actionw will only be generated if the action is listed here.
|
||||
# The special value of :all will test all of the REST actions.
|
||||
#
|
||||
# Example (for a read-only controller):
|
||||
# resource.actions = [:show, :index]
|
||||
attr_accessor :actions
|
||||
|
||||
# Formats that should be tested. Must be a subset of VALID_FORMATS (default).
|
||||
# Each action will be tested against the formats listed here. The special value
|
||||
# of :all will test all of the supported formats.
|
||||
#
|
||||
# Example:
|
||||
# resource.actions = [:html, :xml]
|
||||
attr_accessor :formats
|
||||
|
||||
# ActionOptions object specifying options for the create action.
|
||||
attr_accessor :create
|
||||
|
||||
# ActionOptions object specifying options for the update action.
|
||||
attr_accessor :update
|
||||
|
||||
# ActionOptions object specifying options for the desrtoy action.
|
||||
attr_accessor :destroy
|
||||
|
||||
# DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
|
||||
attr_accessor :denied
|
||||
|
||||
def initialize # :nodoc:
|
||||
@create = ActionOptions.new
|
||||
@update = ActionOptions.new
|
||||
@destroy = ActionOptions.new
|
||||
@denied = DeniedOptions.new
|
||||
|
||||
@create.flash ||= /created/i
|
||||
@update.flash ||= /updated/i
|
||||
@destroy.flash ||= /removed/i
|
||||
@denied.flash ||= /denied/i
|
||||
|
||||
@create.params ||= {}
|
||||
@update.params ||= {}
|
||||
|
||||
@actions = VALID_ACTIONS
|
||||
@formats = VALID_FORMATS
|
||||
@denied.actions = []
|
||||
end
|
||||
|
||||
def normalize!(target) # :nodoc:
|
||||
@denied.actions = VALID_ACTIONS if @denied.actions == :all
|
||||
@actions = VALID_ACTIONS if @actions == :all
|
||||
@formats = VALID_FORMATS if @formats == :all
|
||||
|
||||
@denied.actions = @denied.actions.map(&:to_sym)
|
||||
@actions = @actions.map(&:to_sym)
|
||||
@formats = @formats.map(&:to_sym)
|
||||
|
||||
ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
|
||||
ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
|
||||
ensure_valid_members(@formats, VALID_FORMATS, 'formats')
|
||||
|
||||
@identifier ||= :id
|
||||
@klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
|
||||
@object ||= @klass.name.tableize.singularize
|
||||
@parent ||= []
|
||||
@parent = [@parent] unless @parent.is_a? Array
|
||||
|
||||
collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
|
||||
collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
|
||||
@destroy.redirect ||= "#{collection_helper}(#{collection_args})"
|
||||
|
||||
member_helper = [@parent, @object, 'url'].flatten.join('_')
|
||||
member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
|
||||
@create.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@update.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@denied.redirect ||= "new_session_url"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_valid_members(ary, valid_members, name) # :nodoc:
|
||||
invalid = ary - valid_members
|
||||
raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +0,0 @@
|
||||
require 'shoulda'
|
||||
require 'shoulda/action_mailer/assertions'
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::ActionMailer::Assertions
|
||||
end
|
||||
end
|
||||
end
|
@ -1,38 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionMailer # :nodoc:
|
||||
module Assertions
|
||||
# Asserts that an email was delivered. Can take a block that can further
|
||||
# narrow down the types of emails you're expecting.
|
||||
#
|
||||
# assert_sent_email
|
||||
#
|
||||
# Passes if ActionMailer::Base.deliveries has an email
|
||||
#
|
||||
# assert_sent_email do |email|
|
||||
# email.subject =~ /hi there/ && email.to.include?('none@none.com')
|
||||
# end
|
||||
#
|
||||
# Passes if there is an email with subject containing 'hi there' and
|
||||
# 'none@none.com' as one of the recipients.
|
||||
#
|
||||
def assert_sent_email
|
||||
emails = ::ActionMailer::Base.deliveries
|
||||
assert !emails.empty?, "No emails were sent"
|
||||
if block_given?
|
||||
matching_emails = emails.select {|email| yield email }
|
||||
assert !matching_emails.empty?, "None of the emails matched."
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that no ActionMailer mails were delivered
|
||||
#
|
||||
# assert_did_not_send_email
|
||||
def assert_did_not_send_email
|
||||
msg = "Sent #{::ActionMailer::Base.deliveries.size} emails.\n"
|
||||
::ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" }
|
||||
assert ::ActionMailer::Base.deliveries.empty?, msg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +0,0 @@
|
||||
require 'shoulda'
|
||||
require 'shoulda/action_view/macros'
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
extend Shoulda::ActionView::Macros
|
||||
end
|
||||
end
|
||||
end
|
@ -1,56 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActionView # :nodoc:
|
||||
# = Macro test helpers for your view
|
||||
#
|
||||
# By using the macro helpers you can quickly and easily create concise and
|
||||
# easy to read test suites.
|
||||
#
|
||||
# This code segment:
|
||||
# context "on GET to :new" do
|
||||
# setup do
|
||||
# get :new
|
||||
# end
|
||||
#
|
||||
# should_render_a_form
|
||||
# should_render_page_with_metadata :title => /index/
|
||||
#
|
||||
# should "do something else really cool" do
|
||||
# assert_select '#really_cool'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 3 tests for the +show+ action
|
||||
module Macros
|
||||
|
||||
# Macro that creates a test asserting that the rendered view contains a <form> element.
|
||||
def should_render_a_form
|
||||
should "display a form" do
|
||||
assert_select "form", true, "The template doesn't contain a <form> element"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the rendered view contains the selected metatags.
|
||||
# Values can be string or Regexps.
|
||||
# Example:
|
||||
#
|
||||
# should_render_page_with_metadata :description => "Description of this page", :keywords => /post/
|
||||
#
|
||||
# You can also use this method to test the rendered views title.
|
||||
#
|
||||
# Example:
|
||||
# should_render_page_with_metadata :title => /index/
|
||||
def should_render_page_with_metadata(options)
|
||||
options.each do |key, value|
|
||||
should "have metatag #{key}" do
|
||||
if key.to_sym == :title
|
||||
assert_select "title", value
|
||||
else
|
||||
assert_select "meta[name=?][content#{"*" if value.is_a?(Regexp)}=?]", key, value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,16 +0,0 @@
|
||||
require 'shoulda'
|
||||
require 'shoulda/active_record/helpers'
|
||||
require 'shoulda/active_record/matchers'
|
||||
require 'shoulda/active_record/assertions'
|
||||
require 'shoulda/active_record/macros'
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::ActiveRecord::Helpers
|
||||
include Shoulda::ActiveRecord::Matchers
|
||||
include Shoulda::ActiveRecord::Assertions
|
||||
extend Shoulda::ActiveRecord::Macros
|
||||
end
|
||||
end
|
||||
end
|
@ -1,69 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Assertions
|
||||
# Asserts that the given object can be saved
|
||||
#
|
||||
# assert_save User.new(params)
|
||||
def assert_save(obj)
|
||||
assert obj.save, "Errors: #{pretty_error_messages obj}"
|
||||
obj.reload
|
||||
end
|
||||
|
||||
# Asserts that the given object is valid
|
||||
#
|
||||
# assert_valid User.new(params)
|
||||
def assert_valid(obj)
|
||||
assert obj.valid?, "Errors: #{pretty_error_messages obj}"
|
||||
end
|
||||
|
||||
# Asserts that an Active Record model validates with the passed
|
||||
# <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
|
||||
# contained within the list of errors for that attribute.
|
||||
#
|
||||
# assert_good_value(User.new, :email, "user@example.com")
|
||||
# assert_good_value(User.new, :ssn, "123456789", /length/)
|
||||
#
|
||||
# If a class is passed as the first argument, a new object will be
|
||||
# instantiated before the assertion. If an instance variable exists with
|
||||
# the same name as the class (underscored), that object will be used
|
||||
# instead.
|
||||
#
|
||||
# assert_good_value(User, :email, "user@example.com")
|
||||
#
|
||||
# @product = Product.new(:tangible => false)
|
||||
# assert_good_value(Product, :price, "0")
|
||||
def assert_good_value(object_or_klass, attribute, value, error_message_to_avoid = nil)
|
||||
object = get_instance_of(object_or_klass)
|
||||
matcher = allow_value(value).
|
||||
for(attribute).
|
||||
with_message(error_message_to_avoid)
|
||||
assert_accepts(matcher, object)
|
||||
end
|
||||
|
||||
# Asserts that an Active Record model invalidates the passed
|
||||
# <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
|
||||
# contained within the list of errors for that attribute.
|
||||
#
|
||||
# assert_bad_value(User.new, :email, "invalid")
|
||||
# assert_bad_value(User.new, :ssn, "123", /length/)
|
||||
#
|
||||
# If a class is passed as the first argument, a new object will be
|
||||
# instantiated before the assertion. If an instance variable exists with
|
||||
# the same name as the class (underscored), that object will be used
|
||||
# instead.
|
||||
#
|
||||
# assert_bad_value(User, :email, "invalid")
|
||||
#
|
||||
# @product = Product.new(:tangible => true)
|
||||
# assert_bad_value(Product, :price, "0")
|
||||
def assert_bad_value(object_or_klass, attribute, value,
|
||||
error_message_to_expect = nil)
|
||||
object = get_instance_of(object_or_klass)
|
||||
matcher = allow_value(value).
|
||||
for(attribute).
|
||||
with_message(error_message_to_expect)
|
||||
assert_rejects(matcher, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,40 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Helpers
|
||||
def pretty_error_messages(obj) # :nodoc:
|
||||
obj.errors.map do |a, m|
|
||||
msg = "#{a} #{m}"
|
||||
msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
|
||||
end
|
||||
end
|
||||
|
||||
def get_instance_of(object_or_klass)
|
||||
if object_or_klass.is_a?(Class)
|
||||
klass = object_or_klass
|
||||
instance_variable_get("@#{instance_variable_name_for(klass)}") || klass.new
|
||||
else
|
||||
object_or_klass
|
||||
end
|
||||
end
|
||||
|
||||
def instance_variable_name_for(klass)
|
||||
klass.to_s.split('::').last.underscore
|
||||
end
|
||||
|
||||
# Helper method that determines the default error message used by Active
|
||||
# Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
|
||||
# introduced I18n module used for localization.
|
||||
#
|
||||
# default_error_message(:blank)
|
||||
# default_error_message(:too_short, :count => 5)
|
||||
# default_error_message(:too_long, :count => 60)
|
||||
def default_error_message(key, values = {})
|
||||
if Object.const_defined?(:I18n) # Rails >= 2.2
|
||||
I18n.translate("activerecord.errors.messages.#{key}", values)
|
||||
else # Rails <= 2.1.x
|
||||
::ActiveRecord::Errors.default_error_messages[key] % values[:count]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,589 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
# = Macro test helpers for your active record models
|
||||
#
|
||||
# These helpers will test most of the validations and associations for your ActiveRecord models.
|
||||
#
|
||||
# class UserTest < Test::Unit::TestCase
|
||||
# should_validate_presence_of :name, :phone_number
|
||||
# should_not_allow_values_for :phone_number, "abcd", "1234"
|
||||
# should_allow_values_for :phone_number, "(123) 456-7890"
|
||||
#
|
||||
# should_not_allow_mass_assignment_of :password
|
||||
#
|
||||
# should_have_one :profile
|
||||
# should_have_many :dogs
|
||||
# should_have_many :messes, :through => :dogs
|
||||
# should_belong_to :lover
|
||||
# end
|
||||
#
|
||||
# For all of these helpers, the last parameter may be a hash of options.
|
||||
#
|
||||
module Macros
|
||||
include Helpers
|
||||
include Matchers
|
||||
|
||||
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_validate_presence_of :name, :phone_number
|
||||
#
|
||||
def should_validate_presence_of(*attributes)
|
||||
message = get_options!(attributes, :message)
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
matcher = validate_presence_of(attribute).with_message(message)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, get_instance_of(klass))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecated. See should_validate_presence_of
|
||||
def should_require_attributes(*attributes)
|
||||
warn "[DEPRECATION] should_require_attributes is deprecated. " <<
|
||||
"Use should_validate_presence_of instead."
|
||||
should_validate_presence_of(*attributes)
|
||||
end
|
||||
|
||||
# Ensures that the model cannot be saved if one of the attributes listed is not unique.
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
|
||||
# * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
|
||||
# * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
|
||||
# exact match. Ignored by non-text attributes. Default = <tt>true</tt>
|
||||
#
|
||||
# Examples:
|
||||
# should_validate_uniqueness_of :keyword, :username
|
||||
# should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
|
||||
# should_validate_uniqueness_of :email, :scoped_to => :name
|
||||
# should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
|
||||
# should_validate_uniqueness_of :email, :case_sensitive => false
|
||||
#
|
||||
def should_validate_uniqueness_of(*attributes)
|
||||
message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
|
||||
scope = [*scope].compact
|
||||
case_sensitive = true if case_sensitive.nil?
|
||||
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
matcher = validate_uniqueness_of(attribute).
|
||||
with_message(message).scoped_to(scope)
|
||||
matcher = matcher.case_insensitive unless case_sensitive
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, get_instance_of(klass))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecated. See should_validate_uniqueness_of
|
||||
def should_require_unique_attributes(*attributes)
|
||||
warn "[DEPRECATION] should_require_unique_attributes is deprecated. " <<
|
||||
"Use should_validate_uniqueness_of instead."
|
||||
should_validate_uniqueness_of(*attributes)
|
||||
end
|
||||
|
||||
# Ensures that the attribute can be set on mass update.
|
||||
#
|
||||
# should_allow_mass_assignment_of :first_name, :last_name
|
||||
#
|
||||
def should_allow_mass_assignment_of(*attributes)
|
||||
get_options!(attributes)
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
matcher = allow_mass_assignment_of(attribute)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, klass.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the attribute cannot be set on mass update.
|
||||
#
|
||||
# should_not_allow_mass_assignment_of :password, :admin_flag
|
||||
#
|
||||
def should_not_allow_mass_assignment_of(*attributes)
|
||||
get_options!(attributes)
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
matcher = allow_mass_assignment_of(attribute)
|
||||
should "not #{matcher.description}" do
|
||||
assert_rejects matcher, klass.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecated. See should_not_allow_mass_assignment_of
|
||||
def should_protect_attributes(*attributes)
|
||||
warn "[DEPRECATION] should_protect_attributes is deprecated. " <<
|
||||
"Use should_not_allow_mass_assignment_of instead."
|
||||
should_not_allow_mass_assignment_of(*attributes)
|
||||
end
|
||||
|
||||
# Ensures that the attribute cannot be changed once the record has been created.
|
||||
#
|
||||
# should_have_readonly_attributes :password, :admin_flag
|
||||
#
|
||||
def should_have_readonly_attributes(*attributes)
|
||||
get_options!(attributes)
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
matcher = have_readonly_attribute(attribute)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, klass.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the attribute cannot be set to the given values
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_not_allow_values_for :isbn, "bad 1", "bad 2"
|
||||
#
|
||||
def should_not_allow_values_for(attribute, *bad_values)
|
||||
message = get_options!(bad_values, :message)
|
||||
klass = model_class
|
||||
bad_values.each do |value|
|
||||
matcher = allow_value(value).for(attribute).with_message(message)
|
||||
should "not #{matcher.description}" do
|
||||
assert_rejects matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the attribute can be set to the given values.
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Example:
|
||||
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
||||
#
|
||||
def should_allow_values_for(attribute, *good_values)
|
||||
get_options!(good_values)
|
||||
klass = model_class
|
||||
klass = model_class
|
||||
good_values.each do |value|
|
||||
matcher = allow_value(value).for(attribute)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the length of the attribute is in the given range
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
|
||||
# * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_ensure_length_in_range :password, (6..20)
|
||||
#
|
||||
def should_ensure_length_in_range(attribute, range, opts = {})
|
||||
short_message, long_message = get_options!([opts],
|
||||
:short_message,
|
||||
:long_message)
|
||||
klass = model_class
|
||||
|
||||
matcher = ensure_length_of(attribute).
|
||||
is_at_least(range.first).
|
||||
with_short_message(short_message).
|
||||
is_at_most(range.last).
|
||||
with_long_message(long_message)
|
||||
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the length of the attribute is at least a certain length
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_ensure_length_at_least :name, 3
|
||||
#
|
||||
def should_ensure_length_at_least(attribute, min_length, opts = {})
|
||||
short_message = get_options!([opts], :short_message)
|
||||
klass = model_class
|
||||
|
||||
matcher = ensure_length_of(attribute).
|
||||
is_at_least(min_length).
|
||||
with_short_message(short_message)
|
||||
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the length of the attribute is exactly a certain length
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_ensure_length_is :ssn, 9
|
||||
#
|
||||
def should_ensure_length_is(attribute, length, opts = {})
|
||||
message = get_options!([opts], :message)
|
||||
klass = model_class
|
||||
matcher = ensure_length_of(attribute).
|
||||
is_equal_to(length).
|
||||
with_message(message)
|
||||
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the attribute is in the range specified
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
|
||||
# * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_ensure_value_in_range :age, (0..100)
|
||||
#
|
||||
def should_ensure_value_in_range(attribute, range, opts = {})
|
||||
message, low_message, high_message = get_options!([opts],
|
||||
:message,
|
||||
:low_message,
|
||||
:high_message)
|
||||
klass = model_class
|
||||
matcher = ensure_inclusion_of(attribute).
|
||||
in_range(range).
|
||||
with_message(message).
|
||||
with_low_message(low_message).
|
||||
with_high_message(high_message)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the attribute is numeric
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_validate_numericality_of :age
|
||||
#
|
||||
def should_validate_numericality_of(*attributes)
|
||||
message = get_options!(attributes, :message)
|
||||
klass = model_class
|
||||
attributes.each do |attribute|
|
||||
matcher = validate_numericality_of(attribute).
|
||||
with_message(message)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecated. See should_validate_numericality_of
|
||||
def should_only_allow_numeric_values_for(*attributes)
|
||||
warn "[DEPRECATION] should_only_allow_numeric_values_for is " <<
|
||||
"deprecated. Use should_validate_numericality_of instead."
|
||||
should_validate_numericality_of(*attributes)
|
||||
end
|
||||
|
||||
# Ensures that the has_many relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:through</tt> - association name for <tt>has_many :through</tt>
|
||||
# * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
|
||||
#
|
||||
# Example:
|
||||
# should_have_many :friends
|
||||
# should_have_many :enemies, :through => :friends
|
||||
# should_have_many :enemies, :dependent => :destroy
|
||||
#
|
||||
def should_have_many(*associations)
|
||||
through, dependent = get_options!(associations, :through, :dependent)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
matcher = have_many(association).through(through).dependent(dependent)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, klass.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the has_one relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
|
||||
#
|
||||
# Example:
|
||||
# should_have_one :god # unless hindu
|
||||
#
|
||||
def should_have_one(*associations)
|
||||
dependent = get_options!(associations, :dependent)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
matcher = have_one(association).dependent(dependent)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, klass.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the has_and_belongs_to_many relationship exists, and that the join
|
||||
# table is in place.
|
||||
#
|
||||
# should_have_and_belong_to_many :posts, :cars
|
||||
#
|
||||
def should_have_and_belong_to_many(*associations)
|
||||
get_options!(associations)
|
||||
klass = model_class
|
||||
|
||||
associations.each do |association|
|
||||
matcher = have_and_belong_to_many(association)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, klass.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the belongs_to relationship exists.
|
||||
#
|
||||
# should_belong_to :parent
|
||||
#
|
||||
def should_belong_to(*associations)
|
||||
dependent = get_options!(associations, :dependent)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
matcher = belong_to(association).dependent(dependent)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, klass.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given class methods are defined on the model.
|
||||
#
|
||||
# should_have_class_methods :find, :destroy
|
||||
#
|
||||
def should_have_class_methods(*methods)
|
||||
get_options!(methods)
|
||||
klass = model_class
|
||||
methods.each do |method|
|
||||
should "respond to class method ##{method}" do
|
||||
assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given instance methods are defined on the model.
|
||||
#
|
||||
# should_have_instance_methods :email, :name, :name=
|
||||
#
|
||||
def should_have_instance_methods(*methods)
|
||||
get_options!(methods)
|
||||
klass = model_class
|
||||
methods.each do |method|
|
||||
should "respond to instance method ##{method}" do
|
||||
assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given columns are defined on the models backing SQL table.
|
||||
# Also aliased to should_have_index for readability.
|
||||
# Takes the same options available in migrations:
|
||||
# :type, :precision, :limit, :default, :null, and :scale
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# should_have_db_columns :id, :email, :name, :created_at
|
||||
#
|
||||
# should_have_db_column :email, :type => "string", :limit => 255
|
||||
# should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
|
||||
# should_have_db_column :admin, :default => false, :null => false
|
||||
#
|
||||
def should_have_db_columns(*columns)
|
||||
column_type, precision, limit, default, null, scale, sql_type =
|
||||
get_options!(columns, :type, :precision, :limit,
|
||||
:default, :null, :scale, :sql_type)
|
||||
klass = model_class
|
||||
columns.each do |name|
|
||||
matcher = have_db_column(name).
|
||||
of_type(column_type).
|
||||
with_options(:precision => precision, :limit => limit,
|
||||
:default => default, :null => null,
|
||||
:scale => scale, :sql_type => sql_type)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, klass.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :should_have_db_column, :should_have_db_columns
|
||||
|
||||
# Ensures that there are DB indices on the given columns or tuples of columns.
|
||||
# Also aliased to should_have_index for readability
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:unique</tt> - whether or not the index has a unique
|
||||
# constraint. Use <tt>true</tt> to explicitly test for a unique
|
||||
# constraint. Use <tt>false</tt> to explicitly test for a non-unique
|
||||
# constraint. Use <tt>nil</tt> if you don't care whether the index is
|
||||
# unique or not. Default = <tt>nil</tt>
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# should_have_indices :email, :name, [:commentable_type, :commentable_id]
|
||||
# should_have_index :age
|
||||
# should_have_index :ssn, :unique => true
|
||||
#
|
||||
def should_have_indices(*columns)
|
||||
unique = get_options!(columns, :unique)
|
||||
klass = model_class
|
||||
|
||||
columns.each do |column|
|
||||
matcher = have_index(column).unique(unique)
|
||||
should matcher.description do
|
||||
assert_accepts(matcher, klass.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :should_have_index, :should_have_indices
|
||||
|
||||
# Ensures that the model cannot be saved if one of the attributes listed is not accepted.
|
||||
#
|
||||
# If an instance variable has been created in the setup named after the
|
||||
# model being tested, then this method will use that. Otherwise, it will
|
||||
# create a new instance to test against.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_validate_acceptance_of :eula
|
||||
#
|
||||
def should_validate_acceptance_of(*attributes)
|
||||
message = get_options!(attributes, :message)
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
matcher = validate_acceptance_of(attribute).with_message(message)
|
||||
should matcher.description do
|
||||
assert_accepts matcher, get_instance_of(klass)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecated. See should_validate_uniqueness_of
|
||||
def should_require_acceptance_of(*attributes)
|
||||
warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
|
||||
"Use should_validate_acceptance_of instead."
|
||||
should_validate_acceptance_of(*attributes)
|
||||
end
|
||||
|
||||
# Ensures that the model has a method named scope_name that returns a NamedScope object with the
|
||||
# proxy options set to the options you supply. scope_name can be either a symbol, or a method
|
||||
# call which will be evaled against the model. The eval'd method call has access to all the same
|
||||
# instance variables that a should statement would.
|
||||
#
|
||||
# Options: Any of the options that the named scope would pass on to find.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_have_named_scope :visible, :conditions => {:visible => true}
|
||||
#
|
||||
# Passes for
|
||||
#
|
||||
# named_scope :visible, :conditions => {:visible => true}
|
||||
#
|
||||
# Or for
|
||||
#
|
||||
# def self.visible
|
||||
# scoped(:conditions => {:visible => true})
|
||||
# end
|
||||
#
|
||||
# You can test lambdas or methods that return ActiveRecord#scoped calls:
|
||||
#
|
||||
# should_have_named_scope 'recent(5)', :limit => 5
|
||||
# should_have_named_scope 'recent(1)', :limit => 1
|
||||
#
|
||||
# Passes for
|
||||
# named_scope :recent, lambda {|c| {:limit => c}}
|
||||
#
|
||||
# Or for
|
||||
#
|
||||
# def self.recent(c)
|
||||
# scoped(:limit => c)
|
||||
# end
|
||||
#
|
||||
def should_have_named_scope(scope_call, find_options = nil)
|
||||
klass = model_class
|
||||
matcher = have_named_scope(scope_call).finding(find_options)
|
||||
should matcher.description do
|
||||
assert_accepts matcher.in_context(self), klass.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,42 +0,0 @@
|
||||
require 'shoulda/active_record/helpers'
|
||||
require 'shoulda/active_record/matchers/validation_matcher'
|
||||
require 'shoulda/active_record/matchers/allow_value_matcher'
|
||||
require 'shoulda/active_record/matchers/ensure_length_of_matcher'
|
||||
require 'shoulda/active_record/matchers/ensure_inclusion_of_matcher'
|
||||
require 'shoulda/active_record/matchers/validate_presence_of_matcher'
|
||||
require 'shoulda/active_record/matchers/validate_uniqueness_of_matcher'
|
||||
require 'shoulda/active_record/matchers/validate_acceptance_of_matcher'
|
||||
require 'shoulda/active_record/matchers/validate_numericality_of_matcher'
|
||||
require 'shoulda/active_record/matchers/association_matcher'
|
||||
require 'shoulda/active_record/matchers/have_db_column_matcher'
|
||||
require 'shoulda/active_record/matchers/have_index_matcher'
|
||||
require 'shoulda/active_record/matchers/have_readonly_attribute_matcher'
|
||||
require 'shoulda/active_record/matchers/allow_mass_assignment_of_matcher'
|
||||
require 'shoulda/active_record/matchers/have_named_scope_matcher'
|
||||
|
||||
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
# = Matchers for your active record models
|
||||
#
|
||||
# These matchers will test most of the validations and associations for your
|
||||
# ActiveRecord models.
|
||||
#
|
||||
# describe User do
|
||||
# it { should validate_presence_of(:name) }
|
||||
# it { should validate_presence_of(:phone_number) }
|
||||
# %w(abcd 1234).each do |value|
|
||||
# it { should_not allow_value(value).for(:phone_number) }
|
||||
# end
|
||||
# it { should allow_value("(123) 456-7890").for(:phone_number) }
|
||||
# it { should_not allow_mass_assignment_of(:password) }
|
||||
# it { should have_one(:profile) }
|
||||
# it { should have_many(:dogs) }
|
||||
# it { should have_many(:messes).through(:dogs) }
|
||||
# it { should belong_to(:lover) }
|
||||
# end
|
||||
#
|
||||
module Matchers
|
||||
end
|
||||
end
|
||||
end
|
@ -1,83 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that the attribute can be set on mass update.
|
||||
#
|
||||
# it { should_not allow_mass_assignment_of(:password) }
|
||||
# it { should allow_mass_assignment_of(:first_name) }
|
||||
#
|
||||
def allow_mass_assignment_of(value)
|
||||
AllowMassAssignmentOfMatcher.new(value)
|
||||
end
|
||||
|
||||
class AllowMassAssignmentOfMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute.to_s
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
if attr_mass_assignable?
|
||||
if whitelisting?
|
||||
@failure_message = "#{@attribute} was made accessible"
|
||||
else
|
||||
if protected_attributes.empty?
|
||||
@failure_message = "no attributes were protected"
|
||||
else
|
||||
@failure_message = "#{class_name} is protecting " <<
|
||||
"#{protected_attributes.to_a.to_sentence}, " <<
|
||||
"but not #{@attribute}."
|
||||
end
|
||||
end
|
||||
true
|
||||
else
|
||||
if whitelisting?
|
||||
@negative_failure_message =
|
||||
"Expected #{@attribute} to be accessible"
|
||||
else
|
||||
@negative_failure_message =
|
||||
"Did not expect #{@attribute} to be protected"
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"protect #{@attribute} from mass updates"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def protected_attributes
|
||||
@protected_attributes ||= (@subject.class.protected_attributes || [])
|
||||
end
|
||||
|
||||
def accessible_attributes
|
||||
@accessible_attributes ||= (@subject.class.accessible_attributes || [])
|
||||
end
|
||||
|
||||
def whitelisting?
|
||||
!accessible_attributes.empty?
|
||||
end
|
||||
|
||||
def attr_mass_assignable?
|
||||
if whitelisting?
|
||||
accessible_attributes.include?(@attribute)
|
||||
else
|
||||
!protected_attributes.include?(@attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,102 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that the attribute can be set to the given value.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
||||
# translation for :invalid.
|
||||
#
|
||||
# Example:
|
||||
# it { should_not allow_value('bad').for(:isbn) }
|
||||
# it { should allow_value("isbn 1 2345 6789 0").for(:isbn) }
|
||||
#
|
||||
def allow_value(value)
|
||||
AllowValueMatcher.new(value)
|
||||
end
|
||||
|
||||
class AllowValueMatcher # :nodoc:
|
||||
include Helpers
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
@attribute = attribute
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(instance)
|
||||
@instance = instance
|
||||
@expected_message ||= :invalid
|
||||
if Symbol === @expected_message
|
||||
@expected_message = default_error_message(@expected_message)
|
||||
end
|
||||
@instance.send("#{@attribute}=", @value)
|
||||
!errors_match?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Did not expect #{expectation}, got error: #{@matched_error}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Expected #{expectation}, got #{error_description}"
|
||||
end
|
||||
|
||||
def description
|
||||
"allow #{@attribute} to be set to #{@value.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def errors_match?
|
||||
@instance.valid?
|
||||
@errors = @instance.errors.on(@attribute)
|
||||
@errors = [@errors] unless @errors.is_a?(Array)
|
||||
errors_match_regexp? || errors_match_string?
|
||||
end
|
||||
|
||||
def errors_match_regexp?
|
||||
if Regexp === @expected_message
|
||||
@matched_error = @errors.detect { |e| e =~ @expected_message }
|
||||
!@matched_error.nil?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def errors_match_string?
|
||||
if @errors.include?(@expected_message)
|
||||
@matched_error = @expected_message
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"errors to include #{@expected_message.inspect} " <<
|
||||
"when #{@attribute} is set to #{@value.inspect}"
|
||||
end
|
||||
|
||||
def error_description
|
||||
if @instance.errors.empty?
|
||||
"no errors"
|
||||
else
|
||||
"errors: #{pretty_error_messages(@instance)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,226 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensure that the belongs_to relationship exists.
|
||||
#
|
||||
# it { should belong_to(:parent) }
|
||||
#
|
||||
def belong_to(name)
|
||||
AssociationMatcher.new(:belongs_to, name)
|
||||
end
|
||||
|
||||
# Ensures that the has_many relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>through</tt> - association name for <tt>has_many :through</tt>
|
||||
# * <tt>dependent</tt> - tests that the association makes use of the
|
||||
# dependent option.
|
||||
#
|
||||
# Example:
|
||||
# it { should_have_many(:friends) }
|
||||
# it { should_have_many(:enemies).through(:friends) }
|
||||
# it { should_have_many(:enemies).dependent(:destroy) }
|
||||
#
|
||||
def have_many(name)
|
||||
AssociationMatcher.new(:has_many, name)
|
||||
end
|
||||
|
||||
# Ensure that the has_one relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:dependent</tt> - tests that the association makes use of the
|
||||
# dependent option.
|
||||
#
|
||||
# Example:
|
||||
# it { should have_one(:god) } # unless hindu
|
||||
#
|
||||
def have_one(name)
|
||||
AssociationMatcher.new(:has_one, name)
|
||||
end
|
||||
|
||||
# Ensures that the has_and_belongs_to_many relationship exists, and that
|
||||
# the join table is in place.
|
||||
#
|
||||
# it { should have_and_belong_to_many(:posts) }
|
||||
#
|
||||
def have_and_belong_to_many(name)
|
||||
AssociationMatcher.new(:has_and_belongs_to_many, name)
|
||||
end
|
||||
|
||||
class AssociationMatcher # :nodoc:
|
||||
def initialize(macro, name)
|
||||
@macro = macro
|
||||
@name = name
|
||||
end
|
||||
|
||||
def through(through)
|
||||
@through = through
|
||||
self
|
||||
end
|
||||
|
||||
def dependent(dependent)
|
||||
@dependent = dependent
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
association_exists? &&
|
||||
macro_correct? &&
|
||||
foreign_key_exists? &&
|
||||
through_association_valid? &&
|
||||
dependent_correct? &&
|
||||
join_table_exists?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "#{macro_description} #{@name}"
|
||||
description += " through #{@through}" if @through
|
||||
description += " dependent => #{@dependent}" if @dependent
|
||||
description
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def association_exists?
|
||||
if reflection.nil?
|
||||
@missing = "no association called #{@name}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def macro_correct?
|
||||
if reflection.macro == @macro
|
||||
true
|
||||
else
|
||||
@missing = "actual association type was #{reflection.macro}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def foreign_key_exists?
|
||||
!(belongs_foreign_key_missing? || has_foreign_key_missing?)
|
||||
end
|
||||
|
||||
def belongs_foreign_key_missing?
|
||||
@macro == :belongs_to && !class_has_foreign_key?(model_class)
|
||||
end
|
||||
|
||||
def has_foreign_key_missing?
|
||||
[:has_many, :has_one].include?(@macro) &&
|
||||
!through? &&
|
||||
!class_has_foreign_key?(associated_class)
|
||||
end
|
||||
|
||||
def through_association_valid?
|
||||
@through.nil? || (through_association_exists? && through_association_correct?)
|
||||
end
|
||||
|
||||
def through_association_exists?
|
||||
if through_reflection.nil?
|
||||
"#{model_class.name} does not have any relationship to #{@through}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def through_association_correct?
|
||||
if @through == reflection.options[:through]
|
||||
"Expected #{model_class.name} to have #{@name} through #{@through}, " <<
|
||||
" but got it through #{reflection.options[:through]}"
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def dependent_correct?
|
||||
if @dependent.nil? || @dependent.to_s == reflection.options[:dependent].to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{@name} should have #{@dependent} dependency"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def join_table_exists?
|
||||
if @macro != :has_and_belongs_to_many ||
|
||||
::ActiveRecord::Base.connection.tables.include?(join_table.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "join table #{join_table} doesn't exist"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def class_has_foreign_key?(klass)
|
||||
if klass.column_names.include?(foreign_key.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "#{klass} does not have a #{foreign_key} foreign key."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def join_table
|
||||
reflection.options[:join_table]
|
||||
end
|
||||
|
||||
def associated_class
|
||||
reflection.klass
|
||||
end
|
||||
|
||||
def foreign_key
|
||||
reflection.primary_key_name
|
||||
end
|
||||
|
||||
def through?
|
||||
reflection.options[:through]
|
||||
end
|
||||
|
||||
def reflection
|
||||
@reflection ||= model_class.reflect_on_association(@name)
|
||||
end
|
||||
|
||||
def through_reflection
|
||||
@through_reflection ||= model_class.reflect_on_association(@through)
|
||||
end
|
||||
|
||||
def expectation
|
||||
"#{model_class.name} to have a #{@macro} association called #{@name}"
|
||||
end
|
||||
|
||||
def macro_description
|
||||
case @macro.to_s
|
||||
when 'belongs_to' then 'belong to'
|
||||
when 'has_many' then 'have many'
|
||||
when 'has_one' then 'have one'
|
||||
when 'has_and_belongs_to_many' then
|
||||
'have and belong to many'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,87 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensure that the attribute's value is in the range specified
|
||||
#
|
||||
# Options:
|
||||
# * <tt>in_range</tt> - the range of allowed values for this attribute
|
||||
# * <tt>with_low_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
||||
# translation for :inclusion.
|
||||
# * <tt>with_high_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
||||
# translation for :inclusion.
|
||||
#
|
||||
# Example:
|
||||
# it { should ensure_inclusion_of(:age).in_range(0..100) }
|
||||
#
|
||||
def ensure_inclusion_of(attr)
|
||||
EnsureInclusionOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def in_range(range)
|
||||
@range = range
|
||||
@minimum = range.first
|
||||
@maximum = range.last
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
if message
|
||||
@low_message = message
|
||||
@high_message = message
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def with_low_message(message)
|
||||
@low_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def with_high_message(message)
|
||||
@high_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
"ensure inclusion of #{@attribute} in #{@range.inspect}"
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
|
||||
@low_message ||= :inclusion
|
||||
@high_message ||= :inclusion
|
||||
|
||||
disallows_lower_value &&
|
||||
allows_minimum_value &&
|
||||
disallows_higher_value &&
|
||||
allows_maximum_value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disallows_lower_value
|
||||
@minimum == 0 || disallows_value_of(@minimum - 1, @low_message)
|
||||
end
|
||||
|
||||
def disallows_higher_value
|
||||
disallows_value_of(@maximum + 1, @high_message)
|
||||
end
|
||||
|
||||
def allows_minimum_value
|
||||
allows_value_of(@minimum, @low_message)
|
||||
end
|
||||
|
||||
def allows_maximum_value
|
||||
allows_value_of(@maximum, @high_message)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,141 +0,0 @@
|
||||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Matchers
|
||||
|
||||
# Ensures that the length of the attribute is validated.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>is_at_least</tt> - minimum length of this attribute
|
||||
# * <tt>is_at_most</tt> - maximum length of this attribute
|
||||
# * <tt>is_equal_to</tt> - exact requred length of this attribute
|
||||
# * <tt>with_short_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
||||
# translation for :too_short.
|
||||
# * <tt>with_long_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
||||
# translation for :too_long.
|
||||
# * <tt>with_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
||||
# translation for :wrong_length. Used in conjunction with
|
||||
# <tt>is_equal_to</tt>.
|
||||
#
|
||||
# Examples:
|
||||
# it { should ensure_length_of(:password).
|
||||
# is_at_least(6).
|
||||
# is_at_most(20) }
|
||||
# it { should ensure_length_of(:name).
|
||||
# is_at_least(3).
|
||||
# with_short_message(/not long enough/) }
|
||||
# it { should ensure_length_of(:ssn).
|
||||
# is_equal_to(9).
|
||||
# with_message(/is invalid/) }
|
||||
def ensure_length_of(attr)
|
||||
EnsureLengthOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class EnsureLengthOfMatcher < ValidationMatcher # :nodoc:
|
||||
include Helpers
|
||||
|
||||
def is_at_least(length)
|
||||
@minimum = length
|
||||
@short_message ||= :too_short
|
||||
self
|
||||
end
|
||||
|
||||
def is_at_most(length)
|
||||
@maximum = length
|
||||
@long_message ||= :too_long
|
||||
self
|
||||
end
|
||||
|
||||
def is_equal_to(length)
|
||||
@minimum = length
|
||||
@maximum = length
|
||||
@short_message ||= :wrong_length
|
||||
self
|
||||
end
|
||||
|
||||
def with_short_message(message)
|
||||
@short_message = message if message
|
||||
self
|
||||
end
|
||||
alias_method :with_message, :with_short_message
|
||||
|
||||
def with_long_message(message)
|
||||
@long_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
description = "ensure #{@attribute} has a length "
|
||||
if @minimum && @maximum
|
||||
if @minimum == @maximum
|
||||
description << "of exactly #{@minimum}"
|
||||
else
|
||||
description << "between #{@minimum} and #{@maximum}"
|
||||
end
|
||||
else
|
||||
description << "of at least #{@minimum}" if @minimum
|
||||
description << "of at most #{@maximum}" if @maximum
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
translate_messages!
|
||||
disallows_lower_length &&
|
||||
allows_minimum_length &&
|
||||
((@minimum == @maximum) ||
|
||||
(disallows_higher_length &&
|
||||
allows_maximum_length))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def translate_messages!
|
||||
if Symbol === @short_message
|
||||
@short_message = default_error_message(@short_message,
|
||||
:count => @minimum)
|
||||
end
|
||||
|
||||
if Symbol === @long_message
|
||||
@long_message = default_error_message(@long_message,
|
||||
:count => @maximum)
|
||||
end
|
||||
end
|
||||
|
||||
def disallows_lower_length
|
||||
@minimum == 0 ||
|
||||
@minimum.nil? ||
|
||||
disallows_length_of(@minimum - 1, @short_message)
|
||||
end
|
||||
|
||||
def disallows_higher_length
|
||||
@maximum.nil? || disallows_length_of(@maximum + 1, @long_message)
|
||||
end
|
||||
|
||||
def allows_minimum_length
|
||||
allows_length_of(@minimum, @short_message)
|
||||
end
|
||||
|
||||
def allows_maximum_length
|
||||
allows_length_of(@maximum, @long_message)
|
||||
end
|
||||
|
||||
def allows_length_of(length, message)
|
||||
length.nil? || allows_value_of(string_of_length(length), message)
|
||||
end
|
||||
|
||||
def disallows_length_of(length, message)
|
||||
length.nil? || disallows_value_of(string_of_length(length), message)
|
||||
end
|
||||
|
||||
def string_of_length(length)
|
||||
'x' * length
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Reference in new issue