Initial import

This commit is contained in:
2008-03-02 16:04:34 -03:00
commit 5e4951fa47
798 changed files with 59730 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
* 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.

View File

@@ -0,0 +1,28 @@
*** 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

View File

@@ -0,0 +1,56 @@
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

View File

@@ -0,0 +1,294 @@
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

View File

@@ -0,0 +1,134 @@
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]

View File

@@ -0,0 +1,3 @@
require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models')

View File

@@ -0,0 +1,30 @@
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
}

View File

@@ -0,0 +1,12 @@
#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

View File

@@ -0,0 +1,32 @@
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

View File

@@ -0,0 +1,42 @@
# 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

View File

@@ -0,0 +1,45 @@
# 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

View File

@@ -0,0 +1,34 @@
$:.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

View File

@@ -0,0 +1,14 @@
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

View File

@@ -0,0 +1,133 @@
# 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

View File

@@ -0,0 +1,270 @@
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

View File

@@ -0,0 +1,46 @@
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

View File

@@ -0,0 +1,9 @@
module DrNicMagicModels #:nodoc:
module VERSION #:nodoc:
MAJOR = 0
MINOR = 9
TINY = 2
STRING = [MAJOR, MINOR, TINY].join('.')
end
end

View File

@@ -0,0 +1,33 @@
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

View File

@@ -0,0 +1,19 @@
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

View File

@@ -0,0 +1,66 @@
#!/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)

View File

@@ -0,0 +1,58 @@
#!/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.

View File

@@ -0,0 +1,72 @@
$:.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

View File

@@ -0,0 +1,13 @@
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
)

View File

@@ -0,0 +1,12 @@
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
)

View File

@@ -0,0 +1,10 @@
print "Using native Sqlite3\n"
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:dbfile => "test.db"
)

View File

@@ -0,0 +1,13 @@
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

View File

@@ -0,0 +1,10 @@
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

View File

@@ -0,0 +1,3 @@
first:
id: 1
name: kind

View File

@@ -0,0 +1,3 @@
first:
fun_user_id: 1
adjective_id: 1

View File

@@ -0,0 +1,4 @@
DROP TABLE fun_users;
DROP TABLE groups;
DROP TABLE group_memberships;
DROP TABLE group_tag;

View File

@@ -0,0 +1,56 @@
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;

View File

@@ -0,0 +1,55 @@
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;

View File

@@ -0,0 +1,49 @@
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`)
);

View File

@@ -0,0 +1,14 @@
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

View File

@@ -0,0 +1,4 @@
first_first:
id: 1
group_id: 1
fun_user_id: 1

View File

@@ -0,0 +1,11 @@
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

View File

@@ -0,0 +1,12 @@
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

View File

@@ -0,0 +1,2 @@
class FunUserPlus < FunUser
end

View File

@@ -0,0 +1,71 @@
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

View File

@@ -0,0 +1,61 @@
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

View File

@@ -0,0 +1,23 @@
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

View File

@@ -0,0 +1,20 @@
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

View File

@@ -0,0 +1,20 @@
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

View File

@@ -0,0 +1,404 @@
<!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>

View File

@@ -0,0 +1,291 @@
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

View File

@@ -0,0 +1,285 @@
/****************************************************************
* *
* 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)
}

View File

@@ -0,0 +1,96 @@
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;
}

View File

@@ -0,0 +1,3 @@
// <%= title %>
var version = <%= version.to_json %>;
<%= body %>

View File

@@ -0,0 +1,55 @@
<!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>

View File

@@ -0,0 +1,3 @@
// Version JS file
var version = "0.9.2";
MagicAnnouncement.show('magicmodels', version);

View File

@@ -0,0 +1,2 @@
h1. Version JS file
MagicAnnouncement.show('magicmodels', version);

View File

@@ -0,0 +1,4 @@
// Version JS file
var version = "0.9.2";
document.write(" - " + version);

View File

@@ -0,0 +1,3 @@
h1. Version JS file
document.write(" - " + version);