Gemify and upgrade to shoulda-2.11.3, remove dr_nic_magic_models

master
Alinson S. Xavier 12 years ago
parent 8058096f7e
commit 796c1a279e

@ -81,5 +81,5 @@ Rails::Initializer.run do |config|
config.gem "icalendar"
config.gem "will_paginate"
config.gem "calendar_helper"
config.gem 'thoughtbot-shoulda', :lib => 'shoulda/rails', :source => "http://gems.github.com"
config.gem "shoulda"
end

@ -6,17 +6,14 @@ activeresource (2.3.18)
activesupport (2.3.18)
acts_as_versioned (0.2.3)
atomic (1.1.10)
brazilian-rails (2.1.15)
brcep (2.1.15)
brcpfcnpj (2.1.15)
brdata (2.1.15)
brdinheiro (2.1.15)
brhelper (2.1.15)
brI18n (2.1.15)
brnumeros (2.1.15)
brstring (2.1.15)
bundler (1.3.5)
bundler-unload (1.0.1)
calendar_helper (0.2.5)
haml (3.1.8)
hoe (1.3.0)
hpricot (0.6)
@ -26,6 +23,7 @@ json_pure (1.8.0)
magic_encoding (0.0.2)
minitest (4.7.5)
multi_json (1.7.7)
open4 (1.3.0)
quietbacktrace (0.1.1)
rack (1.1.6, 1.0.1)
rails (2.3.18)
@ -34,6 +32,7 @@ redgreen (1.2.2)
rubyforge (2.0.4)
rubygems-update (1.4.2, 1.4.0, 1.3.2)
rvm (1.11.3.8)
shoulda (2.11.3)
sqlite3-ruby (1.3.1)
thread_safe (0.1.0)
tzinfo (0.3.37)

@ -46,7 +46,7 @@ class AttachmentsControllerTest < ActionController::TestCase
context "on get to :show" do
setup { get :show, :course_id => @course.id, :id => @att.id }
should_respond_with :success
should respond_with :success
should "link to the attachment" do
assert_select 'a[href=?]', download_course_attachment_url(@course, @att)
@ -59,8 +59,8 @@ class AttachmentsControllerTest < ActionController::TestCase
context "on get to :new" do
setup { get :new, :course_id => @course.id }
should_render_a_form
should_respond_with :success
#should render_a_form
should respond_with :success
end
context "on post to :create" do
@ -74,15 +74,15 @@ class AttachmentsControllerTest < ActionController::TestCase
assert @att
end
should_set_the_flash_to(/created/i)
should_redirect_to('the attachment') { course_attachment_url(@course, @att) }
should set_the_flash.to(/created/i)
should redirect_to('the attachment') { course_attachment_url(@course, @att) }
should_create_log_entry {[ AttachmentCreateLogEntry, @att.id, users(:bob).id ]}
end
context "on get to :edit" do
setup { get :edit, :course_id => @course.id, :id => @att.id }
should_render_a_form
should_render_template 'edit'
#should render_a_form
should render_template 'edit'
end
context "on post to :update" do
@ -91,8 +91,8 @@ class AttachmentsControllerTest < ActionController::TestCase
post :update, :course_id => @course.id, :id => @att.id, :attachment => { :description => @att.description, :path => "", :front_page => 't' }
end
should_not_set_the_flash
should_redirect_to('the attachment') { course_attachment_url(@course, @att) }
should_not set_the_flash
should redirect_to('the attachment') { course_attachment_url(@course, @att) }
should "not create a new log entry" do
assert_nil AttachmentEditLogEntry.find(:first, :conditions => { :target_id => @att.id })
@ -103,8 +103,8 @@ class AttachmentsControllerTest < ActionController::TestCase
setup do
post :update, :course_id => @course.id, :id => @att.id, :attachment => { :description => 'new description', :front_page => 't' }
end
should_set_the_flash_to(/updated/i)
should_redirect_to('the attachment') { course_attachment_url(@course, @att) }
should set_the_flash.to(/updated/i)
should redirect_to('the attachment') { course_attachment_url(@course, @att) }
should_create_log_entry {[ AttachmentEditLogEntry, @att.id, users(:bob).id ]}
end
@ -116,8 +116,8 @@ class AttachmentsControllerTest < ActionController::TestCase
teardown do
@new_data.close!
end
should_set_the_flash_to(/updated/i)
should_redirect_to('the attachment') { course_attachment_url(@course, @att) }
should set_the_flash.to(/updated/i)
should redirect_to('the attachment') { course_attachment_url(@course, @att) }
should_create_log_entry {[ AttachmentEditLogEntry, @att.id, users(:bob).id ]}
end
end
@ -125,8 +125,8 @@ class AttachmentsControllerTest < ActionController::TestCase
context "on post to :destroy" do
setup { post :destroy, :course_id => @course.id, :id => @att.id }
should_set_the_flash_to(/removed/i)
should_redirect_to('the course page'){ course_url(@course) }
should set_the_flash.to(/removed/i)
should redirect_to('the course page'){ course_url(@course) }
should_create_log_entry {[ AttachmentDeleteLogEntry, @att.id, users(:bob).id ]}
should "destroy the attachment" do
@ -141,8 +141,8 @@ class AttachmentsControllerTest < ActionController::TestCase
# post :undelete, :course_id => @course.id, :id => @att.id
# end
# should_set_the_flash_to(/restored/i)
# should_redirect_to('the attachment'){ course_attachment_url(@course, @att) }
# should set_the_flash.to(/restored/i)
# should redirect_to('the attachment'){ course_attachment_url(@course, @att) }
# should_create_log_entry {[ AttachmentRestoreLogEntry, @att.id, users(:bob).id ]}
# should "restore the attachment" do
@ -152,7 +152,7 @@ class AttachmentsControllerTest < ActionController::TestCase
context "on get to :download" do
setup { get :download, :course_id => @course.id, :id => @att.id }
should_respond_with :success
should respond_with :success
end
end
end

@ -41,8 +41,8 @@ class CoursesControllerTest < ActionController::TestCase
context "on get to :index" do
setup { get :index }
should_respond_with :success
should_render_template 'index'
should respond_with :success
should render_template 'index'
should "display the course list" do
assert_select 'h1', "Disciplinas #{App.current_period}"
@ -58,8 +58,8 @@ class CoursesControllerTest < ActionController::TestCase
context "on get to :show" do
setup { get :show, :id => @course.id }
should_respond_with :success
should_render_template 'show'
should respond_with :success
should render_template 'show'
should "display the course" do
assert_select 'a[href=?]', course_log_url(@course)

@ -28,15 +28,15 @@ class UsersControllerTest < ActionController::TestCase
context "on get to :dashboard" do
setup { get :dashboard }
should_respond_with :success
should_render_template "dashboard"
should respond_with :success
should render_template "dashboard"
end
context "on post to :logout" do
setup { get :logout }
should_respond_with :redirect
should_redirect_to('the main page') { index_url }
should respond_with :redirect
should redirect_to('the main page') { index_url }
should "log out" do
assert_nil session[:user_id]

@ -51,14 +51,14 @@ class WikiControllerTest < ActionController::TestCase
#context "on get to :index" do
# setup { get :index, :course_id => @course.id }
# should_redirect_to('the course page') { course_url(@course) }
# should redirect_to('the course page') { course_url(@course) }
#end
context "on get to :show" do
setup { get :show, :course_id => @course.id, :id => @wiki_page.id }
should_respond_with :success
should_render_template 'show'
should respond_with :success
should render_template 'show'
should "show the wiki page" do
assert_select 'h1.title', @wiki_page.title
@ -74,8 +74,8 @@ class WikiControllerTest < ActionController::TestCase
context "on get to :versions" do
setup { get :versions, :course_id => @course.id, :id => @wiki_page.id }
should_respond_with :success
should_render_template 'versions'
should respond_with :success
should render_template 'versions'
should "show the wiki page versions" do
@wiki_page.versions.each do |v|
@ -88,7 +88,7 @@ class WikiControllerTest < ActionController::TestCase
context "with valid markup" do
setup { get :preview, :text => "hello {$x$} <script>foo();</script> <i onclick='foo()'>x</i>" }
should_respond_with :success
should respond_with :success
should "display latex formulas" do
assert_select 'img[class=tex_inline]'
@ -102,14 +102,14 @@ class WikiControllerTest < ActionController::TestCase
context "with invalid markup" do
setup { get :preview, :text => "<a" }
should_respond_with :bad_request
should respond_with :bad_request
end
end
context "on get to :diff" do
setup { get :diff, :course_id => @course.id, :id => @wiki_page.id, :from => 1, :to => 2 }
should_respond_with :success
should_assign_to :diff
should respond_with :success
should assign_to :diff
end
end
@ -119,8 +119,8 @@ class WikiControllerTest < ActionController::TestCase
context "on get to :new" do
setup { get :new, :course_id => @course.id }
should_render_a_form
should_respond_with :success
#should render_a_form
should respond_with :success
end
context "on post to :create" do
@ -130,8 +130,8 @@ class WikiControllerTest < ActionController::TestCase
@wiki_page = @course.wiki_pages.find_by_title('test2')
end
should_set_the_flash_to(/created/i)
should_redirect_to('the wiki page') { course_wiki_instance_url(@course, @wiki_page) }
should set_the_flash.to(/created/i)
should redirect_to('the wiki page') { course_wiki_instance_url(@course, @wiki_page) }
should_create_log_entry {[ WikiCreateLogEntry, @wiki_page.id, users(:bob).id ]}
should "create a new wiki page" do
@ -144,8 +144,8 @@ class WikiControllerTest < ActionController::TestCase
context "on get to :edit" do
setup { get :edit, :course_id => @course.id, :id => @wiki_page.id }
should_render_a_form
should_render_template 'edit'
#should render_a_form
should render_template 'edit'
should "render a form with the correct fields" do
assert_select "input[name='wiki_page[title]'][value=?]", @wiki_page.title
@ -168,8 +168,8 @@ class WikiControllerTest < ActionController::TestCase
:title => @wiki_page.title, :content => @wiki_page.content}
end
should_not_set_the_flash
should_redirect_to('the wiki page') { course_wiki_instance_url(@course, @wiki_page) }
should_not set_the_flash
should redirect_to('the wiki page') { course_wiki_instance_url(@course, @wiki_page) }
should "not create a new log entry" do
assert_nil WikiEditLogEntry.find(:first, :conditions => { :target_id => @wiki_page.id })
@ -184,8 +184,8 @@ class WikiControllerTest < ActionController::TestCase
@wiki_page.reload
end
should_set_the_flash_to(/updated/i)
should_redirect_to('the wiki page') { course_wiki_instance_url(@course, @wiki_page) }
should set_the_flash.to(/updated/i)
should redirect_to('the wiki page') { course_wiki_instance_url(@course, @wiki_page) }
should_create_log_entry {[ WikiEditLogEntry, @wiki_page.id, users(:bob).id ]}
should "update the wiki page" do
@ -200,8 +200,8 @@ class WikiControllerTest < ActionController::TestCase
context "on post to :destroy" do
setup { post :destroy, :course_id => @course.id, :id => @wiki_page.id }
should_set_the_flash_to(/removed/i)
should_redirect_to('the course page') { course_url(@course) }
should set_the_flash.to(/removed/i)
should redirect_to('the course page') { course_url(@course) }
should_create_log_entry {[ WikiDeleteLogEntry, @wiki_page.id, users(:bob).id ]}
should "delete the wiki page" do
@ -217,7 +217,7 @@ class WikiControllerTest < ActionController::TestCase
get :move_up, :course_id => @course.id, :id => @another_wiki_page.id
end
should_redirect_to('the course page') { course_url(@course) }
should redirect_to('the course page') { course_url(@course) }
should "move the page up" do
@wiki_page.reload
@ -234,7 +234,7 @@ class WikiControllerTest < ActionController::TestCase
get :move_down, :course_id => @course.id, :id => @wiki_page.id
end
should_redirect_to('the course page') { course_url(@course) }
should redirect_to('the course page') { course_url(@course) }
should "move the page up" do
@wiki_page.reload
@ -246,17 +246,17 @@ class WikiControllerTest < ActionController::TestCase
end
#def test_should_accept_text_on_show
#def test_should accept_text_on_show
# get :show, :format => 'txt', :course_id => 1, :id => @wiki_page.id
# assert_formatted_response :text
#end
#def test_should_accept_html_on_versions
#def test_should accept_html_on_versions
# get :versions, :course_id => 1, :id => @wiki_page.id
# assert_response :success
#end
#def test_should_accept_xml_on_versions
#def test_should accept_xml_on_versions
# get :versions, :format => 'xml', :course_id => 1, :id => @wiki_page.id
# assert_formatted_response :xml, :versions
#end

@ -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

@ -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&#8217;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="/">&#x21A9; 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&#8217;s Magic Models</h1>
<p>If you&#8217;ve used Ruby on Rails you&#8217;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">&lt;</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">=&gt;</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&#8217;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">&lt;</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&#8230;</p>
<p><pre class="syntax">
<span class="keyword">class </span><span class="class">Person</span> <span class="punct">&lt;</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&#8230;</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&#8217;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&#8217;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&#8217;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">&lt;</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">=&gt;</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">=&gt;</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">=&gt;</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">=&gt;</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">=&gt;</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">=&gt;</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&#8217;s do a magic trick. First, let&#8217;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">&lt;</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">&lt;</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">&lt;</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">&lt;</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">&lt;</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">&lt;</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&#8217;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&#8217;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">=&gt;</span> <span class="comment">#&lt;Person:0x393e0f8 @attributes={&quot;lastname&quot;=&gt;&quot;&quot;, &quot;firstname&quot;=&gt;&quot;&quot;, &quot;email&quot;=&gt;&quot;&quot;}, @new_record=true&gt;</span>
<span class="ident">person</span><span class="punct">.</span><span class="ident">valid?</span>
<span class="punct">=&gt;</span> <span class="constant">false</span>
<span class="ident">person</span><span class="punct">.</span><span class="ident">errors</span>
<span class="punct">=&gt;</span> <span class="comment">#&lt;ActiveRecord::Errors:0x3537b38 @errors={</span>
<span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;[&quot;</span><span class="string">can't be blank</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">&quot;],</span>
<span class="punct">&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;[&quot;</span><span class="string">can't be blank</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">&quot;],</span>
<span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;[&quot;</span><span class="string">can't be blank</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">is too long (maximum is 255 characters)</span><span class="punct">&quot;]},</span>
<span class="attribute">@base</span><span class="punct">=</span><span class="comment">#&lt;Person:0x3538bf0 @errors=#&lt;ActiveRecord::Errors:0x3537b38 ...&gt;, @new_record=true, </span>
<span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">}&gt;&gt;</span>
</pre></p>
<p><strong>Kapoow!</strong> Instant validation! (NOTE: not as instant as it used to be &#8211; remember &#8211; you need to call <code>generate_validations</code> on each class as required)</p>
<p>Because you specified the three columns as <code>:null =&gt; 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&#8217;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&#8217;s just watch what Dr Nic&#8217;s Magic Models can do without any effort at all&#8230;</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">=&gt;</span> <span class="punct">&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span> <span class="symbol">:lastname</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="symbol">:email</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;)</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">=&gt;</span> <span class="punct">&quot;</span><span class="string">Magic Models Forum</span><span class="punct">&quot;,</span> <span class="symbol">:description</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">http://groups.google.com/magicmodels</span><span class="punct">&quot;)</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">=&gt;</span> <span class="ident">person</span><span class="punct">,</span> <span class="symbol">:group</span> <span class="punct">=&gt;</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">=&gt;</span> <span class="number">1</span>
<span class="ident">membership</span><span class="punct">.</span><span class="ident">person</span>
<span class="punct">=&gt;</span> <span class="punct">&lt;</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x38898e8</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span>
<span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;}&gt;</span>
<span class="ident">group</span><span class="punct">.</span><span class="ident">memberships</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x3c8cd70</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">group_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">person_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;}&gt;]</span>
</pre></p>
<p>That final association trick is a ripper. Automatic generation of <code>has_many :through</code> associations&#8230;</p>
<p><pre class="syntax">
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">groups</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Group</span><span class="punct">:</span><span class="number">0x39047e0</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">name</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Magic Models Forum</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">description</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">}&gt;]</span>
<span class="punct">&gt;&gt;</span> <span class="ident">group</span><span class="punct">.</span><span class="ident">people</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x3c33580</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span>
<span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;}&gt;]</span>
</pre></p>
<h3>Drum roll&#8230;</h3>
<p>Ladies and gentlemen. For my final feat of magical mastery, I&#8217;ll ask you to do
something you&#8217;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&#8230; be fearless!)
<pre>rm app/models/*.rb</pre>
<p>Re-launch your console.</p>
<p><strong>drums are still rolling&#8230;</strong></p>
<p>Be prepared to applaud loudly&#8230;</p>
<p><pre class="syntax">
<span class="punct">&gt;&gt;</span> <span class="constant">Person</span>
<span class="punct">=&gt;</span> <span class="constant">Person</span>
</pre></p>
<p>You applaud loudly, but watch for more&#8230;</p>
<p><pre class="syntax">
<span class="punct">&gt;&gt;</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">=&gt;</span> <span class="constant">false</span>
<span class="punct">&gt;&gt;</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">=&gt;</span> <span class="punct">&lt;</span><span class="constant">Person</span><span class="punct">:</span><span class="number">0x3958930</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">lastname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Williams</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">firstname</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Nic</span><span class="punct">&quot;,</span>
<span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">email</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">drnicwilliams@gmail.com</span><span class="punct">&quot;}&gt;</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">valid?</span>
<span class="punct">=&gt;</span> <span class="constant">true</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">memberships</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x393a000</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">group_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">person_id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;}&gt;]</span>
<span class="punct">&gt;&gt;</span> <span class="ident">person</span><span class="punct">.</span><span class="ident">groups</span>
<span class="punct">=&gt;</span> <span class="punct">[&lt;</span><span class="constant">Group</span><span class="punct">:</span><span class="number">0x390df60</span> <span class="attribute">@attributes</span><span class="punct">={&quot;</span><span class="string">name</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">Magic Models Forum</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">id</span><span class="punct">&quot;=&gt;&quot;</span><span class="string">1</span><span class="punct">&quot;,</span> <span class="punct">&quot;</span><span class="string">description</span><span class="punct">&quot;=&gt;</span><span class="constant">nil</span><span class="punct">}&gt;]</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">=&gt;</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"># =&gt; 'blog_posts'</span>
</pre></p>
<h2>Dr Nic&#8217;s Blog</h2>
<p><a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a> &#8211; 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> &#8211; 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="/">&#x21A9; 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