From: Tom Hughes Date: Mon, 20 Apr 2009 09:17:10 +0000 (+0000) Subject: Merge vendor tree from api06 branch. X-Git-Tag: live~8318 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/77303f1e5ac466d59129f6cf083e5f6d6ac09288?hp=26f7024137f6e17968e03d8a1be6a4c76dd59421 Merge vendor tree from api06 branch. --- diff --git a/vendor/gems/composite_primary_keys-1.1.0/History.txt b/vendor/gems/composite_primary_keys-1.1.0/History.txt new file mode 100644 index 000000000..7016020dc --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/History.txt @@ -0,0 +1,148 @@ +== 1.1.0 2008-10-29 + +* fixes to get cpk working for Rails 2.1.2 + +== 1.0.10 2008-10-22 + +* add composite key where clause creator method [timurv] + +== 1.0.9 2008-09-08 + +* fix postgres tests +* fix for delete_records when has_many association has composite keys [darxriggs] +* more consistent table/column name quoting [pbrant] + +== 1.0.8 2008-08-27 + +* fix has_many :through for non composite models [thx rcarver] + +== 1.0.7 2008-08-12 + +* fix for the last fix -- when has_many is composite and belongs_to is single + +== 1.0.6 2008-08-06 + +* fix associations create + +== 1.0.5 2008-07-25 + +* fix for calculations with a group by clause [thx Sirius Black] + +== 1.0.4 2008-07-15 + +* support for oracle_enhanced adapter [thx Raimonds Simanovskis] + +== 1.0.3 2008-07-13 + +* more fixes and tests for has many through [thx Menno van der Sman] + +== 1.0.2 2008-06-07 + +* fix for has many through when through association has composite keys + +== 1.0.1 2008-06-06 + +* Oracle fixes + +== 1.0.0 2008-06-05 + +* Support for Rails 2.1 + +== 0.9.93 2008-06-01 + +* set fixed dependency on activerecord 2.0.2 + +== 0.9.92 2008-02-22 + +* Support for has_and_belongs_to_many + +== 0.9.91 2008-01-27 + +* Incremented activerecord dependency to 2.0.2 [thx emmanuel.pirsch] + +== 0.9.90 2008-01-27 + +* Trial release for rails/activerecord 2.0.2 supported + +== 0.9.1 2007-10-28 + +* Migrations fix - allow :primary_key => [:name] to work [no unit test] [thx Shugo Maeda] + +== 0.9.0 2007-09-28 + +* Added support for polymorphs [thx nerdrew] +* init.rb file so gem can be installed as a plugin for Rails [thx nerdrew] +* Added ibm_db support [thx K Venkatasubramaniyan] +* Support for cleaning dependents [thx K Venkatasubramaniyan] +* Rafactored db rake tasks into namespaces +* Added namespaced tests (e.g. mysql:test for test_mysql) + +== 0.8.6 / 2007-6-12 + +* 1 emergency fix due to Rails Core change + * Rails v7004 removed #quote; fixed with connection.quote_column_name [thx nerdrew] + +== 0.8.5 / 2007-6-5 + +* 1 change due to Rails Core change + * Can no longer use RAILS_CONNECTION_ADAPTERS from Rails core +* 7 dev improvement: + * Changed History.txt syntax to rdoc format + * Added deploy tasks + * Removed CHANGELOG + migrated into History.txt + * Changed PKG_NAME -> GEM_NAME in Rakefile + * Renamed README -> README.txt for :publish_docs task + * Added :check_version task + * VER => VERS in rakefile +* 1 website improvement: + * website/index.txt includes link to "8 steps to fix other ppls code" + +== 0.8.4 / 2007-5-3 + +* 1 bugfix + * Corrected ids_list => ids in the exception message. That'll teach me for not adding unit tests before fixing bugs. + +== 0.8.3 / 2007-5-3 + +* 1 bugfix + * Explicit reference to ::ActiveRecord::RecordNotFound +* 1 website addition: + * Added routing help [Pete Sumskas] + +== 0.8.2 / 2007-4-11 + +* 1 major enhancement: + * Oracle unit tests!! [Darrin Holst] + * And they work too + +== 0.8.1 / 2007-4-10 + +* 1 bug fix: + * Fixed the distinct(count) for oracle (removed 'as') + +== 0.8.0 / 2007-4-6 + +* 1 major enhancement: + * Support for calcualtions on associations +* 2 new DB supported: + * Tests run on sqlite + * Tests run on postgresql +* History.txt to keep track of changes like these +* Using Hoe for Rakefile +* Website generator rake tasks + +== 0.3.3 +* id= +* create now work + +== 0.1.4 +* it was important that #{primary_key} for composites --> 'key1,key2' and not 'key1key2' so created PrimaryKeys class + +== 0.0.1 +* Initial version +* set_primary_keys(*keys) is the activation class method to transform an ActiveRecord into a composite primary key AR +* find(*ids) supports the passing of + * id sets: Foo.find(2,1), + * lists of id sets: Foo.find([2,1], [7,3], [8,12]), + * and even stringified versions of the above: + * Foo.find '2,1' or Foo.find '2,1;7,3' diff --git a/vendor/gems/composite_primary_keys-1.1.0/Manifest.txt b/vendor/gems/composite_primary_keys-1.1.0/Manifest.txt new file mode 100644 index 000000000..2ca2fc8da --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/Manifest.txt @@ -0,0 +1,121 @@ +History.txt +Manifest.txt +README.txt +README_DB2.txt +Rakefile +init.rb +install.rb +lib/adapter_helper/base.rb +lib/adapter_helper/mysql.rb +lib/adapter_helper/oracle.rb +lib/adapter_helper/postgresql.rb +lib/adapter_helper/sqlite3.rb +lib/composite_primary_keys.rb +lib/composite_primary_keys/association_preload.rb +lib/composite_primary_keys/associations.rb +lib/composite_primary_keys/attribute_methods.rb +lib/composite_primary_keys/base.rb +lib/composite_primary_keys/calculations.rb +lib/composite_primary_keys/composite_arrays.rb +lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +lib/composite_primary_keys/fixtures.rb +lib/composite_primary_keys/migration.rb +lib/composite_primary_keys/reflection.rb +lib/composite_primary_keys/version.rb +loader.rb +local/database_connections.rb.sample +local/paths.rb.sample +local/tasks.rb.sample +scripts/console.rb +scripts/txt2html +scripts/txt2js +tasks/activerecord_selection.rake +tasks/databases.rake +tasks/databases/mysql.rake +tasks/databases/oracle.rake +tasks/databases/postgresql.rake +tasks/databases/sqlite3.rake +tasks/deployment.rake +tasks/local_setup.rake +tasks/website.rake +test/README_tests.txt +test/abstract_unit.rb +test/connections/native_ibm_db/connection.rb +test/connections/native_mysql/connection.rb +test/connections/native_oracle/connection.rb +test/connections/native_postgresql/connection.rb +test/connections/native_sqlite/connection.rb +test/fixtures/article.rb +test/fixtures/articles.yml +test/fixtures/comment.rb +test/fixtures/comments.yml +test/fixtures/db_definitions/db2-create-tables.sql +test/fixtures/db_definitions/db2-drop-tables.sql +test/fixtures/db_definitions/mysql.sql +test/fixtures/db_definitions/oracle.drop.sql +test/fixtures/db_definitions/oracle.sql +test/fixtures/db_definitions/postgresql.sql +test/fixtures/db_definitions/sqlite.sql +test/fixtures/department.rb +test/fixtures/departments.yml +test/fixtures/employee.rb +test/fixtures/employees.yml +test/fixtures/group.rb +test/fixtures/groups.yml +test/fixtures/hack.rb +test/fixtures/hacks.yml +test/fixtures/membership.rb +test/fixtures/membership_status.rb +test/fixtures/membership_statuses.yml +test/fixtures/memberships.yml +test/fixtures/product.rb +test/fixtures/product_tariff.rb +test/fixtures/product_tariffs.yml +test/fixtures/products.yml +test/fixtures/reading.rb +test/fixtures/readings.yml +test/fixtures/reference_code.rb +test/fixtures/reference_codes.yml +test/fixtures/reference_type.rb +test/fixtures/reference_types.yml +test/fixtures/street.rb +test/fixtures/streets.yml +test/fixtures/suburb.rb +test/fixtures/suburbs.yml +test/fixtures/tariff.rb +test/fixtures/tariffs.yml +test/fixtures/user.rb +test/fixtures/users.yml +test/hash_tricks.rb +test/plugins/pagination.rb +test/plugins/pagination_helper.rb +test/test_associations.rb +test/test_attribute_methods.rb +test/test_attributes.rb +test/test_clone.rb +test/test_composite_arrays.rb +test/test_create.rb +test/test_delete.rb +test/test_dummy.rb +test/test_find.rb +test/test_ids.rb +test/test_miscellaneous.rb +test/test_pagination.rb +test/test_polymorphic.rb +test/test_santiago.rb +test/test_tutorial_examle.rb +test/test_update.rb +tmp/test.db +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 diff --git a/vendor/gems/composite_primary_keys-1.1.0/README.txt b/vendor/gems/composite_primary_keys-1.1.0/README.txt new file mode 100644 index 000000000..11daeb922 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/README.txt @@ -0,0 +1,41 @@ += Composite Primary Keys for ActiveRecords + +== Summary + +ActiveRecords/Rails famously doesn't support composite primary keys. +This RubyGem extends the activerecord gem to provide CPK support. + +== Installation + + gem install composite_primary_keys + +== Usage + + require 'composite_primary_keys' + class ProductVariation + set_primary_keys :product_id, :variation_seq + end + + pv = ProductVariation.find(345, 12) + +It even supports composite foreign keys for associations. + +See http://compositekeys.rubyforge.org for more. + +== Running Tests + +See test/README.tests.txt + +== Url + +http://compositekeys.rubyforge.org + +== Questions, Discussion and Contributions + +http://groups.google.com/compositekeys + +== Author + +Written by Dr Nic Williams, drnicwilliams@gmail +Contributions by many! + diff --git a/vendor/gems/composite_primary_keys-1.1.0/README_DB2.txt b/vendor/gems/composite_primary_keys-1.1.0/README_DB2.txt new file mode 100644 index 000000000..b69505fdb --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/README_DB2.txt @@ -0,0 +1,33 @@ +Composite Primary key support for db2 + +== Driver Support == + +DB2 support requires the IBM_DB driver provided by http://rubyforge.org/projects/rubyibm/ +project. Install using gem install ibm_db. Tested against version 0.60 of the driver. +This rubyforge project appears to be permenant location for the IBM adapter. +Older versions of the driver available from IBM Alphaworks will not work. + +== Driver Bug and workaround provided as part of this plugin == + +Unlike the basic quote routine available for Rails AR, the DB2 adapter's quote +method doesn't return " column_name = 1 " when string values (integers in string type variable) +are passed for quoting numeric column. Rather it returns "column_name = '1'. +DB2 doesn't accept single quoting numeric columns in SQL. Currently, as part of +this plugin a fix is provided for the DB2 adapter since this plugin does +pass string values like this. Perhaps a patch should be sent to the DB2 adapter +project for a permanant fix. + +== Database Setup == + +Database must be manually created using a separate command. Read the rake task +for creating tables and change the db name, user and passwords accordingly. + +== Tested Database Server version == + +This is tested against DB2 v9.1 in Ubuntu Feisty Fawn (7.04) + +== Tested Database Client version == + +This is tested against DB2 v9.1 in Ubuntu Feisty Fawn (7.04) + + diff --git a/vendor/gems/composite_primary_keys-1.1.0/Rakefile b/vendor/gems/composite_primary_keys-1.1.0/Rakefile new file mode 100644 index 000000000..22c1fb664 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/Rakefile @@ -0,0 +1,65 @@ +require 'rubygems' +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' +require 'fileutils' +require 'hoe' +include FileUtils +require File.join(File.dirname(__FILE__), 'lib', 'composite_primary_keys', 'version') + +AUTHOR = "Dr Nic Williams" +EMAIL = "drnicwilliams@gmail.com" +DESCRIPTION = "Composite key support for ActiveRecords" +GEM_NAME = "composite_primary_keys" # what ppl will type to install your gem +if File.exists?("~/.rubyforge/user-config.yml") + # TODO this should prob go in a local/ file + config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml"))) + RUBYFORGE_USERNAME = config["username"] +end +RUBYFORGE_PROJECT = "compositekeys" +HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" + +REV = nil #File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil +VERS = ENV['VERSION'] || (CompositePrimaryKeys::VERSION::STRING + (REV ? ".#{REV}" : "")) +CLEAN.include ['**/.*.sw?', '*.gem', '.config','debug.log','*.db','logfile','log/**/*','**/.DS_Store', '.project'] +RDOC_OPTS = ['--quiet', '--title', "newgem 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 = [['activerecord', '>= 2.1.2']] #An array of rubygem dependencies. + #p.spec_extras - A hash of extra values to set in the gemspec. +end + +CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n") +PATH = RUBYFORGE_PROJECT +hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc') + +PROJECT_ROOT = File.expand_path(".") + +require 'loader' diff --git a/vendor/gems/composite_primary_keys-1.1.0/init.rb b/vendor/gems/composite_primary_keys-1.1.0/init.rb new file mode 100644 index 000000000..7ae5e5d4e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/init.rb @@ -0,0 +1,2 @@ +# Include hook code here +require_dependency 'composite_primary_keys' diff --git a/vendor/gems/composite_primary_keys-1.1.0/install.rb b/vendor/gems/composite_primary_keys-1.1.0/install.rb new file mode 100644 index 000000000..5be89cf10 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/install.rb @@ -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("composite_primary_keys", "composite_primary_keys.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 +} diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/base.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/base.rb new file mode 100644 index 000000000..36ed05a68 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/base.rb @@ -0,0 +1,63 @@ +module AdapterHelper + class Base + class << self + attr_accessor :adapter + + def load_connection_from_env(adapter) + self.adapter = adapter + unless ENV['cpk_adapters'] + puts error_msg_setup_helper + exit + end + + ActiveRecord::Base.configurations = YAML.load(ENV['cpk_adapters']) + unless spec = ActiveRecord::Base.configurations[adapter] + puts error_msg_adapter_helper + exit + end + spec[:adapter] = adapter + spec + end + + def error_msg_setup_helper + <<-EOS +Setup Helper: + CPK now has a place for your individual testing configuration. + That is, instead of hardcoding it in the Rakefile and test/connections files, + there is now a local/database_connections.rb file that is NOT in the + repository. Your personal DB information (username, password etc) can + be stored here without making it difficult to submit patches etc. + +Installation: + i) cp locals/database_connections.rb.sample locals/database_connections.rb + ii) For #{adapter} connection details see "Adapter Setup Helper" below. + iii) Rerun this task + +#{error_msg_adapter_helper} + +Current ENV: + #{ENV.inspect} + EOS + end + + def error_msg_adapter_helper + <<-EOS +Adapter Setup Helper: + To run #{adapter} tests, you need to setup your #{adapter} connections. + In your local/database_connections.rb file, within the ENV['cpk_adapter'] hash, add: + "#{adapter}" => { adapter settings } + + That is, it will look like: + ENV['cpk_adapters'] = { + "#{adapter}" => { + :adapter => "#{adapter}", + :username => "root", + :password => "root", + # ... + } + }.to_yaml + EOS + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/mysql.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/mysql.rb new file mode 100644 index 000000000..8762e1d73 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/mysql.rb @@ -0,0 +1,13 @@ +require File.join(File.dirname(__FILE__), 'base') + +module AdapterHelper + class MySQL < Base + class << self + def load_connection_from_env + spec = super('mysql') + spec[:database] ||= 'composite_primary_keys_unittest' + spec + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/oracle.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/oracle.rb new file mode 100644 index 000000000..76a9d19f4 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/oracle.rb @@ -0,0 +1,12 @@ +require File.join(File.dirname(__FILE__), 'base') + +module AdapterHelper + class Oracle < Base + class << self + def load_connection_from_env + spec = super('oracle') + spec + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/postgresql.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/postgresql.rb new file mode 100644 index 000000000..ea2c4bef1 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/postgresql.rb @@ -0,0 +1,13 @@ +require File.join(File.dirname(__FILE__), 'base') + +module AdapterHelper + class Postgresql < Base + class << self + def load_connection_from_env + spec = super('postgresql') + spec[:database] ||= 'composite_primary_keys_unittest' + spec + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/sqlite3.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/sqlite3.rb new file mode 100644 index 000000000..7a45d9fad --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/adapter_helper/sqlite3.rb @@ -0,0 +1,13 @@ +require File.join(File.dirname(__FILE__), 'base') + +module AdapterHelper + class Sqlite3 < Base + class << self + def load_connection_from_env + spec = super('sqlite3') + spec[:dbfile] ||= "tmp/test.db" + spec + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys.rb new file mode 100644 index 000000000..99b61407e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys.rb @@ -0,0 +1,55 @@ +#-- +# Copyright (c) 2006 Nic Williams +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +$:.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 + +require 'composite_primary_keys/fixtures' +require 'composite_primary_keys/composite_arrays' +require 'composite_primary_keys/associations' +require 'composite_primary_keys/association_preload' +require 'composite_primary_keys/reflection' +require 'composite_primary_keys/base' +require 'composite_primary_keys/calculations' +require 'composite_primary_keys/migration' +require 'composite_primary_keys/attribute_methods' + +ActiveRecord::Base.class_eval do + include CompositePrimaryKeys::ActiveRecord::Base +end + +Dir[File.dirname(__FILE__) + '/composite_primary_keys/connection_adapters/*.rb'].each do |adapter| + begin + require adapter.gsub('.rb','') + rescue MissingSourceFile + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/association_preload.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/association_preload.rb new file mode 100644 index 000000000..54e5eeb0e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/association_preload.rb @@ -0,0 +1,236 @@ +module CompositePrimaryKeys + module ActiveRecord + module AssociationPreload + def self.append_features(base) + super + base.send(:extend, ClassMethods) + end + + # Composite key versions of Association functions + module ClassMethods + def preload_has_and_belongs_to_many_association(records, reflection, preload_options={}) + table_name = reflection.klass.quoted_table_name + id_to_record_map, ids = construct_id_map(records) + records.each {|record| record.send(reflection.name).loaded} + options = reflection.options + + if composite? + primary_key = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP) + where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys| + "(" + keys.map{|key| "t0.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")" + end.join(" OR ") + + conditions = [where, ids].flatten + joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{full_composite_join_clause(reflection, reflection.klass.table_name, reflection.klass.primary_key, 't0', reflection.association_foreign_key)}" + parent_primary_keys = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP).map{|k| "t0.#{connection.quote_column_name(k)}"} + parent_record_id = connection.concat(*parent_primary_keys.zip(["','"] * (parent_primary_keys.size - 1)).flatten.compact) + else + conditions = ["t0.#{connection.quote_column_name(reflection.primary_key_name)} IN (?)", ids] + joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{connection.quote_column_name(reflection.klass.primary_key)} = t0.#{connection.quote_column_name(reflection.association_foreign_key)})" + parent_record_id = reflection.primary_key_name + end + + conditions.first << append_conditions(reflection, preload_options) + + associated_records = reflection.klass.find(:all, + :conditions => conditions, + :include => options[:include], + :joins => joins, + :select => "#{options[:select] || table_name+'.*'}, #{parent_record_id} as parent_record_id_", + :order => options[:order]) + + set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'parent_record_id_') + end + + def preload_has_many_association(records, reflection, preload_options={}) + id_to_record_map, ids = construct_id_map(records) + records.each {|record| record.send(reflection.name).loaded} + options = reflection.options + + if options[:through] + through_records = preload_through_records(records, reflection, options[:through]) + through_reflection = reflections[options[:through]] + through_primary_key = through_reflection.primary_key_name + + unless through_records.empty? + source = reflection.source_reflection.name + #add conditions from reflection! + through_records.first.class.preload_associations(through_records, source, reflection.options) + through_records.each do |through_record| + key = through_primary_key.to_s.split(CompositePrimaryKeys::ID_SEP).map{|k| through_record.send(k)}.join(CompositePrimaryKeys::ID_SEP) + add_preloaded_records_to_collection(id_to_record_map[key], reflection.name, through_record.send(source)) + end + end + else + associated_records = find_associated_records(ids, reflection, preload_options) + set_association_collection_records(id_to_record_map, reflection.name, associated_records, reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP)) + end + end + + def preload_through_records(records, reflection, through_association) + through_reflection = reflections[through_association] + through_primary_key = through_reflection.primary_key_name + + if reflection.options[:source_type] + interface = reflection.source_reflection.options[:foreign_type] + preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]} + + records.compact! + records.first.class.preload_associations(records, through_association, preload_options) + + # Dont cache the association - we would only be caching a subset + through_records = [] + records.each do |record| + proxy = record.send(through_association) + + if proxy.respond_to?(:target) + through_records << proxy.target + proxy.reset + else # this is a has_one :through reflection + through_records << proxy if proxy + end + end + through_records.flatten! + else + records.first.class.preload_associations(records, through_association) + through_records = records.map {|record| record.send(through_association)}.flatten + end + + through_records.compact! + through_records + end + + def preload_belongs_to_association(records, reflection, preload_options={}) + options = reflection.options + primary_key_name = reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP) + + if options[:polymorphic] + raise AssociationNotSupported, "Polymorphic joins not supported for composite keys" + else + # I need to keep the original ids for each record (as opposed to the stringified) so + # that they get properly converted for each db so the id_map ends up looking like: + # + # { '1,2' => {:id => [1,2], :records => [...records...]}} + id_map = {} + + records.each do |record| + key = primary_key_name.map{|k| record.attributes[k]} + key_as_string = key.join(CompositePrimaryKeys::ID_SEP) + + if key_as_string + mapped_records = (id_map[key_as_string] ||= {:id => key, :records => []}) + mapped_records[:records] << record + end + end + + + klasses_and_ids = [[reflection.klass.name, id_map]] + end + + klasses_and_ids.each do |klass_and_id| + klass_name, id_map = *klass_and_id + klass = klass_name.constantize + table_name = klass.quoted_table_name + connection = reflection.active_record.connection + + if composite? + primary_key = klass.primary_key.to_s.split(CompositePrimaryKeys::ID_SEP) + ids = id_map.keys.uniq.map {|id| id_map[id][:id]} + + where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys| + "(" + keys.map{|key| "#{table_name}.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")" + end.join(" OR ") + + conditions = [where, ids].flatten + else + conditions = ["#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)", id_map.keys.uniq] + end + + conditions.first << append_conditions(reflection, preload_options) + + associated_records = klass.find(:all, + :conditions => conditions, + :include => options[:include], + :select => options[:select], + :joins => options[:joins], + :order => options[:order]) + + set_association_single_records(id_map, reflection.name, associated_records, primary_key) + end + end + + def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key) + associated_records.each do |associated_record| + associated_record_key = associated_record[key] + associated_record_key = associated_record_key.is_a?(Array) ? associated_record_key.join(CompositePrimaryKeys::ID_SEP) : associated_record_key.to_s + mapped_records = id_to_record_map[associated_record_key] + add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record) + end + end + + def set_association_single_records(id_to_record_map, reflection_name, associated_records, key) + seen_keys = {} + associated_records.each do |associated_record| + associated_record_key = associated_record[key] + associated_record_key = associated_record_key.is_a?(Array) ? associated_record_key.join(CompositePrimaryKeys::ID_SEP) : associated_record_key.to_s + + #this is a has_one or belongs_to: there should only be one record. + #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please + # only one row per distinct foo_id' so this where we enforce that + next if seen_keys[associated_record_key] + seen_keys[associated_record_key] = true + mapped_records = id_to_record_map[associated_record_key][:records] + mapped_records.each do |mapped_record| + mapped_record.send("set_#{reflection_name}_target", associated_record) + end + end + end + + def find_associated_records(ids, reflection, preload_options) + options = reflection.options + table_name = reflection.klass.quoted_table_name + + if interface = reflection.options[:as] + raise AssociationNotSupported, "Polymorphic joins not supported for composite keys" + else + connection = reflection.active_record.connection + foreign_key = reflection.primary_key_name + conditions = ["#{table_name}.#{connection.quote_column_name(foreign_key)} IN (?)", ids] + + if composite? + foreign_keys = foreign_key.to_s.split(CompositePrimaryKeys::ID_SEP) + + where = (foreign_keys * ids.size).in_groups_of(foreign_keys.size).map do |keys| + "(" + keys.map{|key| "#{table_name}.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")" + end.join(" OR ") + + conditions = [where, ids].flatten + end + end + + conditions.first << append_conditions(reflection, preload_options) + + reflection.klass.find(:all, + :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), + :include => preload_options[:include] || options[:include], + :conditions => conditions, + :joins => options[:joins], + :group => preload_options[:group] || options[:group], + :order => preload_options[:order] || options[:order]) + end + + def full_composite_join_clause(reflection, table1, full_keys1, table2, full_keys2) + connection = reflection.active_record.connection + full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String) + full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String) + where_clause = [full_keys1, full_keys2].transpose.map do |key_pair| + quoted1 = connection.quote_table_name(table1) + quoted2 = connection.quote_table_name(table2) + "#{quoted1}.#{connection.quote_column_name(key_pair.first)}=#{quoted2}.#{connection.quote_column_name(key_pair.last)}" + end.join(" AND ") + "(#{where_clause})" + end + end + end + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/associations.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/associations.rb new file mode 100644 index 000000000..4ea4a7b02 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/associations.rb @@ -0,0 +1,428 @@ +module CompositePrimaryKeys + module ActiveRecord + module Associations + def self.append_features(base) + super + base.send(:extend, ClassMethods) + end + + # Composite key versions of Association functions + module ClassMethods + + def construct_counter_sql_with_included_associations(options, join_dependency) + scope = scope(:find) + sql = "SELECT COUNT(DISTINCT #{quoted_table_columns(primary_key)})" + + # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. + if !self.connection.supports_count_distinct? + sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{quoted_table_columns(primary_key)}" + end + + sql << " FROM #{quoted_table_name} " + sql << join_dependency.join_associations.collect{|join| join.association_join }.join + + add_joins!(sql, options, scope) + add_conditions!(sql, options[:conditions], scope) + add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + + add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) + + if !self.connection.supports_count_distinct? + sql << ")" + end + + return sanitize_sql(sql) + end + + def construct_finder_sql_with_included_associations(options, join_dependency) + scope = scope(:find) + sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " + sql << join_dependency.join_associations.collect{|join| join.association_join }.join + + add_joins!(sql, options, scope) + add_conditions!(sql, options[:conditions], scope) + add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit] + + sql << "ORDER BY #{options[:order]} " if options[:order] + + add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) + + return sanitize_sql(sql) + end + + def table_columns(columns) + columns.collect {|column| "#{self.quoted_table_name}.#{connection.quote_column_name(column)}"} + end + + def quoted_table_columns(columns) + table_columns(columns).join(ID_SEP) + end + + end + + end + end +end + +module ActiveRecord::Associations::ClassMethods + class JoinDependency + def construct_association(record, join, row) + case join.reflection.macro + when :has_many, :has_and_belongs_to_many + collection = record.send(join.reflection.name) + collection.loaded + + join_aliased_primary_keys = join.active_record.composite? ? + join.aliased_primary_key : [join.aliased_primary_key] + return nil if + record.id.to_s != join.parent.record_id(row).to_s or not + join_aliased_primary_keys.select {|key| row[key].nil?}.blank? + association = join.instantiate(row) + collection.target.push(association) unless collection.target.include?(association) + when :has_one, :belongs_to + return if record.id.to_s != join.parent.record_id(row).to_s or + [*join.aliased_primary_key].any? { |key| row[key].nil? } + association = join.instantiate(row) + record.send("set_#{join.reflection.name}_target", association) + else + raise ConfigurationError, "unknown macro: #{join.reflection.macro}" + end + return association + end + + class JoinBase + def aliased_primary_key + active_record.composite? ? + primary_key.inject([]) {|aliased_keys, key| aliased_keys << "#{ aliased_prefix }_r#{aliased_keys.length}"} : + "#{ aliased_prefix }_r0" + end + + def record_id(row) + active_record.composite? ? + aliased_primary_key.map {|key| row[key]}.to_composite_ids : + row[aliased_primary_key] + end + + def column_names_with_alias + unless @column_names_with_alias + @column_names_with_alias = [] + keys = active_record.composite? ? primary_key.map(&:to_s) : [primary_key] + (keys + (column_names - keys)).each_with_index do |column_name, i| + @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"] + end + end + return @column_names_with_alias + end + end + + class JoinAssociation < JoinBase + alias single_association_join association_join + def association_join + reflection.active_record.composite? ? composite_association_join : single_association_join + end + + def composite_association_join + join = case reflection.macro + when :has_and_belongs_to_many + " LEFT OUTER JOIN %s ON %s " % [ + table_alias_for(options[:join_table], aliased_join_table_name), + composite_join_clause( + full_keys(aliased_join_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key), + full_keys(reflection.active_record.table_name, reflection.active_record.primary_key) + ) + ] + + " LEFT OUTER JOIN %s ON %s " % [ + table_name_and_alias, + composite_join_clause( + full_keys(aliased_table_name, klass.primary_key), + full_keys(aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key) + ) + ] + when :has_many, :has_one + case + when reflection.macro == :has_many && reflection.options[:through] + through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : '' + if through_reflection.options[:as] # has_many :through against a polymorphic join + raise AssociationNotSupported, "Polymorphic joins not supported for composite keys" + else + if source_reflection.macro == :has_many && source_reflection.options[:as] + raise AssociationNotSupported, "Polymorphic joins not supported for composite keys" + else + case source_reflection.macro + when :belongs_to + first_key = primary_key + second_key = options[:foreign_key] || klass.to_s.classify.foreign_key + when :has_many + first_key = through_reflection.klass.to_s.classify.foreign_key + second_key = options[:foreign_key] || primary_key + end + + " LEFT OUTER JOIN %s ON %s " % [ + table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), + composite_join_clause( + full_keys(aliased_join_table_name, through_reflection.primary_key_name), + full_keys(parent.aliased_table_name, parent.primary_key) + ) + ] + + " LEFT OUTER JOIN %s ON %s " % [ + table_name_and_alias, + composite_join_clause( + full_keys(aliased_table_name, first_key), + full_keys(aliased_join_table_name, second_key) + ) + ] + end + end + + when reflection.macro == :has_many && reflection.options[:as] + raise AssociationNotSupported, "Polymorphic joins not supported for composite keys" + when reflection.macro == :has_one && reflection.options[:as] + raise AssociationNotSupported, "Polymorphic joins not supported for composite keys" + else + foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key + " LEFT OUTER JOIN %s ON %s " % [ + table_name_and_alias, + composite_join_clause( + full_keys(aliased_table_name, foreign_key), + full_keys(parent.aliased_table_name, parent.primary_key)), + ] + end + when :belongs_to + " LEFT OUTER JOIN %s ON %s " % [ + table_name_and_alias, + composite_join_clause( + full_keys(aliased_table_name, reflection.klass.primary_key), + full_keys(parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key)), + ] + else + "" + end || '' + join << %(AND %s.%s = %s ) % [ + aliased_table_name, + reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column), + klass.connection.quote(klass.name)] unless klass.descends_from_active_record? + join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions] + join + end + + def full_keys(table_name, keys) + connection = reflection.active_record.connection + quoted_table_name = connection.quote_table_name(table_name) + if keys.is_a?(Array) + keys.collect {|key| "#{quoted_table_name}.#{connection.quote_column_name(key)}"}.join(CompositePrimaryKeys::ID_SEP) + else + "#{quoted_table_name}.#{connection.quote_column_name(keys)}" + end + end + + def composite_join_clause(full_keys1, full_keys2) + full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String) + full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String) + where_clause = [full_keys1, full_keys2].transpose.map do |key1, key2| + "#{key1}=#{key2}" + end.join(" AND ") + "(#{where_clause})" + end + end + end +end + +module ActiveRecord::Associations + class AssociationProxy #:nodoc: + + def composite_where_clause(full_keys, ids) + full_keys = full_keys.split(CompositePrimaryKeys::ID_SEP) if full_keys.is_a?(String) + + if ids.is_a?(String) + ids = [[ids]] + elsif not ids.first.is_a?(Array) # if single comp key passed, turn into an array of 1 + ids = [ids.to_composite_ids] + end + + where_clause = ids.map do |id_set| + transposed = id_set.size == 1 ? [[full_keys, id_set.first]] : [full_keys, id_set].transpose + transposed.map do |full_key, id| + "#{full_key.to_s}=#{@reflection.klass.sanitize(id)}" + end.join(" AND ") + end.join(") OR (") + + "(#{where_clause})" + end + + def composite_join_clause(full_keys1, full_keys2) + full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String) + full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String) + + where_clause = [full_keys1, full_keys2].transpose.map do |key1, key2| + "#{key1}=#{key2}" + end.join(" AND ") + + "(#{where_clause})" + end + + def full_composite_join_clause(table1, full_keys1, table2, full_keys2) + connection = @reflection.active_record.connection + full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String) + full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String) + + quoted1 = connection.quote_table_name(table1) + quoted2 = connection.quote_table_name(table2) + + where_clause = [full_keys1, full_keys2].transpose.map do |key_pair| + "#{quoted1}.#{connection.quote_column_name(key_pair.first)}=#{quoted2}.#{connection.quote_column_name(key_pair.last)}" + end.join(" AND ") + + "(#{where_clause})" + end + + def full_keys(table_name, keys) + connection = @reflection.active_record.connection + quoted_table_name = connection.quote_table_name(table_name) + keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String) + if keys.is_a?(Array) + keys.collect {|key| "#{quoted_table_name}.#{connection.quote_column_name(key)}"}.join(CompositePrimaryKeys::ID_SEP) + else + "#{quoted_table_name}.#{connection.quote_column_name(keys)}" + end + end + + def full_columns_equals(table_name, keys, quoted_ids) + connection = @reflection.active_record.connection + quoted_table_name = connection.quote_table_name(table_name) + if keys.is_a?(Symbol) or (keys.is_a?(String) and keys == keys.to_s.split(CompositePrimaryKeys::ID_SEP)) + return "#{quoted_table_name}.#{connection.quote_column_name(keys)} = #{quoted_ids}" + end + keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String) + quoted_ids = quoted_ids.split(CompositePrimaryKeys::ID_SEP) if quoted_ids.is_a?(String) + keys_ids = [keys, quoted_ids].transpose + keys_ids.collect {|key, id| "(#{quoted_table_name}.#{connection.quote_column_name(key)} = #{id})"}.join(' AND ') + end + + def set_belongs_to_association_for(record) + if @reflection.options[:as] + record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record? + record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s + else + key_values = @reflection.primary_key_name.to_s.split(CompositePrimaryKeys::ID_SEP).zip([@owner.id].flatten) + key_values.each{|key, value| record[key] = value} unless @owner.new_record? + end + end + end + + class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: + def construct_sql + @reflection.options[:finder_sql] &&= interpolate_sql(@reflection.options[:finder_sql]) + + if @reflection.options[:finder_sql] + @finder_sql = @reflection.options[:finder_sql] + else + @finder_sql = full_columns_equals(@reflection.options[:join_table], @reflection.primary_key_name, @owner.quoted_id) + @finder_sql << " AND (#{conditions})" if conditions + end + + @join_sql = "INNER JOIN #{@reflection.active_record.connection.quote_table_name(@reflection.options[:join_table])} ON " + + full_composite_join_clause(@reflection.klass.table_name, @reflection.klass.primary_key, @reflection.options[:join_table], @reflection.association_foreign_key) + end + end + + class HasManyAssociation < AssociationCollection #:nodoc: + def construct_sql + case + when @reflection.options[:finder_sql] + @finder_sql = interpolate_sql(@reflection.options[:finder_sql]) + + when @reflection.options[:as] + @finder_sql = + "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " + + "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}" + @finder_sql << " AND (#{conditions})" if conditions + + else + @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id) + @finder_sql << " AND (#{conditions})" if conditions + end + + if @reflection.options[:counter_sql] + @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) + elsif @reflection.options[:finder_sql] + # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ + @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } + @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) + else + @counter_sql = @finder_sql + end + end + + def delete_records(records) + if @reflection.options[:dependent] + records.each { |r| r.destroy } + else + connection = @reflection.active_record.connection + field_names = @reflection.primary_key_name.split(',') + field_names.collect! {|n| connection.quote_column_name(n) + " = NULL"} + records.each do |r| + where_clause = nil + + if r.quoted_id.to_s.include?(CompositePrimaryKeys::ID_SEP) + where_clause_terms = [@reflection.klass.primary_key, r.quoted_id].transpose.map do |pair| + "(#{connection.quote_column_name(pair[0])} = #{pair[1]})" + end + where_clause = where_clause_terms.join(" AND ") + else + where_clause = connection.quote_column_name(@reflection.klass.primary_key) + ' = ' + r.quoted_id + end + + @reflection.klass.update_all( field_names.join(',') , where_clause) + end + end + end + end + + class HasOneAssociation < BelongsToAssociation #:nodoc: + def construct_sql + case + when @reflection.options[:as] + @finder_sql = + "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " + + "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}" + else + @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id) + end + + @finder_sql << " AND (#{conditions})" if conditions + end + end + + class HasManyThroughAssociation < HasManyAssociation #:nodoc: + def construct_conditions_with_composite_keys + if @reflection.through_reflection.options[:as] + construct_conditions_without_composite_keys + else + conditions = full_columns_equals(@reflection.through_reflection.table_name, @reflection.through_reflection.primary_key_name, @owner.quoted_id) + conditions << " AND (#{sql_conditions})" if sql_conditions + conditions + end + end + alias_method_chain :construct_conditions, :composite_keys + + def construct_joins_with_composite_keys(custom_joins = nil) + if @reflection.through_reflection.options[:as] || @reflection.source_reflection.options[:as] + construct_joins_without_composite_keys(custom_joins) + else + if @reflection.source_reflection.macro == :belongs_to + reflection_primary_key = @reflection.klass.primary_key + source_primary_key = @reflection.source_reflection.primary_key_name + else + reflection_primary_key = @reflection.source_reflection.primary_key_name + source_primary_key = @reflection.klass.primary_key + end + + "INNER JOIN %s ON %s #{@reflection.options[:joins]} #{custom_joins}" % [ + @reflection.through_reflection.quoted_table_name, + composite_join_clause(full_keys(@reflection.table_name, reflection_primary_key), full_keys(@reflection.through_reflection.table_name, source_primary_key)) + ] + end + end + alias_method_chain :construct_joins, :composite_keys + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/attribute_methods.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/attribute_methods.rb new file mode 100644 index 000000000..a0e3331df --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/attribute_methods.rb @@ -0,0 +1,84 @@ +module CompositePrimaryKeys + module ActiveRecord + module AttributeMethods #:nodoc: + def self.append_features(base) + super + base.send(:extend, ClassMethods) + end + + module ClassMethods + # Define an attribute reader method. Cope with nil column. + def define_read_method(symbol, attr_name, column) + cast_code = column.type_cast_code('v') if column + cast_code = "::#{cast_code}" if cast_code && cast_code.match('ActiveRecord::.*') + access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" + + unless self.primary_keys.include?(attr_name.to_sym) + access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") + end + + if cache_attribute?(attr_name) + access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" + end + + evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end" + end + + # Evaluate the definition for an attribute related method + def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name) + unless primary_keys.include?(method_name.to_sym) + generated_methods << method_name + end + + begin + class_eval(method_definition, __FILE__, __LINE__) + rescue SyntaxError => err + generated_methods.delete(attr_name) + if logger + logger.warn "Exception occurred during reader method compilation." + logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?" + logger.warn "#{err.message}" + end + end + end + end + + # Allows access to the object attributes, which are held in the @attributes hash, as though they + # were first-class methods. So a Person class with a name attribute can use Person#name and + # Person#name= and never directly use the attributes hash -- except for multiple assigns with + # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that + # the completed attribute is not nil or 0. + # + # It's also possible to instantiate related objects, so a Client class belonging to the clients + # table with a master_id foreign key can instantiate master through Client#master. + def method_missing(method_id, *args, &block) + method_name = method_id.to_s + + # If we haven't generated any methods yet, generate them, then + # see if we've created the method we're looking for. + if !self.class.generated_methods? + self.class.define_attribute_methods + + if self.class.generated_methods.include?(method_name) + return self.send(method_id, *args, &block) + end + end + + if self.class.primary_keys.include?(method_name.to_sym) + ids[self.class.primary_keys.index(method_name.to_sym)] + elsif md = self.class.match_attribute_method?(method_name) + attribute_name, method_type = md.pre_match, md.to_s + if @attributes.include?(attribute_name) + __send__("attribute#{method_type}", attribute_name, *args, &block) + else + super + end + elsif @attributes.include?(method_name) + read_attribute(method_name) + else + super + end + end + end + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/base.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/base.rb new file mode 100644 index 000000000..42ec475af --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/base.rb @@ -0,0 +1,337 @@ +module CompositePrimaryKeys + module ActiveRecord #:nodoc: + class CompositeKeyError < StandardError #:nodoc: + end + + module Base #:nodoc: + + INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys' + NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet' + + def self.append_features(base) + super + base.send(:include, InstanceMethods) + base.extend(ClassMethods) + end + + module ClassMethods + def set_primary_keys(*keys) + keys = keys.first if keys.first.is_a?(Array) + keys = keys.map { |k| k.to_sym } + cattr_accessor :primary_keys + self.primary_keys = keys.to_composite_keys + + class_eval <<-EOV + extend CompositeClassMethods + include CompositeInstanceMethods + + include CompositePrimaryKeys::ActiveRecord::Associations + include CompositePrimaryKeys::ActiveRecord::AssociationPreload + include CompositePrimaryKeys::ActiveRecord::Calculations + include CompositePrimaryKeys::ActiveRecord::AttributeMethods + EOV + end + + def composite? + false + end + end + + module InstanceMethods + def composite?; self.class.composite?; end + end + + module CompositeInstanceMethods + + # A model instance's primary keys is always available as model.ids + # whether you name it the default 'id' or set it to something else. + def id + attr_names = self.class.primary_keys + CompositeIds.new(attr_names.map { |attr_name| read_attribute(attr_name) }) + end + alias_method :ids, :id + + def to_param + id.to_s + end + + def id_before_type_cast #:nodoc: + raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET + end + + def quoted_id #:nodoc: + [self.class.primary_keys, ids]. + transpose. + map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}. + to_composite_ids + end + + # Sets the primary ID. + def id=(ids) + ids = ids.split(ID_SEP) if ids.is_a?(String) + ids.flatten! + unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length + raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids" + end + [primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)} + id + end + + # Returns a clone of the record that hasn't been assigned an id yet and + # is treated as a new record. Note that this is a "shallow" clone: + # it copies the object's attributes only, not its associations. + # The extent of a "deep" clone is application-specific and is therefore + # left to the application to implement according to its need. + def clone + attrs = self.attributes_before_type_cast + self.class.primary_keys.each {|key| attrs.delete(key.to_s)} + self.class.new do |record| + record.send :instance_variable_set, '@attributes', attrs + end + end + + + private + # The xx_without_callbacks methods are overwritten as that is the end of the alias chain + + # Creates a new record with values matching those of the instance attributes. + def create_without_callbacks + unless self.id + raise CompositeKeyError, "Composite keys do not generated ids from sequences, you must provide id values" + end + attributes_minus_pks = attributes_with_quotes(false) + quoted_pk_columns = self.class.primary_key.map { |col| connection.quote_column_name(col) } + cols = quoted_column_names(attributes_minus_pks) << quoted_pk_columns + vals = attributes_minus_pks.values << quoted_id + connection.insert( + "INSERT INTO #{self.class.quoted_table_name} " + + "(#{cols.join(', ')}) " + + "VALUES (#{vals.join(', ')})", + "#{self.class.name} Create", + self.class.primary_key, + self.id + ) + @new_record = false + return true + end + + # Updates the associated record with values matching those of the instance attributes. + def update_without_callbacks + where_clause_terms = [self.class.primary_key, quoted_id].transpose.map do |pair| + "(#{connection.quote_column_name(pair[0])} = #{pair[1]})" + end + where_clause = where_clause_terms.join(" AND ") + connection.update( + "UPDATE #{self.class.quoted_table_name} " + + "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " + + "WHERE #{where_clause}", + "#{self.class.name} Update" + ) + return true + end + + # Deletes the record in the database and freezes this instance to reflect that no changes should + # be made (since they can't be persisted). + def destroy_without_callbacks + where_clause_terms = [self.class.primary_key, quoted_id].transpose.map do |pair| + "(#{connection.quote_column_name(pair[0])} = #{pair[1]})" + end + where_clause = where_clause_terms.join(" AND ") + unless new_record? + connection.delete( + "DELETE FROM #{self.class.quoted_table_name} " + + "WHERE #{where_clause}", + "#{self.class.name} Destroy" + ) + end + freeze + end + end + + module CompositeClassMethods + def primary_key; primary_keys; end + def primary_key=(keys); primary_keys = keys; end + + def composite? + true + end + + #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)" + #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3" + def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')') + many_ids.map {|ids| "#{left_bracket}#{ids}#{right_bracket}"}.join(list_sep) + end + + # Creates WHERE condition from list of composited ids + # User.update_all({:role => 'admin'}, :conditions => composite_where_clause([[1, 2], [2, 2]])) #=> UPDATE admins SET admin.role='admin' WHERE (admin.type=1 AND admin.type2=2) OR (admin.type=2 AND admin.type2=2) + # User.find(:all, :conditions => composite_where_clause([[1, 2], [2, 2]])) #=> SELECT * FROM admins WHERE (admin.type=1 AND admin.type2=2) OR (admin.type=2 AND admin.type2=2) + def composite_where_clause(ids) + if ids.is_a?(String) + ids = [[ids]] + elsif not ids.first.is_a?(Array) # if single comp key passed, turn into an array of 1 + ids = [ids.to_composite_ids] + end + + ids.map do |id_set| + [primary_keys, id_set].transpose.map do |key, id| + "#{table_name}.#{key.to_s}=#{sanitize(id)}" + end.join(" AND ") + end.join(") OR (") + end + + # Returns true if the given +ids+ represents the primary keys of a record in the database, false otherwise. + # Example: + # Person.exists?(5,7) + def exists?(ids) + obj = find(ids) rescue false + !obj.nil? and obj.is_a?(self) + end + + # Deletes the record with the given +ids+ without instantiating an object first, e.g. delete(1,2) + # If an array of ids is provided (e.g. delete([1,2], [3,4]), all of them + # are deleted. + def delete(*ids) + unless ids.is_a?(Array); raise "*ids must be an Array"; end + ids = [ids.to_composite_ids] if not ids.first.is_a?(Array) + where_clause = ids.map do |id_set| + [primary_keys, id_set].transpose.map do |key, id| + "#{quoted_table_name}.#{connection.quote_column_name(key.to_s)}=#{sanitize(id)}" + end.join(" AND ") + end.join(") OR (") + delete_all([ "(#{where_clause})" ]) + end + + # Destroys the record with the given +ids+ by instantiating the object and calling #destroy (all the callbacks are the triggered). + # If an array of ids is provided, all of them are destroyed. + def destroy(*ids) + unless ids.is_a?(Array); raise "*ids must be an Array"; end + if ids.first.is_a?(Array) + ids = ids.map{|compids| compids.to_composite_ids} + else + ids = ids.to_composite_ids + end + ids.first.is_a?(CompositeIds) ? ids.each { |id_set| find(id_set).destroy } : find(ids).destroy + end + + # Returns an array of column objects for the table associated with this class. + # Each column that matches to one of the primary keys has its + # primary attribute set to true + def columns + unless @columns + @columns = connection.columns(table_name, "#{name} Columns") + @columns.each {|column| column.primary = primary_keys.include?(column.name.to_sym)} + end + @columns + end + + ## DEACTIVATED METHODS ## + public + # Lazy-set the sequence name to the connection's default. This method + # is only ever called once since set_sequence_name overrides it. + def sequence_name #:nodoc: + raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS + end + + def reset_sequence_name #:nodoc: + raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS + end + + def set_primary_key(value = nil, &block) + raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS + end + + private + def find_one(id, options) + raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS + end + + def find_some(ids, options) + raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS + end + + def find_from_ids(ids, options) + ids = ids.first if ids.last == nil + conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] + # if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order) + # if ids is list of lists, then each inner list must follow rule above + if ids.first.is_a? String + # find '2,1' -> ids = ['2,1'] + # find '2,1;7,3' -> ids = ['2,1;7,3'] + ids = ids.first.split(ID_SET_SEP).map {|id_set| id_set.split(ID_SEP).to_composite_ids} + # find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds + end + ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array) + ids.each do |id_set| + unless id_set.is_a?(Array) + raise "Ids must be in an Array, instead received: #{id_set.inspect}" + end + unless id_set.length == primary_keys.length + raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}" + end + end + + # Let keys = [:a, :b] + # If ids = [[10, 50], [11, 51]], then :conditions => + # "(#{quoted_table_name}.a, #{quoted_table_name}.b) IN ((10, 50), (11, 51))" + + conditions = ids.map do |id_set| + [primary_keys, id_set].transpose.map do |key, id| + col = columns_hash[key.to_s] + val = quote_value(id, col) + "#{quoted_table_name}.#{connection.quote_column_name(key.to_s)}=#{val}" + end.join(" AND ") + end.join(") OR (") + + options.update :conditions => "(#{conditions})" + + result = find_every(options) + + if result.size == ids.size + ids.size == 1 ? result[0] : result + else + raise ::ActiveRecord::RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids.inspect})#{conditions}" + end + end + end + end + end +end + + +module ActiveRecord + ID_SEP = ',' + ID_SET_SEP = ';' + + class Base + # Allows +attr_name+ to be the list of primary_keys, and returns the id + # of the object + # e.g. @object[@object.class.primary_key] => [1,1] + def [](attr_name) + if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first + attr_name = attr_name.split(ID_SEP) + end + attr_name.is_a?(Array) ? + attr_name.map {|name| read_attribute(name)} : + read_attribute(attr_name) + end + + # Updates the attribute identified by attr_name with the specified +value+. + # (Alias for the protected write_attribute method). + def []=(attr_name, value) + if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first + attr_name = attr_name.split(ID_SEP) + end + + if attr_name.is_a? Array + value = value.split(ID_SEP) if value.is_a? String + unless value.length == attr_name.length + raise "Number of attr_names and values do not match" + end + #breakpoint + [attr_name, value].transpose.map {|name,val| write_attribute(name.to_s, val)} + else + write_attribute(attr_name, value) + end + end + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/calculations.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/calculations.rb new file mode 100644 index 000000000..44280e1c7 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/calculations.rb @@ -0,0 +1,68 @@ +module CompositePrimaryKeys + module ActiveRecord + module Calculations + def self.append_features(base) + super + base.send(:extend, ClassMethods) + end + + module ClassMethods + def construct_calculation_sql(operation, column_name, options) #:nodoc: + operation = operation.to_s.downcase + options = options.symbolize_keys + + scope = scope(:find) + merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) + aggregate_alias = column_alias_for(operation, column_name) + use_workaround = !connection.supports_count_distinct? && options[:distinct] && operation.to_s.downcase == 'count' + join_dependency = nil + + if merged_includes.any? && operation.to_s.downcase == 'count' + options[:distinct] = true + use_workaround = !connection.supports_count_distinct? + column_name = options[:select] || primary_key.map{ |part| "#{quoted_table_name}.#{connection.quote_column_name(part)}"}.join(',') + end + + sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" + + # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. + sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround + + sql << ", #{connection.quote_column_name(options[:group_field])} AS #{options[:group_alias]}" if options[:group] + sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround + sql << " FROM #{quoted_table_name} " + if merged_includes.any? + join_dependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) + sql << join_dependency.join_associations.collect{|join| join.association_join }.join + end + add_joins!(sql, options, scope) + add_conditions!(sql, options[:conditions], scope) + add_limited_ids_condition!(sql, options, join_dependency) if \ + join_dependency && + !using_limitable_reflections?(join_dependency.reflections) && + ((scope && scope[:limit]) || options[:limit]) + + if options[:group] + group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field + sql << " GROUP BY #{connection.quote_column_name(options[group_key])} " + end + + if options[:group] && options[:having] + # FrontBase requires identifiers in the HAVING clause and chokes on function calls + if connection.adapter_name == 'FrontBase' + options[:having].downcase! + options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias) + end + + sql << " HAVING #{options[:having]} " + end + + sql << " ORDER BY #{options[:order]} " if options[:order] + add_limit!(sql, options, scope) + sql << ') w1' if use_workaround # assign a dummy table name as required for postgresql + sql + end + end + end + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/composite_arrays.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/composite_arrays.rb new file mode 100644 index 000000000..030c416f3 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/composite_arrays.rb @@ -0,0 +1,30 @@ +module CompositePrimaryKeys + ID_SEP = ',' + ID_SET_SEP = ';' + + module ArrayExtension + def to_composite_keys + CompositeKeys.new(self) + end + + def to_composite_ids + CompositeIds.new(self) + end + end + + class CompositeArray < Array + def to_s + join(ID_SEP) + end + end + + class CompositeKeys < CompositeArray + + end + + class CompositeIds < CompositeArray + + end +end + +Array.send(:include, CompositePrimaryKeys::ArrayExtension) diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb new file mode 100644 index 000000000..1ab47179f --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb @@ -0,0 +1,21 @@ +module ActiveRecord + module ConnectionAdapters + class IBM_DBAdapter < AbstractAdapter + + # This mightn't be in Core, but count(distinct x,y) doesn't work for me + def supports_count_distinct? #:nodoc: + false + end + + alias_method :quote_original, :quote + def quote(value, column = nil) + if value.kind_of?(String) && column && [:integer, :float].include?(column.type) + value = column.type == :integer ? value.to_i : value.to_f + value.to_s + else + quote_original(value, column) + end + end + end + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb new file mode 100644 index 000000000..af558fa6e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module ConnectionAdapters + class OracleAdapter < AbstractAdapter + + # This mightn't be in Core, but count(distinct x,y) doesn't work for me + def supports_count_distinct? #:nodoc: + false + end + + def concat(*columns) + "(#{columns.join('||')})" + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb new file mode 100644 index 000000000..65fce48f6 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,53 @@ +module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapter < AbstractAdapter + + # This mightn't be in Core, but count(distinct x,y) doesn't work for me + def supports_count_distinct? #:nodoc: + false + end + + def concat(*columns) + columns = columns.map { |c| "CAST(#{c} AS varchar)" } + "(#{columns.join('||')})" + end + + # Executes an INSERT query and returns the new record's ID + def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + # Extract the table from the insert sql. Yuck. + table = sql.split(" ", 4)[2].gsub('"', '') + + # Try an insert with 'returning id' if available (PG >= 8.2) + if supports_insert_with_returning? + pk, sequence_name = *pk_and_sequence_for(table) unless pk + if pk + quoted_pk = if pk.is_a?(Array) + pk.map { |col| quote_column_name(col) }.join(ID_SEP) + else + quote_column_name(pk) + end + id = select_value("#{sql} RETURNING #{quoted_pk}") + clear_query_cache + return id + end + end + + # Otherwise, insert then grab last_insert_id. + if insert_id = super + insert_id + else + # If neither pk nor sequence name is given, look them up. + unless pk || sequence_name + pk, sequence_name = *pk_and_sequence_for(table) + end + + # If a pk is given, fallback to default sequence name. + # Don't fetch last insert id for a table without a pk. + if pk && sequence_name ||= default_sequence_name(table, pk) + last_insert_id(table, sequence_name) + end + end + end + end + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb new file mode 100644 index 000000000..052757720 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb @@ -0,0 +1,15 @@ +require 'active_record/connection_adapters/sqlite_adapter' + +module ActiveRecord + module ConnectionAdapters #:nodoc: + class SQLite3Adapter < SQLiteAdapter # :nodoc: + def supports_count_distinct? #:nodoc: + false + end + + def concat(*columns) + "(#{columns.join('||')})" + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/fixtures.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/fixtures.rb new file mode 100644 index 000000000..7dfaf08fb --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/fixtures.rb @@ -0,0 +1,8 @@ +class Fixture #:nodoc: + def [](key) + if key.is_a? Array + return key.map { |a_key| self[a_key.to_s] }.to_composite_ids.to_s + end + @fixture[key] + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/migration.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/migration.rb new file mode 100644 index 000000000..2a50404c5 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/migration.rb @@ -0,0 +1,20 @@ +ActiveRecord::ConnectionAdapters::ColumnDefinition.send(:alias_method, :to_s_without_composite_keys, :to_s) + +ActiveRecord::ConnectionAdapters::ColumnDefinition.class_eval <<-'EOF' + def to_s + if name.is_a? Array + "PRIMARY KEY (#{name.join(',')})" + else + to_s_without_composite_keys + end + end +EOF + +ActiveRecord::ConnectionAdapters::TableDefinition.class_eval <<-'EOF' + def [](name) + @columns.find { |column| + !column.name.is_a?(Array) && column.name.to_s == name.to_s + } + end +EOF + \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/reflection.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/reflection.rb new file mode 100644 index 000000000..309baf118 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/reflection.rb @@ -0,0 +1,19 @@ +module ActiveRecord + module Reflection + class AssociationReflection + def primary_key_name + return @primary_key_name if @primary_key_name + case + when macro == :belongs_to + @primary_key_name = options[:foreign_key] || class_name.foreign_key + when options[:as] + @primary_key_name = options[:foreign_key] || "#{options[:as]}_id" + else + @primary_key_name = options[:foreign_key] || active_record.name.foreign_key + end + @primary_key_name = @primary_key_name.to_composite_keys.to_s if @primary_key_name.is_a? Array + @primary_key_name + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/version.rb b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/version.rb new file mode 100644 index 000000000..c6cbeeb4c --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/lib/composite_primary_keys/version.rb @@ -0,0 +1,8 @@ +module CompositePrimaryKeys + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 1 + TINY = 0 + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/loader.rb b/vendor/gems/composite_primary_keys-1.1.0/loader.rb new file mode 100644 index 000000000..052c47ce1 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/loader.rb @@ -0,0 +1,24 @@ +# Load local config files in /local +begin + local_file_supported = Dir[File.join(PROJECT_ROOT, 'local/*.sample')].map { |path| File.basename(path).sub(".sample","") } + local_file_supported.each do |file| + require "local/#{file}" + end +rescue LoadError + puts <<-EOS + This Gem supports local developer extensions in local/ folder. + Supported files: + #{local_file_supported.map { |f| "local/#{f}"}.join(', ')} + + Setup default sample files: + rake local:setup + + Current warning: #{$!} + + EOS +end + + +# Now load Rake tasks from /tasks +rakefiles = Dir[File.join(File.dirname(__FILE__), "tasks/**/*.rake")] +rakefiles.each { |rakefile| load File.expand_path(rakefile) } diff --git a/vendor/gems/composite_primary_keys-1.1.0/local/database_connections.rb.sample b/vendor/gems/composite_primary_keys-1.1.0/local/database_connections.rb.sample new file mode 100644 index 000000000..be67edd97 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/local/database_connections.rb.sample @@ -0,0 +1,10 @@ +require 'yaml' + +ENV['cpk_adapters'] = { + "mysql" => { + :adapter => "mysql", + :username => "root", + :password => "root", + # ... + } +}.to_yaml \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/local/paths.rb.sample b/vendor/gems/composite_primary_keys-1.1.0/local/paths.rb.sample new file mode 100644 index 000000000..65ba16f68 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/local/paths.rb.sample @@ -0,0 +1,2 @@ +# location of folder containing activerecord, railties, etc folders for each Rails gem +ENV['EDGE_RAILS_DIR'] ||= "/path/to/copy/of/edge/rails" diff --git a/vendor/gems/composite_primary_keys-1.1.0/local/tasks.rb.sample b/vendor/gems/composite_primary_keys-1.1.0/local/tasks.rb.sample new file mode 100644 index 000000000..29daf8d6f --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/local/tasks.rb.sample @@ -0,0 +1,2 @@ +# This file loaded into Rakefile +# Place any extra development tasks you want here \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/scripts/console.rb b/vendor/gems/composite_primary_keys-1.1.0/scripts/console.rb new file mode 100755 index 000000000..7053e926b --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/scripts/console.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby + +# +# if run as script, load the file as library while starting irb +# +if __FILE__ == $0 + irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' + ENV['ADAPTER'] = ARGV[0] + exec "#{irb} -f -r #{$0} --simple-prompt" +end + +# +# check if the given adapter is supported (default: mysql) +# +adapters = %w[mysql sqlite oracle oracle_enhanced postgresql ibm_db] +adapter = ENV['ADAPTER'] || 'mysql' +unless adapters.include? adapter + puts "Usage: #{__FILE__} " + puts '' + puts 'Adapters: ' + puts adapters.map{ |adapter| " #{adapter}" }.join("\n") + exit 1 +end + +# +# load all necessary libraries +# +require 'rubygems' +require 'local/database_connections' + +$LOAD_PATH.unshift 'lib' + +begin + require 'local/paths' + $LOAD_PATH.unshift "#{ENV['EDGE_RAILS_DIR']}/activerecord/lib" if ENV['EDGE_RAILS_DIR'] + $LOAD_PATH.unshift "#{ENV['EDGE_RAILS_DIR']}/activesupport/lib" if ENV['EDGE_RAILS_DIR'] +rescue +end + +require 'active_support' +require 'active_record' + +require "test/connections/native_#{adapter}/connection" +require 'composite_primary_keys' + +PROJECT_ROOT = File.join(File.dirname(__FILE__), '..') +Dir[File.join(PROJECT_ROOT,'test/fixtures/*.rb')].each { |model| require model } + diff --git a/vendor/gems/composite_primary_keys-1.1.0/scripts/txt2html b/vendor/gems/composite_primary_keys-1.1.0/scripts/txt2html new file mode 100644 index 000000000..d5ab2c698 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/scripts/txt2html @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require File.dirname(__FILE__) + '/../lib/composite_primary_keys/version.rb' + +version = CompositePrimaryKeys::VERSION::STRING +download = 'http://rubyforge.org/projects/compositekeys' + +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!^
|
$!,'') +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=['"]([^'"]+)[^>]*>(.*?)!m){ + ident = syntax_items.length + element, syntax, source = $1, $2, $3 + syntax_items << "<#{element} class=\"syntax\">#{convert_syntax(syntax, source)}" + "syntax-temp-#{ident}" + } + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip + body = RedCloth.new(body_text).to_html + body.gsub!(%r!(?:
)?syntax-temp-(\d+)(?:
)?!){ syntax_items[$1.to_i] } +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/vendor/gems/composite_primary_keys-1.1.0/scripts/txt2js b/vendor/gems/composite_primary_keys-1.1.0/scripts/txt2js new file mode 100644 index 000000000..4a287cad1 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/scripts/txt2js @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require 'active_support' +require File.dirname(__FILE__) + '/../lib/composite_primary_keys/version.rb' + +version = CompositePrimaryKeys::VERSION::STRING +download = 'http://rubyforge.org/projects/compositekeys' + +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!^
|
$!,'') +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.js] > 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 + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip + body = RedCloth.new(body_text) +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/activerecord_selection.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/activerecord_selection.rake new file mode 100644 index 000000000..44ca4bb24 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/activerecord_selection.rake @@ -0,0 +1,43 @@ +namespace :ar do + desc 'Pre-load edge rails ActiveRecord' + task :edge do + unless path = ENV['EDGE_RAILS_DIR'] || ENV['EDGE_RAILS'] + puts <<-EOS + +Need to define env var EDGE_RAILS_DIR or EDGE_RAILS- root of edge rails on your machine. + i) Get copy of Edge Rails - http://dev.rubyonrails.org + ii) Set EDGE_RAILS_DIR to this folder in local/paths.rb - see local/paths.rb.sample for example + or + a) Set folder from environment or command line (rake ar:edge EDGE_RAILS_DIR=/path/to/rails) + + EOS + exit + end + + ENV['AR_LOAD_PATH'] = File.join(path, "activerecord/lib") + end + + desc 'Pre-load ActiveRecord using VERSION=X.Y.Z, instead of latest' + task :set do + unless version = ENV['VERSION'] + puts <<-EOS +Usage: rake ar:get_version VERSION=1.15.3 + Specify the version number with VERSION=X.Y.Z; and make sure you have that activerecord gem version installed. + + EOS + end + version = nil if version == "" || version == [] + begin + version ? gem('activerecord', version) : gem('activerecord') + require 'active_record' + ENV['AR_LOAD_PATH'] = $:.reverse.find { |path| /activerecord/ =~ path } + rescue LoadError + puts <<-EOS +Missing: Cannot find activerecord #{version} installed. + Install: gem install activerecord -v #{version} + + EOS + exit + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/databases.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases.rake new file mode 100644 index 000000000..0d9151753 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases.rake @@ -0,0 +1,12 @@ +require 'active_record' + +# UNTESTED - firebird sqlserver sqlserver_odbc db2 sybase openbase +for adapter in %w( mysql sqlite oracle oracle_enhanced postgresql ibm_db ) + Rake::TestTask.new("test_#{adapter}") { |t| + t.libs << "test" << "test/connections/native_#{adapter}" + t.pattern = "test/test_*.rb" + t.verbose = true + } +end + +SCHEMA_PATH = File.join(PROJECT_ROOT, *%w(test fixtures db_definitions)) diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/mysql.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/mysql.rake new file mode 100644 index 000000000..e05239ee2 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/mysql.rake @@ -0,0 +1,30 @@ +namespace :mysql do + desc 'Build the MySQL test databases' + task :build_databases => :load_connection do + puts File.join(SCHEMA_PATH, 'mysql.sql') + options_str = ENV['cpk_adapter_options_str'] + # creates something like "-u#{username} -p#{password} -S#{socket}" + sh %{ mysqladmin #{options_str} create "#{GEM_NAME}_unittest" } + sh %{ mysql #{options_str} "#{GEM_NAME}_unittest" < #{File.join(SCHEMA_PATH, 'mysql.sql')} } + end + + desc 'Drop the MySQL test databases' + task :drop_databases => :load_connection do + options_str = ENV['cpk_adapter_options_str'] + sh %{ mysqladmin #{options_str} -f drop "#{GEM_NAME}_unittest" } + end + + desc 'Rebuild the MySQL test databases' + task :rebuild_databases => [:drop_databases, :build_databases] + + task :load_connection do + require File.join(PROJECT_ROOT, %w[lib adapter_helper mysql]) + spec = AdapterHelper::MySQL.load_connection_from_env + options = {} + options['u'] = spec[:username] if spec[:username] + options['p'] = spec[:password] if spec[:password] + options['S'] = spec[:sock] if spec[:sock] + options_str = options.map { |key, value| "-#{key}#{value}" }.join(" ") + ENV['cpk_adapter_options_str'] = options_str + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/oracle.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/oracle.rake new file mode 100644 index 000000000..4861d0137 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/oracle.rake @@ -0,0 +1,25 @@ +namespace :oracle do + desc 'Build the Oracle test databases' + task :build_databases => :load_connection do + puts File.join(SCHEMA_PATH, 'oracle.sql') + options_str = ENV['cpk_adapter_options_str'] + sh %( sqlplus #{options_str} < #{File.join(SCHEMA_PATH, 'oracle.sql')} ) + end + + desc 'Drop the Oracle test databases' + task :drop_databases => :load_connection do + puts File.join(SCHEMA_PATH, 'oracle.drop.sql') + options_str = ENV['cpk_adapter_options_str'] + sh %( sqlplus #{options_str} < #{File.join(SCHEMA_PATH, 'oracle.drop.sql')} ) + end + + desc 'Rebuild the Oracle test databases' + task :rebuild_databases => [:drop_databases, :build_databases] + + task :load_connection do + require File.join(PROJECT_ROOT, %w[lib adapter_helper oracle]) + spec = AdapterHelper::Oracle.load_connection_from_env + ENV['cpk_adapter_options_str'] = "#{spec[:username]}/#{spec[:password]}@#{spec[:host]}" + end + +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/postgresql.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/postgresql.rake new file mode 100644 index 000000000..13b34e2ed --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/postgresql.rake @@ -0,0 +1,26 @@ +namespace :postgresql do + desc 'Build the PostgreSQL test databases' + task :build_databases => :load_connection do + sh %{ createdb "#{GEM_NAME}_unittest" } + sh %{ psql "#{GEM_NAME}_unittest" -f #{File.join(SCHEMA_PATH, 'postgresql.sql')} } + end + + desc 'Drop the PostgreSQL test databases' + task :drop_databases => :load_connection do + sh %{ dropdb "#{GEM_NAME}_unittest" } + end + + desc 'Rebuild the PostgreSQL test databases' + task :rebuild_databases => [:drop_databases, :build_databases] + + task :load_connection do + require File.join(PROJECT_ROOT, %w[lib adapter_helper postgresql]) + spec = AdapterHelper::Postgresql.load_connection_from_env + options = {} + options['u'] = spec[:username] if spec[:username] + options['p'] = spec[:password] if spec[:password] + options_str = options.map { |key, value| "-#{key}#{value}" }.join(" ") + ENV['cpk_adapter_options_str'] = options_str + end +end + diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/sqlite3.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/sqlite3.rake new file mode 100644 index 000000000..9a5579ad8 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/databases/sqlite3.rake @@ -0,0 +1,28 @@ +namespace :sqlite3 do + desc 'Build the sqlite test databases' + task :build_databases => :load_connection do + file = File.join(SCHEMA_PATH, 'sqlite.sql') + dbfile = File.join(PROJECT_ROOT, ENV['cpk_adapter_options_str']) + cmd = "mkdir -p #{File.dirname(dbfile)}" + puts cmd + sh %{ #{cmd} } + cmd = "sqlite3 #{dbfile} < #{file}" + puts cmd + sh %{ #{cmd} } + end + + desc 'Drop the sqlite test databases' + task :drop_databases => :load_connection do + dbfile = ENV['cpk_adapter_options_str'] + sh %{ rm -f #{dbfile} } + end + + desc 'Rebuild the sqlite test databases' + task :rebuild_databases => [:drop_databases, :build_databases] + + task :load_connection do + require File.join(PROJECT_ROOT, %w[lib adapter_helper sqlite3]) + spec = AdapterHelper::Sqlite3.load_connection_from_env + ENV['cpk_adapter_options_str'] = spec[:dbfile] + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/deployment.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/deployment.rake new file mode 100644 index 000000000..84f143b70 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/deployment.rake @@ -0,0 +1,22 @@ +desc 'Release the website and new gem version' +task :deploy => [:check_version, :website, :release] do + puts "Remember to create SVN tag:" + puts "svn copy svn+ssh://#{RUBYFORGE_USERNAME}@rubyforge.org/var/svn/#{PATH}/trunk " + + "svn+ssh://#{RUBYFORGE_USERNAME}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} " + puts "Suggested comment:" + puts "Tagging release #{CHANGES}" +end + +desc 'Runs tasks website_generate and install_gem as a local deployment of the gem' +task :local_deploy => [:website_generate, :install_gem] + +task :check_version do + unless ENV['VERSION'] + puts 'Must pass a VERSION=x.y.z release version' + exit + end + unless ENV['VERSION'] == VERS + puts "Please update your version.rb to match the release version, currently #{VERS}" + exit + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/local_setup.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/local_setup.rake new file mode 100644 index 000000000..1b8afa8b7 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/local_setup.rake @@ -0,0 +1,13 @@ +namespace :local do + desc 'Copies over the same local files ready for editing' + task :setup do + sample_files = Dir[File.join(PROJECT_ROOT, "local/*.rb.sample")] + sample_files.each do |sample_file| + file = sample_file.sub(".sample","") + unless File.exists?(file) + puts "Copying #{sample_file} -> #{file}" + sh %{ cp #{sample_file} #{file} } + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/tasks/website.rake b/vendor/gems/composite_primary_keys-1.1.0/tasks/website.rake new file mode 100644 index 000000000..600f5633f --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/tasks/website.rake @@ -0,0 +1,18 @@ +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}/" + local_dir = 'website' + sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}} +end + +desc 'Generate and upload website files' +task :website => [:website_generate, :website_upload, :publish_docs] diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/README_tests.txt b/vendor/gems/composite_primary_keys-1.1.0/test/README_tests.txt new file mode 100644 index 000000000..66fe21f2e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/README_tests.txt @@ -0,0 +1,67 @@ += Composite Primary Keys - Testing Readme + +== Testing an adapter + +There are tests available for the following adapters: + +* ibmdb +* mysql +* oracle +* postgresql +* sqlite + +To run the tests for on of the adapters, follow these steps (using mysql in the example): + +* rake -T | grep mysql + + rake mysql:build_databases # Build the MySQL test databases + rake mysql:drop_databases # Drop the MySQL test databases + rake mysql:rebuild_databases # Rebuild the MySQL test databases + rake test_mysql # Run tests for test_mysql + +* rake mysql:build_databases +* rake test_mysql + +== Testing against different ActiveRecord versions (or Edge Rails) + +ActiveRecord is a RubyGem within Rails, and is constantly being improved/changed on +its repository (http://dev.rubyonrails.org). These changes may create errors for the CPK +gem. So, we need a way to test CPK against Edge Rails, as well as officially released RubyGems. + +The default test (as above) uses the latest RubyGem in your cache. + +You can select an older RubyGem version by running the following: + +* rake ar:set VERSION=1.14.4 test_mysql + +== Edge Rails + +Before you can test CPK against Edge Rails, you must checkout a copy of edge rails somewhere (see http://dev.rubyonrails.org for for examples) + +* cd /path/to/gems +* svn co http://svn.rubyonrails.org/rails/trunk rails + +Say the rails folder is /path/to/gems/rails + +Three ways to run CPK tests for Edge Rails: + +i) Run: + + EDGE_RAILS_DIR=/path/to/gems/rails rake ar:edge test_mysql + +ii) In your .profile, set the environment variable EDGE_RAILS_DIR=/path/to/gems/rails, + and once you reload your profile, run: + + rake ar:edge test_mysql + +iii) Store the path in local/paths.rb. Run: + + cp local/paths.rb.sample local/paths.rb + # Now set ENV['EDGE_RAILS_DIR']=/path/to/gems/rails + rake ar:edge test_mysql + +These are all variations of the same theme: + +* Set the environment variable EDGE_RAILS_DIR to the path to Rails (which contains the activerecord/lib folder) +* Run: rake ar:edge test_ + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/abstract_unit.rb b/vendor/gems/composite_primary_keys-1.1.0/test/abstract_unit.rb new file mode 100644 index 000000000..f33edfaeb --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/abstract_unit.rb @@ -0,0 +1,94 @@ +$:.unshift(ENV['AR_LOAD_PATH']) if ENV['AR_LOAD_PATH'] + +require 'test/unit' +require 'hash_tricks' +require 'rubygems' +require 'active_record' +require 'active_record/fixtures' +begin + require 'connection' +rescue MissingSourceFile => e + adapter = 'postgresql' #'sqlite' + require "#{File.dirname(__FILE__)}/connections/native_#{adapter}/connection" +end +require 'composite_primary_keys' + +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 + + 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 + + cattr_accessor :classes +protected + + def testing_with(&block) + classes.keys.each do |@key_test| + @klass_info = classes[@key_test] + @klass, @primary_keys = @klass_info[:class], @klass_info[:primary_keys] + order = @klass.primary_key.is_a?(String) ? @klass.primary_key : @klass.primary_key.join(',') + @first = @klass.find(:first, :order => order) + yield + end + end + + def first_id + ids = (1..@primary_keys.length).map {|num| 1} + composite? ? ids.to_composite_ids : ids.first + end + + def first_id_str + composite? ? first_id.join(CompositePrimaryKeys::ID_SEP) : first_id.to_s + end + + def composite? + @key_test != :single + 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 diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_ibm_db/connection.rb b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_ibm_db/connection.rb new file mode 100644 index 000000000..7a40f7c3d --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_ibm_db/connection.rb @@ -0,0 +1,23 @@ +print "Using IBM2 \n" +require 'logger' + +gem 'ibm_db' +require 'IBM_DB' + +RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase ibm_db ) + + +ActiveRecord::Base.logger = Logger.new("debug.log") + +db1 = 'composite_primary_keys_unittest' + +connection_options = { + :adapter => "ibm_db", + :database => "ocdpdev", + :username => "db2inst1", + :password => "password", + :host => '192.168.2.21' +} + +ActiveRecord::Base.configurations = { db1 => connection_options } +ActiveRecord::Base.establish_connection(connection_options) diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_mysql/connection.rb b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_mysql/connection.rb new file mode 100644 index 000000000..12dbe4cd3 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_mysql/connection.rb @@ -0,0 +1,13 @@ +print "Using native MySQL\n" +require 'fileutils' +require 'logger' +require 'adapter_helper/mysql' + +log_path = File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. .. log])) +FileUtils.mkdir_p log_path +puts "Logging to #{log_path}/debug.log" +ActiveRecord::Base.logger = Logger.new("#{log_path}/debug.log") + +# Adapter config setup in locals/database_connections.rb +connection_options = AdapterHelper::MySQL.load_connection_from_env +ActiveRecord::Base.establish_connection(connection_options) diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_oracle/connection.rb b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_oracle/connection.rb new file mode 100644 index 000000000..383538fed --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_oracle/connection.rb @@ -0,0 +1,14 @@ +print "Using native Oracle\n" +require 'fileutils' +require 'logger' +require 'adapter_helper/oracle' + +log_path = File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. .. log])) +FileUtils.mkdir_p log_path +puts "Logging to #{log_path}/debug.log" +ActiveRecord::Base.logger = Logger.new("#{log_path}/debug.log") + +# Adapter config setup in locals/database_connections.rb +connection_options = AdapterHelper::Oracle.load_connection_from_env +puts connection_options.inspect +ActiveRecord::Base.establish_connection(connection_options) diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_postgresql/connection.rb b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_postgresql/connection.rb new file mode 100644 index 000000000..a2d93f92b --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_postgresql/connection.rb @@ -0,0 +1,9 @@ +print "Using native Postgresql\n" +require 'logger' +require 'adapter_helper/postgresql' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +# Adapter config setup in locals/database_connections.rb +connection_options = AdapterHelper::Postgresql.load_connection_from_env +ActiveRecord::Base.establish_connection(connection_options) diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_sqlite/connection.rb b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_sqlite/connection.rb new file mode 100644 index 000000000..7c6102e0b --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/connections/native_sqlite/connection.rb @@ -0,0 +1,9 @@ +print "Using native Sqlite3\n" +require 'logger' +require 'adapter_helper/sqlite3' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +# Adapter config setup in locals/database_connections.rb +connection_options = AdapterHelper::Sqlite3.load_connection_from_env +ActiveRecord::Base.establish_connection(connection_options) diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/article.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/article.rb new file mode 100644 index 000000000..7233f8126 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/article.rb @@ -0,0 +1,5 @@ +class Article < ActiveRecord::Base + has_many :readings + has_many :users, :through => :readings +end + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/articles.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/articles.yml new file mode 100644 index 000000000..e51060463 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/articles.yml @@ -0,0 +1,6 @@ +first: + id: 1 + name: Article One +second: + id: 2 + name: Article Two \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/comment.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/comment.rb new file mode 100644 index 000000000..857bf70aa --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/comment.rb @@ -0,0 +1,6 @@ +class Comment < ActiveRecord::Base + set_primary_keys :id + belongs_to :person, :polymorphic => true + belongs_to :hack +end + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/comments.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/comments.yml new file mode 100644 index 000000000..7f145143a --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/comments.yml @@ -0,0 +1,16 @@ +comment1: + id: 1 + person_id: 1 + person_type: Employee + +comment2: + id: 2 + person_id: 1 + person_type: User + hack_id: andrew + +comment3: + id: 3 + person_id: andrew + person_type: Hack + \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/db2-create-tables.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/db2-create-tables.sql new file mode 100644 index 000000000..edff930de --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/db2-create-tables.sql @@ -0,0 +1,113 @@ +CREATE TABLE reference_types ( + reference_type_id integer NOT NULL generated by default as identity (start with 100, increment by 1, no cache), + type_label varchar(50) default NULL, + abbreviation varchar(50) default NULL, + description varchar(50) default NULL, + PRIMARY KEY (reference_type_id) +); + +CREATE TABLE reference_codes ( + reference_type_id integer, + reference_code integer NOT NULL, + code_label varchar(50) default NULL, + abbreviation varchar(50) default NULL, + description varchar(50) default NULL, + PRIMARY KEY (reference_type_id,reference_code) +); + +CREATE TABLE products ( + id integer NOT NULL, + name varchar(50) default NULL, + PRIMARY KEY (id) +); + +CREATE TABLE tariffs ( + tariff_id integer NOT NULL, + start_date date NOT NULL, + amount integer default NULL, + PRIMARY KEY (tariff_id,start_date) +); + +CREATE TABLE product_tariffs ( + product_id integer NOT NULL, + tariff_id integer NOT NULL, + tariff_start_date date NOT NULL, + PRIMARY KEY (product_id,tariff_id,tariff_start_date) +); + +CREATE TABLE suburbs ( + city_id integer NOT NULL, + suburb_id integer NOT NULL, + name varchar(50) NOT NULL, + PRIMARY KEY (city_id,suburb_id) +); + +CREATE TABLE streets ( + id integer NOT NULL , + city_id integer NOT NULL, + suburb_id integer NOT NULL, + name varchar(50) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE users ( + id integer NOT NULL , + name varchar(50) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE articles ( + id integer NOT NULL , + name varchar(50) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE readings ( + id integer NOT NULL , + user_id integer NOT NULL, + article_id integer NOT NULL, + rating integer NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE groups ( + id integer NOT NULL , + name varchar(50) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE memberships ( + user_id integer NOT NULL, + group_id integer NOT NULL, + PRIMARY KEY (user_id,group_id) +); + +CREATE TABLE membership_statuses ( + id integer NOT NULL , + user_id integer NOT NULL, + group_id integer NOT NULL, + status varchar(50) NOT NULL, + PRIMARY KEY (id) +); + +create table kitchen_sinks ( + id_1 integer not null, + id_2 integer not null, + a_date date, + a_string varchar(100), + primary key (id_1, id_2) +); + +create table restaurants ( + franchise_id integer not null, + store_id integer not null, + name varchar(100), + primary key (franchise_id, store_id) +); + +create table restaurants_suburbs ( + franchise_id integer not null, + store_id integer not null, + city_id integer not null, + suburb_id integer not null +); diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/db2-drop-tables.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/db2-drop-tables.sql new file mode 100644 index 000000000..eb4843358 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/db2-drop-tables.sql @@ -0,0 +1,16 @@ +drop table MEMBERSHIPS; +drop table REFERENCE_CODES; +drop table TARIFFS; +drop table ARTICLES; +drop table GROUPS; +drop table MEMBERSHIP_STATUSES; +drop table READINGS; +drop table REFERENCE_TYPES; +drop table STREETS; +drop table PRODUCTS; +drop table USERS; +drop table SUBURBS; +drop table PRODUCT_TARIFFS; +drop table KITCHEN_SINK; +drop table RESTAURANTS; +drop table RESTAURANTS_SUBURBS; diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/mysql.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/mysql.sql new file mode 100644 index 000000000..e83d3aae5 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/mysql.sql @@ -0,0 +1,174 @@ +create table reference_types ( + reference_type_id int(11) not null auto_increment, + type_label varchar(50) default null, + abbreviation varchar(50) default null, + description varchar(50) default null, + primary key (reference_type_id) +) type=InnoDB; + +create table reference_codes ( + reference_type_id int(11), + reference_code int(11) not null, + code_label varchar(50) default null, + abbreviation varchar(50) default null, + description varchar(50) default null, + primary key (reference_type_id, reference_code) +) type=InnoDB; + +create table products ( + id int(11) not null auto_increment, + name varchar(50) default null, + primary key (id) +) type=InnoDB; + +create table tariffs ( + tariff_id int(11) not null, + start_date date not null, + amount integer(11) default null, + primary key (tariff_id, start_date) +) type=InnoDB; + +create table product_tariffs ( + product_id int(11) not null, + tariff_id int(11) not null, + tariff_start_date date not null, + primary key (product_id, tariff_id, tariff_start_date) +) type=InnoDB; + +create table suburbs ( + city_id int(11) not null, + suburb_id int(11) not null, + name varchar(50) not null, + primary key (city_id, suburb_id) +) type=InnoDB; + +create table streets ( + id int(11) not null auto_increment, + city_id int(11) not null, + suburb_id int(11) not null, + name varchar(50) not null, + primary key (id) +) type=InnoDB; + +create table users ( + id int(11) not null auto_increment, + name varchar(50) not null, + primary key (id) +) type=InnoDB; + +create table articles ( + id int(11) not null auto_increment, + name varchar(50) not null, + primary key (id) +) type=InnoDB; + +create table readings ( + id int(11) not null auto_increment, + user_id int(11) not null, + article_id int(11) not null, + rating int(11) not null, + primary key (id) +) type=InnoDB; + +create table groups ( + id int(11) not null auto_increment, + name varchar(50) not null, + primary key (id) +) type=InnoDB; + +create table memberships ( + user_id int(11) not null, + group_id int(11) not null, + primary key (user_id,group_id) +) type=InnoDB; + +create table membership_statuses ( + id int(11) not null auto_increment, + user_id int(11) not null, + group_id int(11) not null, + status varchar(50) not null, + primary key (id) +) type=InnoDB; + +create table departments ( + department_id int(11) not null, + location_id int(11) not null, + primary key (department_id, location_id) +) type=InnoDB; + +create table employees ( + id int(11) not null auto_increment, + department_id int(11) default null, + location_id int(11) default null, + primary key (id) +) type=InnoDB; + +create table comments ( + id int(11) not null auto_increment, + person_id varchar(100) default null, + person_type varchar(100) default null, + hack_id varchar(100) default null, + primary key (id) +) type=InnoDB; + +create table hacks ( + name varchar(50) not null, + primary key (name) +) type=InnoDB; + +create table kitchen_sinks ( + id_1 int(11) not null, + id_2 int(11) not null, + a_date date, + a_string varchar(100), + primary key (id_1, id_2) +) type=InnoDB; + +create table restaurants ( + franchise_id int(11) not null, + store_id int(11) not null, + name varchar(100), + primary key (franchise_id, store_id) +) type=InnoDB; + +create table restaurants_suburbs ( + franchise_id int(11) not null, + store_id int(11) not null, + city_id int(11) not null, + suburb_id int(11) not null +) type=InnoDB; + +create table dorms ( + id int(11) not null auto_increment, + primary key(id) +) type=InnoDB; + +create table rooms ( + dorm_id int(11) not null, + room_id int(11) not null, + primary key (dorm_id, room_id) +) type=InnoDB; + +create table room_attributes ( + id int(11) not null auto_increment, + name varchar(50), + primary key(id) +) type=InnoDB; + +create table room_attribute_assignments ( + dorm_id int(11) not null, + room_id int(11) not null, + room_attribute_id int(11) not null +) type=InnoDB; + +create table students ( + id int(11) not null auto_increment, + primary key(id) +) type=InnoDB; + +create table room_assignments ( + student_id int(11) not null, + dorm_id int(11) not null, + room_id int(11) not null +) type=InnoDB; + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/oracle.drop.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/oracle.drop.sql new file mode 100644 index 000000000..d23e3a341 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/oracle.drop.sql @@ -0,0 +1,39 @@ +drop table reference_types; +drop sequence reference_types_seq; +drop table reference_codes; +drop table products; +drop sequence products_seq; +drop table tariffs; +drop table product_tariffs; +drop table suburbs; +drop table streets; +drop sequence streets_seq; +drop table users; +drop sequence users_seq; +drop table articles; +drop sequence articles_seq; +drop table readings; +drop sequence readings_seq; +drop table groups; +drop sequence groups_seq; +drop table memberships; +drop table membership_statuses; +drop sequence membership_statuses_seq; +drop table departments; +drop table employees; +drop sequence employees_seq; +drop table comments; +drop sequence comments_seq; +drop table hacks; +drop table kitchen_sinks; +drop table restaurants; +drop table restaurants_suburbs; +drop table dorms; +drop sequence dorms_seq; +drop table rooms; +drop table room_attributes; +drop sequence room_attributes_seq; +drop table room_attribute_assignments; +drop table room_assignments; +drop table students; +drop sequence students_seq; diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/oracle.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/oracle.sql new file mode 100644 index 000000000..8db0ff208 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/oracle.sql @@ -0,0 +1,188 @@ +create sequence reference_types_seq start with 1000; + +create table reference_types ( + reference_type_id number(11) primary key, + type_label varchar2(50) default null, + abbreviation varchar2(50) default null, + description varchar2(50) default null +); + +create table reference_codes ( + reference_type_id number(11), + reference_code number(11), + code_label varchar2(50) default null, + abbreviation varchar2(50) default null, + description varchar2(50) default null +); + +create sequence products_seq start with 1000; + +create table products ( + id number(11) primary key, + name varchar2(50) default null +); + +create table tariffs ( + tariff_id number(11), + start_date date, + amount number(11) default null, + constraint tariffs_pk primary key (tariff_id, start_date) +); + +create table product_tariffs ( + product_id number(11), + tariff_id number(11), + tariff_start_date date, + constraint product_tariffs_pk primary key (product_id, tariff_id, tariff_start_date) +); + +create table suburbs ( + city_id number(11), + suburb_id number(11), + name varchar2(50) not null, + constraint suburbs_pk primary key (city_id, suburb_id) +); + +create sequence streets_seq start with 1000; + +create table streets ( + id number(11) primary key, + city_id number(11) not null, + suburb_id number(11) not null, + name varchar2(50) not null +); + +create sequence users_seq start with 1000; + +create table users ( + id number(11) primary key, + name varchar2(50) not null +); + +create sequence articles_seq start with 1000; + +create table articles ( + id number(11) primary key, + name varchar2(50) not null +); + +create sequence readings_seq start with 1000; + +create table readings ( + id number(11) primary key, + user_id number(11) not null, + article_id number(11) not null, + rating number(11) not null +); + +create sequence groups_seq start with 1000; + +create table groups ( + id number(11) primary key, + name varchar2(50) not null +); + +create table memberships ( + user_id number(11) not null, + group_id number(11) not null, + constraint memberships_pk primary key (user_id, group_id) +); + +create sequence membership_statuses_seq start with 1000; + +create table membership_statuses ( + id number(11) primary key, + user_id number(11) not null, + group_id number(11) not null, + status varchar2(50) not null +); + +create table departments ( + department_id number(11) not null, + location_id number(11) not null, + constraint departments_pk primary key (department_id, location_id) +); + +create sequence employees_seq start with 1000; + +create table employees ( + id number(11) not null primary key, + department_id number(11) default null, + location_id number(11) default null +); + +create sequence comments_seq start with 1000; + +create table comments ( + id number(11) not null primary key, + person_id varchar(100) default null, + person_type varchar(100) default null, + hack_id varchar(100) default null +); + +create table hacks ( + name varchar(50) not null primary key +); + +create table kitchen_sinks ( + id_1 number(11) not null, + id_2 number(11) not null, + a_date date, + a_string varchar(100), + constraint kitchen_sinks_pk primary key (id_1, id_2) +); + +create table restaurants ( + franchise_id number(11) not null, + store_id number(11) not null, + name varchar(100), + constraint restaurants_pk primary key (franchise_id, store_id) +); + +create table restaurants_suburbs ( + franchise_id number(11) not null, + store_id number(11) not null, + city_id number(11) not null, + suburb_id number(11) not null +); + +create sequence dorms_seq start with 1000; + +create table dorms ( + id number(11) not null, + constraint dorms_pk primary key (id) +); + +create table rooms ( + dorm_id number(11) not null, + room_id number(11) not null, + constraint rooms_pk primary key (dorm_id, room_id) +); + +create sequence room_attributes_seq start with 1000; + +create table room_attributes ( + id number(11) not null, + name varchar(50), + constraint room_attributes_pk primary key (id) +); + +create table room_attribute_assignments ( + dorm_id number(11) not null, + room_id number(11) not null, + room_attribute_id number(11) not null +); + +create sequence students_seq start with 1000; + +create table students ( + id number(11) not null, + constraint students_pk primary key (id) +); + +create table room_assignments ( + student_id number(11) not null, + dorm_id number(11) not null, + room_id number(11) not null +); + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/postgresql.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/postgresql.sql new file mode 100644 index 000000000..c3ca7881e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/postgresql.sql @@ -0,0 +1,199 @@ +create sequence public.reference_types_seq start 1000; + +create table reference_types ( + reference_type_id int default nextval('public.reference_types_seq'), + type_label varchar(50) default null, + abbreviation varchar(50) default null, + description varchar(50) default null, + primary key (reference_type_id) +); + +create table reference_codes ( + reference_type_id int, + reference_code int not null, + code_label varchar(50) default null, + abbreviation varchar(50) default null, + description varchar(50) default null +); + +create sequence public.products_seq start 1000; + +create table products ( + id int not null default nextval('public.products_seq'), + name varchar(50) default null, + primary key (id) +); + +create table tariffs ( + tariff_id int not null, + start_date date not null, + amount int default null, + primary key (tariff_id, start_date) +); + +create table product_tariffs ( + product_id int not null, + tariff_id int not null, + tariff_start_date date not null, + primary key (product_id, tariff_id, tariff_start_date) +); + +create table suburbs ( + city_id int not null, + suburb_id int not null, + name varchar(50) not null, + primary key (city_id, suburb_id) +); + +create sequence public.streets_seq start 1000; + +create table streets ( + id int not null default nextval('public.streets_seq'), + city_id int not null, + suburb_id int not null, + name varchar(50) not null, + primary key (id) +); + +create sequence public.users_seq start 1000; + +create table users ( + id int not null default nextval('public.users_seq'), + name varchar(50) not null, + primary key (id) +); + +create sequence public.articles_seq start 1000; + +create table articles ( + id int not null default nextval('public.articles_seq'), + name varchar(50) not null, + primary key (id) +); + +create sequence public.readings_seq start 1000; + +create table readings ( + id int not null default nextval('public.readings_seq'), + user_id int not null, + article_id int not null, + rating int not null, + primary key (id) +); + +create sequence public.groups_seq start 1000; + +create table groups ( + id int not null default nextval('public.groups_seq'), + name varchar(50) not null, + primary key (id) +); + +create table memberships ( + user_id int not null, + group_id int not null, + primary key (user_id, group_id) +); + +create sequence public.membership_statuses_seq start 1000; + +create table membership_statuses ( + id int not null default nextval('public.membership_statuses_seq'), + user_id int not null, + group_id int not null, + status varchar(50) not null, + primary key (id) +); + +create table departments ( + department_id int not null, + location_id int not null, + primary key (department_id, location_id) +); + +create sequence public.employees_seq start 1000; + +create table employees ( + id int not null default nextval('public.employees_seq'), + department_id int default null, + location_id int default null, + primary key (id) +); + +create sequence public.comments_seq start 1000; + +create table comments ( + id int not null default nextval('public.comments_seq'), + person_id varchar(100) default null, + person_type varchar(100) default null, + hack_id varchar(100) default null, + primary key (id) +); + +create table hacks ( + name varchar(50) not null, + primary key (name) +); + +create table kitchen_sinks ( + id_1 int not null, + id_2 int not null, + a_date date, + a_string varchar(100), + primary key (id_1, id_2) +); + +create table restaurants ( + franchise_id int not null, + store_id int not null, + name varchar(100), + primary key (franchise_id, store_id) +); + +create table restaurants_suburbs ( + franchise_id int not null, + store_id int not null, + city_id int not null, + suburb_id int not null +); + +create sequence public.dorms_seq start 1000; + +create table dorms ( + id int not null default nextval('public.dorms_seq'), + primary key (id) +); + +create table rooms ( + dorm_id int not null, + room_id int not null, + primary key (dorm_id, room_id) +); + +create sequence public.room_attributes_seq start 1000; + +create table room_attributes ( + id int not null default nextval('public.room_attributes_seq'), + name varchar(50), + primary key (id) +); + +create table room_attribute_assignments ( + dorm_id int not null, + room_id int not null, + room_attribute_id int not null +); + +create sequence public.students_seq start 1000; + +create table students ( + id int not null default nextval('public.students_seq'), + primary key (id) +); + +create table room_assignments ( + student_id int not null, + dorm_id int not null, + room_id int not null +); + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/sqlite.sql b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/sqlite.sql new file mode 100644 index 000000000..fd8c56687 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/db_definitions/sqlite.sql @@ -0,0 +1,160 @@ +create table reference_types ( + reference_type_id integer primary key, + type_label varchar(50) default null, + abbreviation varchar(50) default null, + description varchar(50) default null +); + +create table reference_codes ( + reference_type_id int(11), + reference_code int(11) not null, + code_label varchar(50) default null, + abbreviation varchar(50) default null, + description varchar(50) default null, + primary key (reference_type_id, reference_code) +); + +create table products ( + id int(11) not null primary key, + name varchar(50) default null +); + +create table tariffs ( + tariff_id int(11) not null, + start_date date not null, + amount integer(11) default null, + primary key (tariff_id, start_date) +); + +create table product_tariffs ( + product_id int(11) not null, + tariff_id int(11) not null, + tariff_start_date date not null, + primary key (product_id, tariff_id, tariff_start_date) +); + +create table suburbs ( + city_id int(11) not null, + suburb_id int(11) not null, + name varchar(50) not null, + primary key (city_id, suburb_id) +); + +create table streets ( + id integer not null primary key autoincrement, + city_id int(11) not null, + suburb_id int(11) not null, + name varchar(50) not null +); + +create table users ( + id integer not null primary key autoincrement, + name varchar(50) not null +); + +create table articles ( + id integer not null primary key autoincrement, + name varchar(50) not null +); + +create table readings ( + id integer not null primary key autoincrement, + user_id int(11) not null, + article_id int(11) not null, + rating int(11) not null +); + +create table groups ( + id integer not null primary key autoincrement, + name varchar(50) not null +); + +create table memberships ( + user_id int not null, + group_id int not null, + primary key (user_id, group_id) +); + +create table membership_statuses ( + id integer not null primary key autoincrement, + user_id int not null, + group_id int not null, + status varchar(50) not null +); + +create table departments ( + department_id integer not null, + location_id integer not null, + primary key (department_id, location_id) +); + +create table employees ( + id integer not null primary key autoincrement, + department_id integer null, + location_id integer null +); + +create table comments ( + id integer not null primary key autoincrement, + person_id varchar(100) null, + person_type varchar(100) null, + hack_id varchar(100) null +); + +create table hacks ( + name varchar(50) not null primary key +); + +create table kitchen_sinks ( + id_1 integer not null, + id_2 integer not null, + a_date date, + a_string varchar(100), + primary key (id_1, id_2) +); + +create table restaurants ( + franchise_id integer not null, + store_id integer not null, + name varchar(100), + primary key (franchise_id, store_id) +); + +create table restaurants_suburbs ( + franchise_id integer not null, + store_id integer not null, + city_id integer not null, + suburb_id integer not null +); + +create table dorms ( + id integer not null primary key autoincrement +); + +create table rooms ( + dorm_id integer not null, + room_id integer not null, + primary key (dorm_id, room_id) +); + +create table room_attributes ( + id integer not null primary key autoincrement, + name varchar(50) +); + +create table room_attribute_assignments ( + dorm_id integer not null, + room_id integer not null, + room_attribute_id integer not null +); + +create table students ( + id integer not null primary key autoincrement +); + +create table room_assignments ( + student_id integer not null, + dorm_id integer not null, + room_id integer not null +); + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/department.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/department.rb new file mode 100644 index 000000000..a76eaf3ca --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/department.rb @@ -0,0 +1,5 @@ +class Department < ActiveRecord::Base + # set_primary_keys *keys - turns on composite key functionality + set_primary_keys :department_id, :location_id + has_many :employees, :foreign_key => [:department_id, :location_id] +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/departments.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/departments.yml new file mode 100644 index 000000000..4213244a4 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/departments.yml @@ -0,0 +1,3 @@ +department1-cpk: + department_id: 1 + location_id: 1 diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/employee.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/employee.rb new file mode 100644 index 000000000..2b47e0989 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/employee.rb @@ -0,0 +1,4 @@ +class Employee < ActiveRecord::Base + belongs_to :department, :foreign_key => [:department_id, :location_id] + has_many :comments, :as => :person +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/employees.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/employees.yml new file mode 100644 index 000000000..c2efd839a --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/employees.yml @@ -0,0 +1,9 @@ +employee1: + id: 1 + department_id: 1 + location_id: 1 +employee2: + id: 2 + department_id: 1 + location_id: 1 + diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/group.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/group.rb new file mode 100644 index 000000000..889ee2f52 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/group.rb @@ -0,0 +1,3 @@ +class Group < ActiveRecord::Base + has_many :memberships +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/groups.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/groups.yml new file mode 100644 index 000000000..a15185e13 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/groups.yml @@ -0,0 +1,3 @@ +cpk: + id: 1 + name: Composite Primary Keys \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/hack.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/hack.rb new file mode 100644 index 000000000..71d6cac83 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/hack.rb @@ -0,0 +1,6 @@ +class Hack < ActiveRecord::Base + set_primary_keys :name + has_many :comments, :as => :person + + has_one :first_comment, :as => :person, :class_name => "Comment" +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/hacks.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/hacks.yml new file mode 100644 index 000000000..29f67b1f4 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/hacks.yml @@ -0,0 +1,2 @@ +andrew: + name: andrew \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership.rb new file mode 100644 index 000000000..d5111e964 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership.rb @@ -0,0 +1,7 @@ +class Membership < ActiveRecord::Base + # set_primary_keys *keys - turns on composite key functionality + set_primary_keys :user_id, :group_id + belongs_to :user + belongs_to :group + has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id] +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership_status.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership_status.rb new file mode 100644 index 000000000..54b687c6c --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership_status.rb @@ -0,0 +1,3 @@ +class MembershipStatus < ActiveRecord::Base + belongs_to :membership, :foreign_key => [:user_id, :group_id] +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership_statuses.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership_statuses.yml new file mode 100644 index 000000000..d3f3c3062 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/membership_statuses.yml @@ -0,0 +1,10 @@ +santiago-cpk: + id: 1 + user_id: 1 + group_id: 1 + status: Active +drnic-cpk: + id: 2 + user_id: 2 + group_id: 1 + status: Owner \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/memberships.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/memberships.yml new file mode 100644 index 000000000..f6cdc84a3 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/memberships.yml @@ -0,0 +1,6 @@ +santiago-cpk: + user_id: 1 + group_id: 1 +drnic-cpk: + user_id: 2 + group_id: 1 \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product.rb new file mode 100644 index 000000000..5466dcabe --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product.rb @@ -0,0 +1,7 @@ +class Product < ActiveRecord::Base + set_primary_keys :id # redundant + has_many :product_tariffs, :foreign_key => :product_id + has_one :product_tariff, :foreign_key => :product_id + + has_many :tariffs, :through => :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date] +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product_tariff.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product_tariff.rb new file mode 100644 index 000000000..cbabee7c5 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product_tariff.rb @@ -0,0 +1,5 @@ +class ProductTariff < ActiveRecord::Base + set_primary_keys :product_id, :tariff_id, :tariff_start_date + belongs_to :product, :foreign_key => :product_id + belongs_to :tariff, :foreign_key => [:tariff_id, :tariff_start_date] +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product_tariffs.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product_tariffs.yml new file mode 100644 index 000000000..27a464fb3 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/product_tariffs.yml @@ -0,0 +1,12 @@ +first_flat: + product_id: 1 + tariff_id: 1 + tariff_start_date: <%= Date.today.to_s(:db) %> +first_free: + product_id: 1 + tariff_id: 2 + tariff_start_date: <%= Date.today.to_s(:db) %> +second_free: + product_id: 2 + tariff_id: 2 + tariff_start_date: <%= Date.today.to_s(:db) %> diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/products.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/products.yml new file mode 100644 index 000000000..3c38a5ba0 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/products.yml @@ -0,0 +1,6 @@ +first_product: + id: 1 + name: Product One +second_product: + id: 2 + name: Product Two \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reading.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reading.rb new file mode 100644 index 000000000..2e8197062 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reading.rb @@ -0,0 +1,4 @@ +class Reading < ActiveRecord::Base + belongs_to :article + belongs_to :user +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/readings.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/readings.yml new file mode 100644 index 000000000..e3afaa9cd --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/readings.yml @@ -0,0 +1,10 @@ +santiago_first: + id: 1 + user_id: 1 + article_id: 1 + rating: 4 +santiago_second: + id: 2 + user_id: 1 + article_id: 2 + rating: 5 \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_code.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_code.rb new file mode 100644 index 000000000..594d8d8be --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_code.rb @@ -0,0 +1,7 @@ +class ReferenceCode < ActiveRecord::Base + set_primary_keys :reference_type_id, :reference_code + + belongs_to :reference_type, :foreign_key => "reference_type_id" + + validates_presence_of :reference_code, :code_label, :abbreviation +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_codes.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_codes.yml new file mode 100644 index 000000000..397938199 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_codes.yml @@ -0,0 +1,28 @@ +name_prefix_mr: + reference_type_id: 1 + reference_code: 1 + code_label: MR + abbreviation: Mr +name_prefix_mrs: + reference_type_id: 1 + reference_code: 2 + code_label: MRS + abbreviation: Mrs +name_prefix_ms: + reference_type_id: 1 + reference_code: 3 + code_label: MS + abbreviation: Ms + +gender_male: + reference_type_id: 2 + reference_code: 1 + code_label: MALE + abbreviation: Male +gender_female: + reference_type_id: 2 + reference_code: 2 + code_label: FEMALE + abbreviation: Female + + \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_type.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_type.rb new file mode 100644 index 000000000..5b2b12b4e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_type.rb @@ -0,0 +1,7 @@ +class ReferenceType < ActiveRecord::Base + set_primary_key :reference_type_id + has_many :reference_codes, :foreign_key => "reference_type_id" + + validates_presence_of :type_label, :abbreviation + validates_uniqueness_of :type_label +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_types.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_types.yml new file mode 100644 index 000000000..0520ba9f9 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/reference_types.yml @@ -0,0 +1,9 @@ +name_prefix: + reference_type_id: 1 + type_label: NAME_PREFIX + abbreviation: Name Prefix + +gender: + reference_type_id: 2 + type_label: GENDER + abbreviation: Gender diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/street.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/street.rb new file mode 100644 index 000000000..de92917d0 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/street.rb @@ -0,0 +1,3 @@ +class Street < ActiveRecord::Base + belongs_to :suburb, :foreign_key => [:city_id, :suburb_id] +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/streets.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/streets.yml new file mode 100644 index 000000000..38998c469 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/streets.yml @@ -0,0 +1,15 @@ +first: + id: 1 + city_id: 1 + suburb_id: 1 + name: First Street +second1: + id: 2 + city_id: 2 + suburb_id: 1 + name: First Street +second2: + id: 3 + city_id: 2 + suburb_id: 1 + name: Second Street \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/suburb.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/suburb.rb new file mode 100644 index 000000000..93045350e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/suburb.rb @@ -0,0 +1,6 @@ +class Suburb < ActiveRecord::Base + set_primary_keys :city_id, :suburb_id + has_many :streets, :foreign_key => [:city_id, :suburb_id] + has_many :first_streets, :foreign_key => [:city_id, :suburb_id], + :class_name => 'Street', :conditions => "streets.name = 'First Street'" +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/suburbs.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/suburbs.yml new file mode 100644 index 000000000..efae0c0a2 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/suburbs.yml @@ -0,0 +1,9 @@ +first: + city_id: 1 + suburb_id: 1 + name: First Suburb +second: + city_id: 2 + suburb_id: 1 + name: Second Suburb + \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/tariff.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/tariff.rb new file mode 100644 index 000000000..d5cb07da1 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/tariff.rb @@ -0,0 +1,6 @@ +class Tariff < ActiveRecord::Base + set_primary_keys [:tariff_id, :start_date] + has_many :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date] + has_one :product_tariff, :foreign_key => [:tariff_id, :tariff_start_date] + has_many :products, :through => :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date] +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/tariffs.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/tariffs.yml new file mode 100644 index 000000000..7346fc510 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/tariffs.yml @@ -0,0 +1,13 @@ +flat: + tariff_id: 1 + start_date: <%= Date.today.to_s(:db) %> + amount: 50 +free: + tariff_id: 2 + start_date: <%= Date.today.to_s(:db) %> + amount: 0 +flat_future: + tariff_id: 1 + start_date: <%= Date.today.next.to_s(:db) %> + amount: 100 + \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/user.rb b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/user.rb new file mode 100644 index 000000000..a8487c49f --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/user.rb @@ -0,0 +1,10 @@ +class User < ActiveRecord::Base + has_many :readings + has_many :articles, :through => :readings + has_many :comments, :as => :person + has_many :hacks, :through => :comments, :source => :hack + + def find_custom_articles + articles.find(:all, :conditions => ["name = ?", "Article One"]) + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/users.yml b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/users.yml new file mode 100644 index 000000000..d33a38a4a --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/fixtures/users.yml @@ -0,0 +1,6 @@ +santiago: + id: 1 + name: Santiago +drnic: + id: 2 + name: Dr Nic \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/hash_tricks.rb b/vendor/gems/composite_primary_keys-1.1.0/test/hash_tricks.rb new file mode 100644 index 000000000..b37bbbbf1 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/hash_tricks.rb @@ -0,0 +1,34 @@ +# From: +# http://www.bigbold.com/snippets/posts/show/2178 +# http://blog.caboo.se/articles/2006/06/11/stupid-hash-tricks +# +# An example utilisation of these methods in a controller is: +# def some_action +# # some script kiddie also passed in :bee, which we don't want tampered with _here_. +# @model = Model.create(params.pass(:foo, :bar)) +# end +class Hash + + # lets through the keys in the argument + # >> {:one => 1, :two => 2, :three => 3}.pass(:one) + # => {:one=>1} + def pass(*keys) + keys = keys.first if keys.first.is_a?(Array) + tmp = self.clone + tmp.delete_if {|k,v| ! keys.include?(k.to_sym) } + tmp.delete_if {|k,v| ! keys.include?(k.to_s) } + tmp + end + + # blocks the keys in the arguments + # >> {:one => 1, :two => 2, :three => 3}.block(:one) + # => {:two=>2, :three=>3} + def block(*keys) + keys = keys.first if keys.first.is_a?(Array) + tmp = self.clone + tmp.delete_if {|k,v| keys.include?(k.to_sym) } + tmp.delete_if {|k,v| keys.include?(k.to_s) } + tmp + end + +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/plugins/pagination.rb b/vendor/gems/composite_primary_keys-1.1.0/test/plugins/pagination.rb new file mode 100644 index 000000000..6a3e1a97b --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/plugins/pagination.rb @@ -0,0 +1,405 @@ +module ActionController + # === Action Pack pagination for Active Record collections + # + # The Pagination module aids in the process of paging large collections of + # Active Record objects. It offers macro-style automatic fetching of your + # model for multiple views, or explicit fetching for single actions. And if + # the magic isn't flexible enough for your needs, you can create your own + # paginators with a minimal amount of code. + # + # The Pagination module can handle as much or as little as you wish. In the + # controller, have it automatically query your model for pagination; or, + # if you prefer, create Paginator objects yourself. + # + # Pagination is included automatically for all controllers. + # + # For help rendering pagination links, see + # ActionView::Helpers::PaginationHelper. + # + # ==== Automatic pagination for every action in a controller + # + # class PersonController < ApplicationController + # model :person + # + # paginate :people, :order => 'last_name, first_name', + # :per_page => 20 + # + # # ... + # end + # + # Each action in this controller now has access to a @people + # instance variable, which is an ordered collection of model objects for the + # current page (at most 20, sorted by last name and first name), and a + # @person_pages Paginator instance. The current page is determined + # by the params[:page] variable. + # + # ==== Pagination for a single action + # + # def list + # @person_pages, @people = + # paginate :people, :order => 'last_name, first_name' + # end + # + # Like the previous example, but explicitly creates @person_pages + # and @people for a single action, and uses the default of 10 items + # per page. + # + # ==== Custom/"classic" pagination + # + # def list + # @person_pages = Paginator.new self, Person.count, 10, params[:page] + # @people = Person.find :all, :order => 'last_name, first_name', + # :limit => @person_pages.items_per_page, + # :offset => @person_pages.current.offset + # end + # + # Explicitly creates the paginator from the previous example and uses + # Paginator#to_sql to retrieve @people from the model. + # + module Pagination + unless const_defined?(:OPTIONS) + # A hash holding options for controllers using macro-style pagination + OPTIONS = Hash.new + + # The default options for pagination + DEFAULT_OPTIONS = { + :class_name => nil, + :singular_name => nil, + :per_page => 10, + :conditions => nil, + :order_by => nil, + :order => nil, + :join => nil, + :joins => nil, + :count => nil, + :include => nil, + :select => nil, + :group => nil, + :parameter => 'page' + } + else + DEFAULT_OPTIONS[:group] = nil + end + + def self.included(base) #:nodoc: + super + base.extend(ClassMethods) + end + + def self.validate_options!(collection_id, options, in_action) #:nodoc: + options.merge!(DEFAULT_OPTIONS) {|key, old, new| old} + + valid_options = DEFAULT_OPTIONS.keys + valid_options << :actions unless in_action + + unknown_option_keys = options.keys - valid_options + raise ActionController::ActionControllerError, + "Unknown options: #{unknown_option_keys.join(', ')}" unless + unknown_option_keys.empty? + + options[:singular_name] ||= ActiveSupport::Inflector.singularize(collection_id.to_s) + options[:class_name] ||= ActiveSupport::Inflector.camelize(options[:singular_name]) + end + + # Returns a paginator and a collection of Active Record model instances + # for the paginator's current page. This is designed to be used in a + # single action; to automatically paginate multiple actions, consider + # ClassMethods#paginate. + # + # +options+ are: + # :singular_name:: the singular name to use, if it can't be inferred by singularizing the collection name + # :class_name:: the class name to use, if it can't be inferred by + # camelizing the singular name + # :per_page:: the maximum number of items to include in a + # single page. Defaults to 10 + # :conditions:: optional conditions passed to Model.find(:all, *params) and + # Model.count + # :order:: optional order parameter passed to Model.find(:all, *params) + # :order_by:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params) + # :joins:: optional joins parameter passed to Model.find(:all, *params) + # and Model.count + # :join:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params) + # and Model.count + # :include:: optional eager loading parameter passed to Model.find(:all, *params) + # and Model.count + # :select:: :select parameter passed to Model.find(:all, *params) + # + # :count:: parameter passed as :select option to Model.count(*params) + # + # :group:: :group parameter passed to Model.find(:all, *params). It forces the use of DISTINCT instead of plain COUNT to come up with the total number of records + # + def paginate(collection_id, options={}) + Pagination.validate_options!(collection_id, options, true) + paginator_and_collection_for(collection_id, options) + end + + # These methods become class methods on any controller + module ClassMethods + # Creates a +before_filter+ which automatically paginates an Active + # Record model for all actions in a controller (or certain actions if + # specified with the :actions option). + # + # +options+ are the same as PaginationHelper#paginate, with the addition + # of: + # :actions:: an array of actions for which the pagination is + # active. Defaults to +nil+ (i.e., every action) + def paginate(collection_id, options={}) + Pagination.validate_options!(collection_id, options, false) + module_eval do + before_filter :create_paginators_and_retrieve_collections + OPTIONS[self] ||= Hash.new + OPTIONS[self][collection_id] = options + end + end + end + + def create_paginators_and_retrieve_collections #:nodoc: + Pagination::OPTIONS[self.class].each do |collection_id, options| + next unless options[:actions].include? action_name if + options[:actions] + + paginator, collection = + paginator_and_collection_for(collection_id, options) + + paginator_name = "@#{options[:singular_name]}_pages" + self.instance_variable_set(paginator_name, paginator) + + collection_name = "@#{collection_id.to_s}" + self.instance_variable_set(collection_name, collection) + end + end + + # Returns the total number of items in the collection to be paginated for + # the +model+ and given +conditions+. Override this method to implement a + # custom counter. + def count_collection_for_pagination(model, options) + model.count(:conditions => options[:conditions], + :joins => options[:join] || options[:joins], + :include => options[:include], + :select => (options[:group] ? "DISTINCT #{options[:group]}" : options[:count])) + end + + # Returns a collection of items for the given +model+ and +options[conditions]+, + # ordered by +options[order]+, for the current page in the given +paginator+. + # Override this method to implement a custom finder. + def find_collection_for_pagination(model, options, paginator) + model.find(:all, :conditions => options[:conditions], + :order => options[:order_by] || options[:order], + :joins => options[:join] || options[:joins], :include => options[:include], + :select => options[:select], :limit => options[:per_page], + :group => options[:group], :offset => paginator.current.offset) + end + + protected :create_paginators_and_retrieve_collections, + :count_collection_for_pagination, + :find_collection_for_pagination + + def paginator_and_collection_for(collection_id, options) #:nodoc: + klass = options[:class_name].constantize + page = params[options[:parameter]] + count = count_collection_for_pagination(klass, options) + paginator = Paginator.new(self, count, options[:per_page], page) + collection = find_collection_for_pagination(klass, options, paginator) + + return paginator, collection + end + + private :paginator_and_collection_for + + # A class representing a paginator for an Active Record collection. + class Paginator + include Enumerable + + # Creates a new Paginator on the given +controller+ for a set of items + # of size +item_count+ and having +items_per_page+ items per page. + # Raises ArgumentError if items_per_page is out of bounds (i.e., less + # than or equal to zero). The page CGI parameter for links defaults to + # "page" and can be overridden with +page_parameter+. + def initialize(controller, item_count, items_per_page, current_page=1) + raise ArgumentError, 'must have at least one item per page' if + items_per_page <= 0 + + @controller = controller + @item_count = item_count || 0 + @items_per_page = items_per_page + @pages = {} + + self.current_page = current_page + end + attr_reader :controller, :item_count, :items_per_page + + # Sets the current page number of this paginator. If +page+ is a Page + # object, its +number+ attribute is used as the value; if the page does + # not belong to this Paginator, an ArgumentError is raised. + def current_page=(page) + if page.is_a? Page + raise ArgumentError, 'Page/Paginator mismatch' unless + page.paginator == self + end + page = page.to_i + @current_page_number = has_page_number?(page) ? page : 1 + end + + # Returns a Page object representing this paginator's current page. + def current_page + @current_page ||= self[@current_page_number] + end + alias current :current_page + + # Returns a new Page representing the first page in this paginator. + def first_page + @first_page ||= self[1] + end + alias first :first_page + + # Returns a new Page representing the last page in this paginator. + def last_page + @last_page ||= self[page_count] + end + alias last :last_page + + # Returns the number of pages in this paginator. + def page_count + @page_count ||= @item_count.zero? ? 1 : + (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1) + end + + alias length :page_count + + # Returns true if this paginator contains the page of index +number+. + def has_page_number?(number) + number >= 1 and number <= page_count + end + + # Returns a new Page representing the page with the given index + # +number+. + def [](number) + @pages[number] ||= Page.new(self, number) + end + + # Successively yields all the paginator's pages to the given block. + def each(&block) + page_count.times do |n| + yield self[n+1] + end + end + + # A class representing a single page in a paginator. + class Page + include Comparable + + # Creates a new Page for the given +paginator+ with the index + # +number+. If +number+ is not in the range of valid page numbers or + # is not a number at all, it defaults to 1. + def initialize(paginator, number) + @paginator = paginator + @number = number.to_i + @number = 1 unless @paginator.has_page_number? @number + end + attr_reader :paginator, :number + alias to_i :number + + # Compares two Page objects and returns true when they represent the + # same page (i.e., their paginators are the same and they have the + # same page number). + def ==(page) + return false if page.nil? + @paginator == page.paginator and + @number == page.number + end + + # Compares two Page objects and returns -1 if the left-hand page comes + # before the right-hand page, 0 if the pages are equal, and 1 if the + # left-hand page comes after the right-hand page. Raises ArgumentError + # if the pages do not belong to the same Paginator object. + def <=>(page) + raise ArgumentError unless @paginator == page.paginator + @number <=> page.number + end + + # Returns the item offset for the first item in this page. + def offset + @paginator.items_per_page * (@number - 1) + end + + # Returns the number of the first item displayed. + def first_item + offset + 1 + end + + # Returns the number of the last item displayed. + def last_item + [@paginator.items_per_page * @number, @paginator.item_count].min + end + + # Returns true if this page is the first page in the paginator. + def first? + self == @paginator.first + end + + # Returns true if this page is the last page in the paginator. + def last? + self == @paginator.last + end + + # Returns a new Page object representing the page just before this + # page, or nil if this is the first page. + def previous + if first? then nil else @paginator[@number - 1] end + end + + # Returns a new Page object representing the page just after this + # page, or nil if this is the last page. + def next + if last? then nil else @paginator[@number + 1] end + end + + # Returns a new Window object for this page with the specified + # +padding+. + def window(padding=2) + Window.new(self, padding) + end + + # Returns the limit/offset array for this page. + def to_sql + [@paginator.items_per_page, offset] + end + + def to_param #:nodoc: + @number.to_s + end + end + + # A class for representing ranges around a given page. + class Window + # Creates a new Window object for the given +page+ with the specified + # +padding+. + def initialize(page, padding=2) + @paginator = page.paginator + @page = page + self.padding = padding + end + attr_reader :paginator, :page + + # Sets the window's padding (the number of pages on either side of the + # window page). + def padding=(padding) + @padding = padding < 0 ? 0 : padding + # Find the beginning and end pages of the window + @first = @paginator.has_page_number?(@page.number - @padding) ? + @paginator[@page.number - @padding] : @paginator.first + @last = @paginator.has_page_number?(@page.number + @padding) ? + @paginator[@page.number + @padding] : @paginator.last + end + attr_reader :padding, :first, :last + + # Returns an array of Page objects in the current window. + def pages + (@first.number..@last.number).to_a.collect! {|n| @paginator[n]} + end + alias to_a :pages + end + end + + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/plugins/pagination_helper.rb b/vendor/gems/composite_primary_keys-1.1.0/test/plugins/pagination_helper.rb new file mode 100644 index 000000000..069d77566 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/plugins/pagination_helper.rb @@ -0,0 +1,135 @@ +module ActionView + module Helpers + # Provides methods for linking to ActionController::Pagination objects using a simple generator API. You can optionally + # also build your links manually using ActionView::Helpers::AssetHelper#link_to like so: + # + # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %> + # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %> + module PaginationHelper + unless const_defined?(:DEFAULT_OPTIONS) + DEFAULT_OPTIONS = { + :name => :page, + :window_size => 2, + :always_show_anchors => true, + :link_to_current_page => false, + :params => {} + } + end + + # Creates a basic HTML link bar for the given +paginator+. Links will be created + # for the next and/or previous page and for a number of other pages around the current + # pages position. The +html_options+ hash is passed to +link_to+ when the links are created. + # + # ==== Options + # :name:: the routing name for this paginator + # (defaults to +page+) + # :prefix:: prefix for pagination links + # (i.e. Older Pages: 1 2 3 4) + # :suffix:: suffix for pagination links + # (i.e. 1 2 3 4 <- Older Pages) + # :window_size:: the number of pages to show around + # the current page (defaults to 2) + # :always_show_anchors:: whether or not the first and last + # pages should always be shown + # (defaults to +true+) + # :link_to_current_page:: whether or not the current page + # should be linked to (defaults to + # +false+) + # :params:: any additional routing parameters + # for page URLs + # + # ==== Examples + # # We'll assume we have a paginator setup in @person_pages... + # + # pagination_links(@person_pages) + # # => 1 2 3 ... 10 + # + # pagination_links(@person_pages, :link_to_current_page => true) + # # => 1 2 3 ... 10 + # + # pagination_links(@person_pages, :always_show_anchors => false) + # # => 1 2 3 + # + # pagination_links(@person_pages, :window_size => 1) + # # => 1 2 ... 10 + # + # pagination_links(@person_pages, :params => { :viewer => "flash" }) + # # => 1 2 3 ... + # # 10 + def pagination_links(paginator, options={}, html_options={}) + name = options[:name] || DEFAULT_OPTIONS[:name] + params = (options[:params] || DEFAULT_OPTIONS[:params]).clone + + prefix = options[:prefix] || '' + suffix = options[:suffix] || '' + + pagination_links_each(paginator, options, prefix, suffix) do |n| + params[name] = n + link_to(n.to_s, params, html_options) + end + end + + # Iterate through the pages of a given +paginator+, invoking a + # block for each page number that needs to be rendered as a link. + # + # ==== Options + # :window_size:: the number of pages to show around + # the current page (defaults to +2+) + # :always_show_anchors:: whether or not the first and last + # pages should always be shown + # (defaults to +true+) + # :link_to_current_page:: whether or not the current page + # should be linked to (defaults to + # +false+) + # + # ==== Example + # # Turn paginated links into an Ajax call + # pagination_links_each(paginator, page_options) do |link| + # options = { :url => {:action => 'list'}, :update => 'results' } + # html_options = { :href => url_for(:action => 'list') } + # + # link_to_remote(link.to_s, options, html_options) + # end + def pagination_links_each(paginator, options, prefix = nil, suffix = nil) + options = DEFAULT_OPTIONS.merge(options) + link_to_current_page = options[:link_to_current_page] + always_show_anchors = options[:always_show_anchors] + + current_page = paginator.current_page + window_pages = current_page.window(options[:window_size]).pages + return if window_pages.length <= 1 unless link_to_current_page + + first, last = paginator.first, paginator.last + + html = '' + + html << prefix if prefix + + if always_show_anchors and not (wp_first = window_pages[0]).first? + html << yield(first.number) + html << ' ... ' if wp_first.number - first.number > 1 + html << ' ' + end + + window_pages.each do |page| + if current_page == page && !link_to_current_page + html << page.number.to_s + else + html << yield(page.number) + end + html << ' ' + end + + if always_show_anchors and not (wp_last = window_pages[-1]).last? + html << ' ... ' if last.number - wp_last.number > 1 + html << yield(last.number) + end + + html << suffix if suffix + + html + end + + end # PaginationHelper + end # Helpers +end # ActionView diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_associations.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_associations.rb new file mode 100644 index 000000000..78302f86c --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_associations.rb @@ -0,0 +1,160 @@ +require 'abstract_unit' +require 'fixtures/article' +require 'fixtures/product' +require 'fixtures/tariff' +require 'fixtures/product_tariff' +require 'fixtures/suburb' +require 'fixtures/street' +require 'fixtures/restaurant' +require 'fixtures/dorm' +require 'fixtures/room' +require 'fixtures/room_attribute' +require 'fixtures/room_attribute_assignment' +require 'fixtures/student' +require 'fixtures/room_assignment' +require 'fixtures/user' +require 'fixtures/reading' + +class TestAssociations < Test::Unit::TestCase + fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants, :restaurants_suburbs, + :dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings + + def test_has_many_through_with_conditions_when_through_association_is_not_composite + user = User.find(:first) + assert_equal 1, user.articles.find(:all, :conditions => ["articles.name = ?", "Article One"]).size + end + + def test_has_many_through_with_conditions_when_through_association_is_composite + room = Room.find(:first) + assert_equal 0, room.room_attributes.find(:all, :conditions => ["room_attributes.name != ?", "keg"]).size + end + + def test_has_many_through_on_custom_finder_when_through_association_is_composite_finder_when_through_association_is_not_composite + user = User.find(:first) + assert_equal 1, user.find_custom_articles.size + end + + def test_has_many_through_on_custom_finder_when_through_association_is_composite + room = Room.find(:first) + assert_equal 0, room.find_custom_room_attributes.size + end + + def test_count + assert_equal 2, Product.count(:include => :product_tariffs) + assert_equal 3, Tariff.count(:include => :product_tariffs) + assert_equal 2, Tariff.count(:group => :start_date).size + end + + def test_products + assert_not_nil products(:first_product).product_tariffs + assert_equal 2, products(:first_product).product_tariffs.length + assert_not_nil products(:first_product).tariffs + assert_equal 2, products(:first_product).tariffs.length + assert_not_nil products(:first_product).product_tariff + end + + def test_product_tariffs + assert_not_nil product_tariffs(:first_flat).product + assert_not_nil product_tariffs(:first_flat).tariff + assert_equal Product, product_tariffs(:first_flat).product.class + assert_equal Tariff, product_tariffs(:first_flat).tariff.class + end + + def test_tariffs + assert_not_nil tariffs(:flat).product_tariffs + assert_equal 1, tariffs(:flat).product_tariffs.length + assert_not_nil tariffs(:flat).products + assert_equal 1, tariffs(:flat).products.length + assert_not_nil tariffs(:flat).product_tariff + end + + # Its not generating the instances of associated classes from the rows + def test_find_includes_products + assert @products = Product.find(:all, :include => :product_tariffs) + assert_equal 2, @products.length + assert_not_nil @products.first.instance_variable_get('@product_tariffs'), '@product_tariffs not set; should be array' + assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, + "Incorrect number of product_tariffs returned" + end + + def test_find_includes_tariffs + assert @tariffs = Tariff.find(:all, :include => :product_tariffs) + assert_equal 3, @tariffs.length + assert_not_nil @tariffs.first.instance_variable_get('@product_tariffs'), '@product_tariffs not set; should be array' + assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, + "Incorrect number of product_tariffs returnedturned" + end + + def test_find_includes_product + assert @product_tariffs = ProductTariff.find(:all, :include => :product) + assert_equal 3, @product_tariffs.length + assert_not_nil @product_tariffs.first.instance_variable_get('@product'), '@product not set' + end + + def test_find_includes_comp_belongs_to_tariff + assert @product_tariffs = ProductTariff.find(:all, :include => :tariff) + assert_equal 3, @product_tariffs.length + assert_not_nil @product_tariffs.first.instance_variable_get('@tariff'), '@tariff not set' + end + + def test_find_includes_extended + assert @products = Product.find(:all, :include => {:product_tariffs => :tariff}) + assert_equal 3, @products.inject(0) {|sum, product| sum + product.instance_variable_get('@product_tariffs').length}, + "Incorrect number of product_tariffs returned" + + assert @tariffs = Tariff.find(:all, :include => {:product_tariffs => :product}) + assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, + "Incorrect number of product_tariffs returned" + end + + def test_join_where_clause + @product = Product.find(:first, :include => :product_tariffs) + where_clause = @product.product_tariffs.composite_where_clause( + ['foo','bar'], [1,2] + ) + assert_equal('(foo=1 AND bar=2)', where_clause) + end + + def test_has_many_through + @products = Product.find(:all, :include => :tariffs) + assert_equal 3, @products.inject(0) {|sum, product| sum + product.instance_variable_get('@tariffs').length}, + "Incorrect number of tariffs returned" + end + + def test_has_many_through_when_not_pre_loaded + student = Student.find(:first) + rooms = student.rooms + assert_equal 1, rooms.size + assert_equal 1, rooms.first.dorm_id + assert_equal 1, rooms.first.room_id + end + + def test_has_many_through_when_through_association_is_composite + dorm = Dorm.find(:first) + assert_equal 1, dorm.rooms.length + assert_equal 1, dorm.rooms.first.room_attributes.length + assert_equal 'keg', dorm.rooms.first.room_attributes.first.name + end + + def test_associations_with_conditions + @suburb = Suburb.find([2, 1]) + assert_equal 2, @suburb.streets.size + + @suburb = Suburb.find([2, 1]) + assert_equal 1, @suburb.first_streets.size + + @suburb = Suburb.find([2, 1], :include => :streets) + assert_equal 2, @suburb.streets.size + + @suburb = Suburb.find([2, 1], :include => :first_streets) + assert_equal 1, @suburb.first_streets.size + end + + def test_has_and_belongs_to_many + @restaurant = Restaurant.find([1,1]) + assert_equal 2, @restaurant.suburbs.size + + @restaurant = Restaurant.find([1,1], :include => :suburbs) + assert_equal 2, @restaurant.suburbs.size + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_attribute_methods.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_attribute_methods.rb new file mode 100644 index 000000000..b020a64ca --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_attribute_methods.rb @@ -0,0 +1,22 @@ +require 'abstract_unit' +require 'fixtures/kitchen_sink' +require 'fixtures/reference_type' + +class TestAttributeMethods < Test::Unit::TestCase + fixtures :kitchen_sinks, :reference_types + + def test_read_attribute_with_single_key + rt = ReferenceType.find(1) + assert_equal(1, rt.reference_type_id) + assert_equal('NAME_PREFIX', rt.type_label) + assert_equal('Name Prefix', rt.abbreviation) + end + + def test_read_attribute_with_composite_keys + sink = KitchenSink.find(1,2) + assert_equal(1, sink.id_1) + assert_equal(2, sink.id_2) + assert_equal(Date.today, sink.a_date.to_date) + assert_equal('string', sink.a_string) + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_attributes.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_attributes.rb new file mode 100644 index 000000000..750408202 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_attributes.rb @@ -0,0 +1,84 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' +require 'fixtures/product' +require 'fixtures/tariff' +require 'fixtures/product_tariff' + +class TestAttributes < Test::Unit::TestCase + fixtures :reference_types, :reference_codes, :products, :tariffs, :product_tariffs + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_brackets + testing_with do + @first.attributes.each_pair do |attr_name, value| + assert_equal value, @first[attr_name] + end + end + end + + def test_brackets_primary_key + testing_with do + assert_equal @first.id, @first[@primary_keys], "[] failing for #{@klass}" + assert_equal @first.id, @first[@first.class.primary_key] + end + end + + def test_brackets_assignment + testing_with do + @first.attributes.each_pair do |attr_name, value| + @first[attr_name]= !value.nil? ? value * 2 : '1' + assert_equal !value.nil? ? value * 2 : '1', @first[attr_name] + end + end + end + + def test_brackets_foreign_key_assignment + @flat = Tariff.find(1, Date.today.to_s(:db)) + @second_free = ProductTariff.find(2,2,Date.today.to_s(:db)) + @second_free_fk = [:tariff_id, :tariff_start_date] + @second_free[key = @second_free_fk] = @flat.id + compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk) + assert_equal @flat.id, @second_free[key] + @second_free[key = @second_free_fk.to_composite_ids] = @flat.id + assert_equal @flat.id, @second_free[key] + compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk) + @second_free[key = @second_free_fk.to_composite_ids] = @flat.id.to_s + assert_equal @flat.id, @second_free[key] + compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk) + @second_free[key = @second_free_fk.to_composite_ids] = @flat.id.to_s + assert_equal @flat.id, @second_free[key] + compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk) + @second_free[key = @second_free_fk.to_composite_ids.to_s] = @flat.id + assert_equal @flat.id, @second_free[key] + compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk) + @second_free[key = @second_free_fk.to_composite_ids.to_s] = @flat.id.to_s + assert_equal @flat.id, @second_free[key] + compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk) + end +private + def compare_indexes(obj_name1, indexes1, obj_name2, indexes2) + obj1, obj2 = eval "[#{obj_name1}, #{obj_name2}]" + indexes1.length.times do |key_index| + assert_equal obj1[indexes1[key_index].to_s], + obj2[indexes2[key_index].to_s], + "#{obj_name1}[#{indexes1[key_index]}]=#{obj1[indexes1[key_index].to_s].inspect} != " + + "#{obj_name2}[#{indexes2[key_index]}]=#{obj2[indexes2[key_index].to_s].inspect}; " + + "#{obj_name2} = #{obj2.inspect}" + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_clone.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_clone.rb new file mode 100644 index 000000000..822974430 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_clone.rb @@ -0,0 +1,34 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +class TestClone < Test::Unit::TestCase + fixtures :reference_types, :reference_codes + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_truth + testing_with do + clone = @first.clone + assert_equal @first.attributes.block(@klass.primary_key), clone.attributes + if composite? + @klass.primary_key.each {|key| assert_nil clone[key], "Primary key '#{key}' should be nil"} + else + assert_nil clone[@klass.primary_key], "Sole primary key should be nil" + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_composite_arrays.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_composite_arrays.rb new file mode 100644 index 000000000..41e21f8f8 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_composite_arrays.rb @@ -0,0 +1,51 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +class CompositeArraysTest < Test::Unit::TestCase + + def test_new_primary_keys + keys = CompositePrimaryKeys::CompositeKeys.new + assert_not_nil keys + assert_equal '', keys.to_s + assert_equal '', "#{keys}" + end + + def test_initialize_primary_keys + keys = CompositePrimaryKeys::CompositeKeys.new([1,2,3]) + assert_not_nil keys + assert_equal '1,2,3', keys.to_s + assert_equal '1,2,3', "#{keys}" + end + + def test_to_composite_keys + keys = [1,2,3].to_composite_keys + assert_equal CompositePrimaryKeys::CompositeKeys, keys.class + assert_equal '1,2,3', keys.to_s + end + + def test_new_ids + keys = CompositePrimaryKeys::CompositeIds.new + assert_not_nil keys + assert_equal '', keys.to_s + assert_equal '', "#{keys}" + end + + def test_initialize_ids + keys = CompositePrimaryKeys::CompositeIds.new([1,2,3]) + assert_not_nil keys + assert_equal '1,2,3', keys.to_s + assert_equal '1,2,3', "#{keys}" + end + + def test_to_composite_ids + keys = [1,2,3].to_composite_ids + assert_equal CompositePrimaryKeys::CompositeIds, keys.class + assert_equal '1,2,3', keys.to_s + end + + def test_flatten + keys = [CompositePrimaryKeys::CompositeIds.new([1,2,3]), CompositePrimaryKeys::CompositeIds.new([4,5,6])] + assert_equal 6, keys.flatten.size + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_create.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_create.rb new file mode 100644 index 000000000..dfbc77397 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_create.rb @@ -0,0 +1,68 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' +require 'fixtures/street' +require 'fixtures/suburb' + +class TestCreate < Test::Unit::TestCase + fixtures :reference_types, :reference_codes, :streets, :suburbs + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + :create => {:reference_type_id => 10, :type_label => 'NEW_TYPE', :abbreviation => 'New Type'} + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + :create => {:reference_type_id => 1, :reference_code => 20, :code_label => 'NEW_CODE', :abbreviation => 'New Code'} + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_setup + testing_with do + assert_not_nil @klass_info[:create] + end + end + + def test_create + testing_with do + assert new_obj = @klass.create(@klass_info[:create]) + assert !new_obj.new_record? + end + end + + def test_create_no_id + testing_with do + begin + @obj = @klass.create(@klass_info[:create].block(@klass.primary_key)) + @successful = !composite? + rescue CompositePrimaryKeys::ActiveRecord::CompositeKeyError + @successful = false + rescue + flunk "Incorrect exception raised: #{$!}, #{$!.class}" + end + assert_equal composite?, !@successful, "Create should have failed for composites; #{@obj.inspect}" + end + end + + def test_create_on_association + suburb = Suburb.find(:first) + suburb.streets.create(:name => "my street") + street = Street.find_by_name('my street') + assert_equal(suburb.city_id, street.city_id) + assert_equal(suburb.suburb_id, street.suburb_id) + end + + def test_create_on_association_when_belongs_to_is_single_key + rt = ReferenceType.find(:first) + rt.reference_codes.create(:reference_code => 4321, :code_label => 'foo', :abbreviation => 'bar') + rc = ReferenceCode.find_by_reference_code(4321) + assert_equal(rc.reference_type_id, rt.reference_type_id) + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_delete.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_delete.rb new file mode 100644 index 000000000..cd79bbd72 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_delete.rb @@ -0,0 +1,96 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' +require 'fixtures/department' +require 'fixtures/employee' + +class TestDelete < Test::Unit::TestCase + fixtures :reference_types, :reference_codes, :departments, :employees + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_destroy_one + testing_with do + #assert @first.destroy + assert true + end + end + + def test_destroy_one_via_class + testing_with do + assert @klass.destroy(*@first.id) + end + end + + def test_destroy_one_alone_via_class + testing_with do + assert @klass.destroy(@first.id) + end + end + + def test_delete_one + testing_with do + assert @klass.delete(*@first.id) if composite? + end + end + + def test_delete_one_alone + testing_with do + assert @klass.delete(@first.id) + end + end + + def test_delete_many + testing_with do + to_delete = @klass.find(:all)[0..1] + assert_equal 2, to_delete.length + end + end + + def test_delete_all + testing_with do + @klass.delete_all + end + end + + def test_clear_association + department = Department.find(1,1) + assert_equal 2, department.employees.size, "Before clear employee count should be 2." + department.employees.clear + assert_equal 0, department.employees.size, "After clear employee count should be 0." + department.reload + assert_equal 0, department.employees.size, "After clear and a reload from DB employee count should be 0." + end + + def test_delete_association + department = Department.find(1,1) + assert_equal 2, department.employees.size , "Before delete employee count should be 2." + first_employee = department.employees[0] + department.employees.delete(first_employee) + assert_equal 1, department.employees.size, "After delete employee count should be 1." + department.reload + assert_equal 1, department.employees.size, "After delete and a reload from DB employee count should be 1." + end + + def test_delete_records_for_has_many_association_with_composite_primary_key + reference_type = ReferenceType.find(1) + codes_to_delete = reference_type.reference_codes[0..1] + assert_equal 3, reference_type.reference_codes.size, "Before deleting records reference_code count should be 3." + reference_type.reference_codes.delete_records(codes_to_delete) + reference_type.reload + assert_equal 1, reference_type.reference_codes.size, "After deleting 2 records and a reload from DB reference_code count should be 1." + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_dummy.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_dummy.rb new file mode 100644 index 000000000..44386685b --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_dummy.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +class TestDummy < Test::Unit::TestCase + fixtures :reference_types, :reference_codes + + classes = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + } + + def setup + self.class.classes = classes + end + + def test_truth + testing_with do + assert true + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_find.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_find.rb new file mode 100644 index 000000000..a07d30a64 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_find.rb @@ -0,0 +1,73 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +# Testing the find action on composite ActiveRecords with two primary keys +class TestFind < Test::Unit::TestCase + fixtures :reference_types, :reference_codes + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => [:reference_type_id], + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + :dual_strs => { + :class => ReferenceCode, + :primary_keys => ['reference_type_id', 'reference_code'], + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_find_first + testing_with do + obj = @klass.find(:first) + assert obj + assert_equal @klass, obj.class + end + end + + def test_find + testing_with do + found = @klass.find(*first_id) # e.g. find(1,1) or find 1,1 + assert found + assert_equal @klass, found.class + assert_equal found, @klass.find(found.id) + assert_equal found, @klass.find(found.to_param) + end + end + + def test_find_composite_ids + testing_with do + found = @klass.find(first_id) # e.g. find([1,1].to_composite_ids) + assert found + assert_equal @klass, found.class + assert_equal found, @klass.find(found.id) + assert_equal found, @klass.find(found.to_param) + end + end + + def test_to_param + testing_with do + assert_equal first_id_str, @first.to_param.to_s + end + end + + def things_to_look_at + testing_with do + assert_equal found, @klass.find(found.id.to_s) # fails for 2+ keys + end + end + + def test_not_found + assert_raise(::ActiveRecord::RecordNotFound) do + ReferenceCode.send :find, '999,999' + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_ids.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_ids.rb new file mode 100644 index 000000000..9ba2d92a7 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_ids.rb @@ -0,0 +1,97 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +class TestIds < Test::Unit::TestCase + fixtures :reference_types, :reference_codes + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => [:reference_type_id], + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + :dual_strs => { + :class => ReferenceCode, + :primary_keys => ['reference_type_id', 'reference_code'], + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_id + testing_with do + assert_equal @first.id, @first.ids if composite? + end + end + + def test_id_to_s + testing_with do + assert_equal first_id_str, @first.id.to_s + assert_equal first_id_str, "#{@first.id}" + end + end + + def test_ids_to_s + testing_with do + order = @klass.primary_key.is_a?(String) ? @klass.primary_key : @klass.primary_key.join(',') + to_test = @klass.find(:all, :order => order)[0..1].map(&:id) + assert_equal '(1,1),(1,2)', @klass.ids_to_s(to_test) if @key_test == :dual + assert_equal '1,1;1,2', @klass.ids_to_s(to_test, ',', ';', '', '') if @key_test == :dual + end + end + + def test_composite_where_clause + testing_with do + where = 'reference_codes.reference_type_id=1 AND reference_codes.reference_code=2) OR (reference_codes.reference_type_id=2 AND reference_codes.reference_code=2' + assert_equal(where, @klass.composite_where_clause([[1, 2], [2, 2]])) if @key_test == :dual + end + end + + def test_set_ids_string + testing_with do + array = @primary_keys.collect {|key| 5} + expected = composite? ? array.to_composite_keys : array.first + @first.id = expected.to_s + assert_equal expected, @first.id + end + end + + def test_set_ids_array + testing_with do + array = @primary_keys.collect {|key| 5} + expected = composite? ? array.to_composite_keys : array.first + @first.id = expected + assert_equal expected, @first.id + end + end + + def test_set_ids_comp + testing_with do + array = @primary_keys.collect {|key| 5} + expected = composite? ? array.to_composite_keys : array.first + @first.id = expected + assert_equal expected, @first.id + end + end + + def test_primary_keys + testing_with do + if composite? + assert_not_nil @klass.primary_keys + assert_equal @primary_keys.map {|key| key.to_sym}, @klass.primary_keys + assert_equal @klass.primary_keys, @klass.primary_key + else + assert_not_nil @klass.primary_key + assert_equal @primary_keys, [@klass.primary_key.to_sym] + end + assert_equal @primary_keys.join(','), @klass.primary_key.to_s + # Need a :primary_keys should be Array with to_s overridden + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_miscellaneous.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_miscellaneous.rb new file mode 100644 index 000000000..25f6096fe --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_miscellaneous.rb @@ -0,0 +1,39 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +class TestMiscellaneous < Test::Unit::TestCase + fixtures :reference_types, :reference_codes, :products + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_composite_class + testing_with do + assert_equal composite?, @klass.composite? + end + end + + def test_composite_instance + testing_with do + assert_equal composite?, @first.composite? + end + end + + def test_count + assert_equal 2, Product.count + end + +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_pagination.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_pagination.rb new file mode 100644 index 000000000..fa19d95a6 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_pagination.rb @@ -0,0 +1,38 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' +require 'plugins/pagination' + +class TestPagination < Test::Unit::TestCase + fixtures :reference_types, :reference_codes + + include ActionController::Pagination + DEFAULT_PAGE_SIZE = 2 + + attr_accessor :params + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + :table => :reference_types, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + :table => :reference_codes, + }, + } + + def setup + self.class.classes = CLASSES + @params = {} + end + + def test_paginate_all + testing_with do + @object_pages, @objects = paginate @klass_info[:table], :per_page => DEFAULT_PAGE_SIZE + assert_equal 2, @objects.length, "Each page should have #{DEFAULT_PAGE_SIZE} items" + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_polymorphic.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_polymorphic.rb new file mode 100644 index 000000000..a632da977 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_polymorphic.rb @@ -0,0 +1,31 @@ +require 'abstract_unit' +require 'fixtures/comment' +require 'fixtures/user' +require 'fixtures/employee' +require 'fixtures/hack' + +class TestPolymorphic < Test::Unit::TestCase + fixtures :users, :employees, :comments, :hacks + + def test_polymorphic_has_many + comments = Hack.find('andrew').comments + assert_equal 'andrew', comments[0].person_id + end + + def test_polymorphic_has_one + first_comment = Hack.find('andrew').first_comment + assert_equal 'andrew', first_comment.person_id + end + + def test_has_many_through + user = users(:santiago) + article_names = user.articles.collect { |a| a.name }.sort + assert_equal ['Article One', 'Article Two'], article_names + end + + def test_polymorphic_has_many_through + user = users(:santiago) + assert_equal ['andrew'], user.hacks.collect { |a| a.name }.sort + end + +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_santiago.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_santiago.rb new file mode 100644 index 000000000..4b5f433e4 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_santiago.rb @@ -0,0 +1,27 @@ +# Test cases devised by Santiago that broke the Composite Primary Keys +# code at one point in time. But no more!!! + +require 'abstract_unit' +require 'fixtures/user' +require 'fixtures/article' +require 'fixtures/reading' + +class TestSantiago < Test::Unit::TestCase + fixtures :suburbs, :streets, :users, :articles, :readings + + def test_normal_and_composite_associations + assert_not_nil @suburb = Suburb.find(1,1) + assert_equal 1, @suburb.streets.length + + assert_not_nil @street = Street.find(1) + assert_not_nil @street.suburb + end + + def test_single_keys + @santiago = User.find(1) + assert_not_nil @santiago.articles + assert_equal 2, @santiago.articles.length + assert_not_nil @santiago.readings + assert_equal 2, @santiago.readings.length + end +end diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_tutorial_examle.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_tutorial_examle.rb new file mode 100644 index 000000000..01f9ec603 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_tutorial_examle.rb @@ -0,0 +1,26 @@ +require 'abstract_unit' +require 'fixtures/user' +require 'fixtures/group' +require 'fixtures/membership_status' +require 'fixtures/membership' + +class TestTutorialExample < Test::Unit::TestCase + fixtures :users, :groups, :memberships, :membership_statuses + + def test_membership + assert(membership = Membership.find(1,1), "Cannot find a membership") + assert(membership.user) + assert(membership.group) + end + + def test_status + assert(membership = Membership.find(1,1), "Cannot find a membership") + assert(statuses = membership.statuses, "No has_many association to status") + assert_equal(membership, statuses.first.membership) + end + + def test_count + assert(membership = Membership.find(1,1), "Cannot find a membership") + assert_equal(1, membership.statuses.count) + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/test/test_update.rb b/vendor/gems/composite_primary_keys-1.1.0/test/test_update.rb new file mode 100644 index 000000000..d612c92a8 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/test/test_update.rb @@ -0,0 +1,40 @@ +require 'abstract_unit' +require 'fixtures/reference_type' +require 'fixtures/reference_code' + +class TestUpdate < Test::Unit::TestCase + fixtures :reference_types, :reference_codes + + CLASSES = { + :single => { + :class => ReferenceType, + :primary_keys => :reference_type_id, + :update => { :description => 'RT Desc' }, + }, + :dual => { + :class => ReferenceCode, + :primary_keys => [:reference_type_id, :reference_code], + :update => { :description => 'RT Desc' }, + }, + } + + def setup + self.class.classes = CLASSES + end + + def test_setup + testing_with do + assert_not_nil @klass_info[:update] + end + end + + def test_update_attributes + testing_with do + assert @first.update_attributes(@klass_info[:update]) + assert @first.reload + @klass_info[:update].each_pair do |attr_name, new_value| + assert_equal new_value, @first[attr_name], "Attribute #{attr_name} is incorrect" + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/tmp/test.db b/vendor/gems/composite_primary_keys-1.1.0/tmp/test.db new file mode 100644 index 000000000..923df5f42 Binary files /dev/null and b/vendor/gems/composite_primary_keys-1.1.0/tmp/test.db differ diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/index.html b/vendor/gems/composite_primary_keys-1.1.0/website/index.html new file mode 100644 index 000000000..27baec185 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/index.html @@ -0,0 +1,199 @@ + + + + + + + Composite Primary Keys + + + + + + +
+ +

Composite Primary Keys

+
+ Get Version + 1.1.0 +
+

&#x2192; Ruby on Rails

+

&#x2192; ActiveRecords

+

What

+

Ruby on Rails does not support composite primary keys. This free software is an extension
+to the database layer of Rails – ActiveRecords – to support composite primary keys as transparently as possible.

+

Any Ruby script using ActiveRecords can use Composite Primary Keys with this library.

+

Installing

+

sudo gem install composite_primary_keys

+

Rails: Add the following to the bottom of your environment.rb file

+

require 'composite_primary_keys'

+

Ruby scripts: Add the following to the top of your script

+

require 'rubygems'
+require 'composite_primary_keys'

+

The basics

+

A model with composite primary keys would look like…

+

class Membership < ActiveRecord::Base
+  # set_primary_keys *keys - turns on composite key functionality
+  set_primary_keys :user_id, :group_id
+  belongs_to :user
+  belongs_to :group
+  has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id]
+end

+

A model associated with a composite key model would be defined like…

+

class MembershipStatus < ActiveRecord::Base
+  belongs_to :membership, :foreign_key => [:user_id, :group_id]
+end

+

That is, associations can include composite keys too. Nice.

+

Demonstration of usage

+

Once you’ve created your models to specify composite primary keys (such as the Membership class) and associations (such as MembershipStatus#membership), you can uses them like any normal model with associations.

+

But first, lets check out our primary keys.

+

MembershipStatus.primary_key # => "id"    # normal single key
+Membership.primary_key  # => [:user_id, :group_id] # composite keys
+Membership.primary_key.to_s # => "user_id,group_id"

+

Now we want to be able to find instances using the same syntax we always use for ActiveRecords…

+

MembershipStatus.find(1)    # single id returns single instance
+=> <MembershipStatus:0x392a8c8 @attributes={"id"=>"1", "status"=>"Active"}>
+Membership.find(1,1)  # composite ids returns single instance
+=> <Membership:0x39218b0 @attributes={"user_id"=>"1", "group_id"=>"1"}>

+

Using Ruby on Rails? You’ll want to your url_for helpers
+to convert composite keys into strings and back again…

+

Membership.find(:first).to_param # => "1,1"

+

And then use the string id within your controller to find the object again

+

params[:id] # => '1,1'
+Membership.find(params[:id])
+=> <Membership:0x3904288 @attributes={"user_id"=>"1", "group_id"=>"1"}>

+

That is, an ActiveRecord supporting composite keys behaves transparently
+throughout your application. Just like a normal ActiveRecord.

+

Other tricks

+

Pass a list of composite ids to the #find method

+

Membership.find [1,1], [2,1]
+=> [
+  <Membership:0x394ade8 @attributes={"user_id"=>"1", "group_id"=>"1"}>, 
+  <Membership:0x394ada0 @attributes={"user_id"=>"2", "group_id"=>"1"}>
+]

+

Perform #count operations

+

MembershipStatus.find(:first).memberships.count # => 1

+

Routes with Rails

+

From Pete Sumskas:

+
+

I ran into one problem that I didn’t see mentioned on this list
+ and I didn’t see any information about what I should do to address it in the
+ documentation (might have missed it).

+

The problem was that the urls being generated for a ‘show’ action (for
+ example) had a syntax like:
+
+

/controller/show/123000,Bu70

+

for a two-field composite PK. The default routing would not match that,
+ so after working out how to do the routing I added:
+
+

map.connect ':controller/:action/:id', :id => /\w+(,\w+)*/

+
+ to my route.rb file.

+
+

+

Which databases?

+

A suite of unit tests have been run on the following databases supported by ActiveRecord:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DatabaseTest SuccessUser feedback
mysql YESYES (Yes! or No…)
sqlite3 YESYES (Yes! or No…)
postgresqlYESYES (Yes! or No…)
oracle YESYES (Yes! or No…)
sqlserver ??? (I can help)??? (Yes! or No…)
db2 ??? (I can help)??? (Yes! or No…)
firebird ??? (I can help)??? (Yes! or No…)
sybase ??? (I can help)??? (Yes! or No…)
openbase ??? (I can help)??? (Yes! or No…)
frontbase ??? (I can help)??? (Yes! or No…)
+

Dr Nic’s Blog

+

http://www.drnicwilliams.com – for future announcements and
+other stories and things.

+

Forum

+

http://groups.google.com/group/compositekeys

+

How to submit patches

+

Read the 8 steps for fixing other people’s code and for section 8b: Submit patch to Google Groups, use the Google Group above.

+

The source for this project is available via git. You can browse and/or fork the source, or to clone the project locally:
+
+

git clone git://github.com/drnic/composite_primary_keys.git

+

Licence

+

This code is free to use under the terms of the MIT licence.

+

Contact

+

Comments are welcome. Send an email to Dr Nic Williams.

+

+ Dr Nic, 25th October 2008
+ Theme extended from Paul Battley +

+
+ + + + + + diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/index.txt b/vendor/gems/composite_primary_keys-1.1.0/website/index.txt new file mode 100644 index 000000000..fd66d978e --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/index.txt @@ -0,0 +1,159 @@ +h1. Composite Primary Keys + +h1. → Ruby on Rails + +h1. → ActiveRecords + +h2. What + +Ruby on Rails does not support composite primary keys. This free software is an extension +to the database layer of Rails - "ActiveRecords":http://wiki.rubyonrails.com/rails/pages/ActiveRecord - to support composite primary keys as transparently as possible. + +Any Ruby script using ActiveRecords can use Composite Primary Keys with this library. + +h2. Installing + +
sudo gem install composite_primary_keys
+ +Rails: Add the following to the bottom of your environment.rb file + +
require 'composite_primary_keys'
+ +Ruby scripts: Add the following to the top of your script + +
require 'rubygems'
+require 'composite_primary_keys'
+ +h2. The basics + +A model with composite primary keys would look like... + +
class Membership < ActiveRecord::Base
+  # set_primary_keys *keys - turns on composite key functionality
+  set_primary_keys :user_id, :group_id
+  belongs_to :user
+  belongs_to :group
+  has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id]
+end
+ +A model associated with a composite key model would be defined like... + +
class MembershipStatus < ActiveRecord::Base
+  belongs_to :membership, :foreign_key => [:user_id, :group_id]
+end
+ +That is, associations can include composite keys too. Nice. + +h2. Demonstration of usage + +Once you've created your models to specify composite primary keys (such as the Membership class) and associations (such as MembershipStatus#membership), you can uses them like any normal model with associations. + +But first, lets check out our primary keys. + +
MembershipStatus.primary_key # => "id"    # normal single key
+Membership.primary_key  # => [:user_id, :group_id] # composite keys
+Membership.primary_key.to_s # => "user_id,group_id"
+ +Now we want to be able to find instances using the same syntax we always use for ActiveRecords... + +
MembershipStatus.find(1)    # single id returns single instance
+=> "1", "status"=>"Active"}>
+Membership.find(1,1)  # composite ids returns single instance
+=> "1", "group_id"=>"1"}>
+ +Using "Ruby on Rails":http://www.rubyonrails.org? You'll want to your url_for helpers +to convert composite keys into strings and back again... + +
Membership.find(:first).to_param # => "1,1"
+ +And then use the string id within your controller to find the object again + +
params[:id] # => '1,1'
+Membership.find(params[:id])
+=> "1", "group_id"=>"1"}>
+ +That is, an ActiveRecord supporting composite keys behaves transparently +throughout your application. Just like a normal ActiveRecord. + + +h2. Other tricks + +h3. Pass a list of composite ids to the #find method + +
Membership.find [1,1], [2,1]
+=> [
+  "1", "group_id"=>"1"}>, 
+  "2", "group_id"=>"1"}>
+]
+ +Perform #count operations + +
MembershipStatus.find(:first).memberships.count # => 1
+ +h3. Routes with Rails + +From Pete Sumskas: + +
+ I ran into one problem that I didn't see mentioned on "this list":http://groups.google.com/group/compositekeys - + and I didn't see any information about what I should do to address it in the + documentation (might have missed it). + + The problem was that the urls being generated for a 'show' action (for + example) had a syntax like: + +
/controller/show/123000,Bu70
+ + for a two-field composite PK. The default routing would not match that, + so after working out how to do the routing I added: + +
map.connect ':controller/:action/:id', :id => /\w+(,\w+)*/
+ + to my route.rb file. + +
+ + + +h2. Which databases? + + +A suite of unit tests have been run on the following databases supported by ActiveRecord: + +|_.Database|_.Test Success|_.User feedback| +|mysql |YES|YES ("Yes!":mailto:compositekeys@googlegroups.com?subject=Mysql+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Mysql+is+failing)| +|sqlite3 |YES|YES ("Yes!":mailto:compositekeys@googlegroups.com?subject=Sqlite3+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Sqlite3+is+failing)| +|postgresql|YES|YES ("Yes!":mailto:compositekeys@googlegroups.com?subject=Postgresql+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Postgresql+is+failing)| +|oracle |YES|YES ("Yes!":mailto:compositekeys@googlegroups.com?subject=Oracle+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Oracle+is+failing)| +|sqlserver |??? ("I can help":mailto:compositekeys@googlegroups.com?subject=Help+with+SQLServer)|??? ("Yes!":mailto:compositekeys@googlegroups.com?subject=SQLServer+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=SQLServer+is+failing)| +|db2 |??? ("I can help":mailto:compositekeys@googlegroups.com?subject=Help+with+DB2)|??? ("Yes!":mailto:compositekeys@googlegroups.com?subject=DB2+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=DB2+is+failing)| +|firebird |??? ("I can help":mailto:compositekeys@googlegroups.com?subject=Help+with+Firebird)|??? ("Yes!":mailto:compositekeys@googlegroups.com?subject=Firebird+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Firebird+is+failing)| +|sybase |??? ("I can help":mailto:compositekeys@googlegroups.com?subject=Help+with+Sybase)|??? ("Yes!":mailto:compositekeys@googlegroups.com?subject=Sybase+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Sybase+is+failing)| +|openbase |??? ("I can help":mailto:compositekeys@googlegroups.com?subject=Help+with+Openbase)|??? ("Yes!":mailto:compositekeys@googlegroups.com?subject=Openbase+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Openbase+is+failing)| +|frontbase |??? ("I can help":mailto:compositekeys@googlegroups.com?subject=Help+with+Frontbase)|??? ("Yes!":mailto:compositekeys@googlegroups.com?subject=Frontbase+is+working or "No...":mailto:compositekeys@googlegroups.com?subject=Frontbase+is+failing)| + +h2. Dr Nic's Blog + +"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and +other stories and things. + +h2. Forum + +"http://groups.google.com/group/compositekeys":http://groups.google.com/group/compositekeys + +h2. How to submit patches + +Read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/ and for section "8b: Submit patch to Google Groups":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/#8b-google-groups, use the Google Group above. + + +The source for this project is available via git. You can "browse and/or fork the source":http://github.com/drnic/composite_primary_keys/tree/master, or to clone the project locally: + +
git clone git://github.com/drnic/composite_primary_keys.git
+ +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. diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/javascripts/rounded_corners_lite.inc.js b/vendor/gems/composite_primary_keys-1.1.0/website/javascripts/rounded_corners_lite.inc.js new file mode 100644 index 000000000..afc3ea327 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/javascripts/rounded_corners_lite.inc.js @@ -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) +} diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/stylesheets/screen.css b/vendor/gems/composite_primary_keys-1.1.0/website/stylesheets/screen.css new file mode 100644 index 000000000..3f2d8f951 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/stylesheets/screen.css @@ -0,0 +1,126 @@ +body { + background-color: #2F30EE; + 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: #FFEDFA; +} +h1 { + font-family: sans-serif; + font-weight: normal; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin: 5px; +} +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: 45em; + padding: 0; + margin: 0 auto; +} +.coda { + text-align: right; + color: #77f; + font-size: smaller; +} + +table { + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; + border-style: dashed; +} + +th { + color: #fff; +} + +td { + padding: 2px 10px 2px 10px; +} + +.success { + color: #0CC52B; +} + +.failed { + color: #E90A1B; +} + +.unknown { + color: #995000; +} +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; + margin-top: 15px; + 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; +} + diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/template.js b/vendor/gems/composite_primary_keys-1.1.0/website/template.js new file mode 100644 index 000000000..fbaf5a5e8 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/template.js @@ -0,0 +1,3 @@ +// <%= title %> +var version = <%= version.to_json %>; +<%= body %> diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/template.rhtml b/vendor/gems/composite_primary_keys-1.1.0/website/template.rhtml new file mode 100644 index 000000000..3e2c531c0 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/template.rhtml @@ -0,0 +1,53 @@ + + + + + + + <%= title %> + + + + + + +
+ +

<%= title %>

+
+ Get Version + <%= version %> +
+ <%= body %> +

+ Dr Nic, <%= modified.pretty %>
+ Theme extended from Paul Battley +

+
+ + + + + + diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/version-raw.js b/vendor/gems/composite_primary_keys-1.1.0/website/version-raw.js new file mode 100644 index 000000000..9d2ac788f --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/version-raw.js @@ -0,0 +1,3 @@ +// Announcement JS file +var version = "1.1.0"; +MagicAnnouncement.show('compositekeys', version); diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/version-raw.txt b/vendor/gems/composite_primary_keys-1.1.0/website/version-raw.txt new file mode 100644 index 000000000..74ca3ac67 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/version-raw.txt @@ -0,0 +1,2 @@ +h1. Announcement JS file +MagicAnnouncement.show('compositekeys', version); \ No newline at end of file diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/version.js b/vendor/gems/composite_primary_keys-1.1.0/website/version.js new file mode 100644 index 000000000..56921a962 --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/version.js @@ -0,0 +1,4 @@ +// Version JS file +var version = "1.1.0"; + +document.write(" - " + version); diff --git a/vendor/gems/composite_primary_keys-1.1.0/website/version.txt b/vendor/gems/composite_primary_keys-1.1.0/website/version.txt new file mode 100644 index 000000000..d0ac6a7ac --- /dev/null +++ b/vendor/gems/composite_primary_keys-1.1.0/website/version.txt @@ -0,0 +1,3 @@ +h1. Version JS file + +document.write(" - " + version); \ No newline at end of file diff --git a/vendor/plugins/classic_pagination/lib/pagination.rb b/vendor/plugins/classic_pagination/lib/pagination.rb index b6e9cf4bc..6a3e1a97b 100644 --- a/vendor/plugins/classic_pagination/lib/pagination.rb +++ b/vendor/plugins/classic_pagination/lib/pagination.rb @@ -97,8 +97,8 @@ module ActionController "Unknown options: #{unknown_option_keys.join(', ')}" unless unknown_option_keys.empty? - options[:singular_name] ||= Inflector.singularize(collection_id.to_s) - options[:class_name] ||= Inflector.camelize(options[:singular_name]) + options[:singular_name] ||= ActiveSupport::Inflector.singularize(collection_id.to_s) + options[:class_name] ||= ActiveSupport::Inflector.camelize(options[:singular_name]) end # Returns a paginator and a collection of Active Record model instances diff --git a/vendor/plugins/deadlock_retry/README b/vendor/plugins/deadlock_retry/README new file mode 100644 index 000000000..b5937ce0e --- /dev/null +++ b/vendor/plugins/deadlock_retry/README @@ -0,0 +1,10 @@ +Deadlock Retry +============== + +Deadlock retry allows the database adapter (currently only tested with the +MySQLAdapter) to retry transactions that fall into deadlock. It will retry +such transactions three times before finally failing. + +This capability is automatically added to ActiveRecord. No code changes or otherwise are required. + +Copyright (c) 2005 Jamis Buck, released under the MIT license \ No newline at end of file diff --git a/vendor/plugins/deadlock_retry/Rakefile b/vendor/plugins/deadlock_retry/Rakefile new file mode 100644 index 000000000..8063a6ed4 --- /dev/null +++ b/vendor/plugins/deadlock_retry/Rakefile @@ -0,0 +1,10 @@ +require 'rake' +require 'rake/testtask' + +desc "Default task" +task :default => [ :test ] + +Rake::TestTask.new do |t| + t.test_files = Dir["test/**/*_test.rb"] + t.verbose = true +end diff --git a/vendor/plugins/deadlock_retry/init.rb b/vendor/plugins/deadlock_retry/init.rb new file mode 100644 index 000000000..e090f68af --- /dev/null +++ b/vendor/plugins/deadlock_retry/init.rb @@ -0,0 +1,2 @@ +require 'deadlock_retry' +ActiveRecord::Base.send :include, DeadlockRetry diff --git a/vendor/plugins/deadlock_retry/lib/deadlock_retry.rb b/vendor/plugins/deadlock_retry/lib/deadlock_retry.rb new file mode 100644 index 000000000..413cb823c --- /dev/null +++ b/vendor/plugins/deadlock_retry/lib/deadlock_retry.rb @@ -0,0 +1,58 @@ +# Copyright (c) 2005 Jamis Buck +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +module DeadlockRetry + def self.append_features(base) + super + base.extend(ClassMethods) + base.class_eval do + class < error + if DEADLOCK_ERROR_MESSAGES.any? { |msg| error.message =~ /#{Regexp.escape(msg)}/ } + raise if retry_count >= MAXIMUM_RETRIES_ON_DEADLOCK + retry_count += 1 + logger.info "Deadlock detected on retry #{retry_count}, restarting transaction" + retry + else + raise + end + end + end + end +end diff --git a/vendor/plugins/deadlock_retry/test/deadlock_retry_test.rb b/vendor/plugins/deadlock_retry/test/deadlock_retry_test.rb new file mode 100644 index 000000000..db0f6195d --- /dev/null +++ b/vendor/plugins/deadlock_retry/test/deadlock_retry_test.rb @@ -0,0 +1,65 @@ +begin + require 'active_record' +rescue LoadError + if ENV['ACTIVERECORD_PATH'].nil? + abort < true option. + * added support for file_column enabled unit tests [Manuel Holtgrewe] + * support for custom transformation of images [Frederik Fix] + * allow setting of image attributes (e.g., quality) [Frederik Fix] + * :magick columns can optionally ignore non-images (i.e., do not try to + resize them) + +0.3.1 + * make object with file_columns serializable + * use normal require for RMagick, so that it works with gem + and custom install as well + +0.3 + * fixed bug where empty file uploads were not recognized with some browsers + * fixed bug on windows when "file" utility is not present + * added option to disable automatic file extension correction + * Only allow one attribute per call to file_column, so that options only + apply to one argument + * try to detect when people forget to set the form encoding to + 'multipart/form-data' + * converted to rails plugin + * easy integration with RMagick + +0.2 + * complete rewrite using state pattern + * fixed sanitize filename [Michael Raidel] + * fixed bug when no file was uploaded [Michael Raidel] + * try to fix filename extensions [Michael Raidel] + * Feed absolute paths through File.expand_path to make them as simple as possible + * Make file_column_field helper work with auto-ids (e.g., "event[]") + +0.1.3 + * test cases with more than 1 file_column + * fixed bug when file_column was called with several arguments + * treat empty ("") file_columns as nil + * support for binary files on windows + +0.1.2 + * better rails integration, so that you do not have to include the modules yourself. You + just have to "require 'rails_file_column'" in your "config/environment.rb" + * Rakefile for testing and packaging + +0.1.1 (2005-08-11) + * fixed nasty bug in url_for_file_column that made it unusable on Apache + * prepared for public release + +0.1 (2005-08-10) + * initial release diff --git a/vendor/plugins/file_column/README b/vendor/plugins/file_column/README new file mode 100644 index 000000000..07a6e9661 --- /dev/null +++ b/vendor/plugins/file_column/README @@ -0,0 +1,54 @@ +FEATURES +======== + +Let's assume an model class named Entry, where we want to define the "image" column +as a "file_upload" column. + +class Entry < ActiveRecord::Base + file_column :image +end + +* every entry can have one uploaded file, the filename will be stored in the "image" column + +* files will be stored in "public/entry/image//filename.ext" + +* Newly uploaded files will be stored in "public/entry/tmp//filename.ext" so that + they can be reused in form redisplays (due to validation etc.) + +* in a view, "<%= file_column_field 'entry', 'image' %> will create a file upload field as well + as a hidden field to recover files uploaded before in a case of a form redisplay + +* in a view, "<%= url_for_file_column 'entry', 'image' %> will create an URL to access the + uploaded file. Note that you need an Entry object in the instance variable @entry for this + to work. + +* easy integration with RMagick to resize images and/or create thumb-nails. + +USAGE +===== + +Just drop the whole directory into your application's "vendor/plugins" directory. Starting +with version 1.0rc of rails, it will be automatically picked for you by rails plugin +mechanism. + +DOCUMENTATION +============= + +Please look at the rdoc-generated documentation in the "doc" directory. + +RUNNING UNITTESTS +================= + +There are extensive unittests in the "test" directory. Currently, only MySQL is supported, but +you should be able to easily fix this by looking at "connection.rb". You have to create a +database for the tests and put the connection information into "connection.rb". The schema +for MySQL can be found in "test/fixtures/mysql.sql". + +You can run the tests by starting the "*_test.rb" in the directory "test" + +BUGS & FEEDBACK +=============== + +Bug reports (as well as patches) and feedback are very welcome. Please send it to +sebastian.kanthak@muehlheim.de + diff --git a/vendor/plugins/file_column/Rakefile b/vendor/plugins/file_column/Rakefile new file mode 100644 index 000000000..0a2468248 --- /dev/null +++ b/vendor/plugins/file_column/Rakefile @@ -0,0 +1,36 @@ +task :default => [:test] + +PKG_NAME = "file-column" +PKG_VERSION = "0.3.1" + +PKG_DIR = "release/#{PKG_NAME}-#{PKG_VERSION}" + +task :clean do + rm_rf "release" +end + +task :setup_directories do + mkpath "release" +end + + +task :checkout_release => :setup_directories do + rm_rf PKG_DIR + revision = ENV["REVISION"] || "HEAD" + sh "svn export -r #{revision} . #{PKG_DIR}" +end + +task :release_docs => :checkout_release do + sh "cd #{PKG_DIR}; rdoc lib" +end + +task :package => [:checkout_release, :release_docs] do + sh "cd release; tar czf #{PKG_NAME}-#{PKG_VERSION}.tar.gz #{PKG_NAME}-#{PKG_VERSION}" +end + +task :test do + sh "cd test; ruby file_column_test.rb" + sh "cd test; ruby file_column_helper_test.rb" + sh "cd test; ruby magick_test.rb" + sh "cd test; ruby magick_view_only_test.rb" +end diff --git a/vendor/plugins/file_column/TODO b/vendor/plugins/file_column/TODO new file mode 100644 index 000000000..d46e9fa80 --- /dev/null +++ b/vendor/plugins/file_column/TODO @@ -0,0 +1,6 @@ +* document configuration options better +* support setting of permissions +* validation methods for file format/size +* delete stale files from tmp directories + +* ensure valid URLs are created even when deployed at sub-path (compute_public_url?) diff --git a/vendor/plugins/file_column/init.rb b/vendor/plugins/file_column/init.rb new file mode 100644 index 000000000..d31ef1b9c --- /dev/null +++ b/vendor/plugins/file_column/init.rb @@ -0,0 +1,13 @@ +# plugin init file for rails +# this file will be picked up by rails automatically and +# add the file_column extensions to rails + +require 'file_column' +require 'file_compat' +require 'file_column_helper' +require 'validations' +require 'test_case' + +ActiveRecord::Base.send(:include, FileColumn) +ActionView::Base.send(:include, FileColumnHelper) +ActiveRecord::Base.send(:include, FileColumn::Validations) \ No newline at end of file diff --git a/vendor/plugins/file_column/lib/file_column.rb b/vendor/plugins/file_column/lib/file_column.rb new file mode 100644 index 000000000..791a5be3e --- /dev/null +++ b/vendor/plugins/file_column/lib/file_column.rb @@ -0,0 +1,720 @@ +require 'fileutils' +require 'tempfile' +require 'magick_file_column' + +module FileColumn # :nodoc: + def self.append_features(base) + super + base.extend(ClassMethods) + end + + def self.create_state(instance,attr) + filename = instance[attr] + if filename.nil? or filename.empty? + NoUploadedFile.new(instance,attr) + else + PermanentUploadedFile.new(instance,attr) + end + end + + def self.init_options(defaults, model, attr) + options = defaults.dup + options[:store_dir] ||= File.join(options[:root_path], model, attr) + unless options[:store_dir].is_a?(Symbol) + options[:tmp_base_dir] ||= File.join(options[:store_dir], "tmp") + end + options[:base_url] ||= options[:web_root] + File.join(model, attr) + + [:store_dir, :tmp_base_dir].each do |dir_sym| + if options[dir_sym].is_a?(String) and !File.exists?(options[dir_sym]) + FileUtils.mkpath(options[dir_sym]) + end + end + + options + end + + class BaseUploadedFile # :nodoc: + + def initialize(instance,attr) + @instance, @attr = instance, attr + @options_method = "#{attr}_options".to_sym + end + + + def assign(file) + if file.is_a? File + # this did not come in via a CGI request. However, + # assigning files directly may be useful, so we + # make just this file object similar enough to an uploaded + # file that we can handle it. + file.extend FileColumn::FileCompat + end + + if file.nil? + delete + else + if file.size == 0 + # user did not submit a file, so we + # can simply ignore this + self + else + if file.is_a?(String) + # if file is a non-empty string it is most probably + # the filename and the user forgot to set the encoding + # to multipart/form-data. Since we would raise an exception + # because of the missing "original_filename" method anyways, + # we raise a more meaningful exception rightaway. + raise TypeError.new("Do not know how to handle a string with value '#{file}' that was passed to a file_column. Check if the form's encoding has been set to 'multipart/form-data'.") + end + upload(file) + end + end + end + + def just_uploaded? + @just_uploaded + end + + def on_save(&blk) + @on_save ||= [] + @on_save << Proc.new + end + + # the following methods are overriden by sub-classes if needed + + def temp_path + nil + end + + def absolute_dir + if absolute_path then File.dirname(absolute_path) else nil end + end + + def relative_dir + if relative_path then File.dirname(relative_path) else nil end + end + + def after_save + @on_save.each { |blk| blk.call } if @on_save + self + end + + def after_destroy + end + + def options + @instance.send(@options_method) + end + + private + + def store_dir + if options[:store_dir].is_a? Symbol + raise ArgumentError.new("'#{options[:store_dir]}' is not an instance method of class #{@instance.class.name}") unless @instance.respond_to?(options[:store_dir]) + + dir = File.join(options[:root_path], @instance.send(options[:store_dir])) + FileUtils.mkpath(dir) unless File.exists?(dir) + dir + else + options[:store_dir] + end + end + + def tmp_base_dir + if options[:tmp_base_dir] + options[:tmp_base_dir] + else + dir = File.join(store_dir, "tmp") + FileUtils.mkpath(dir) unless File.exists?(dir) + dir + end + end + + def clone_as(klass) + klass.new(@instance, @attr) + end + + end + + + class NoUploadedFile < BaseUploadedFile # :nodoc: + def delete + # we do not have a file so deleting is easy + self + end + + def upload(file) + # replace ourselves with a TempUploadedFile + temp = clone_as TempUploadedFile + temp.store_upload(file) + temp + end + + def absolute_path(subdir=nil) + nil + end + + + def relative_path(subdir=nil) + nil + end + + def assign_temp(temp_path) + return self if temp_path.nil? or temp_path.empty? + temp = clone_as TempUploadedFile + temp.parse_temp_path temp_path + temp + end + end + + class RealUploadedFile < BaseUploadedFile # :nodoc: + def absolute_path(subdir=nil) + if subdir + File.join(@dir, subdir, @filename) + else + File.join(@dir, @filename) + end + end + + def relative_path(subdir=nil) + if subdir + File.join(relative_path_prefix, subdir, @filename) + else + File.join(relative_path_prefix, @filename) + end + end + + private + + # regular expressions to try for identifying extensions + EXT_REGEXPS = [ + /^(.+)\.([^.]+\.[^.]+)$/, # matches "something.tar.gz" + /^(.+)\.([^.]+)$/ # matches "something.jpg" + ] + + def split_extension(filename,fallback=nil) + EXT_REGEXPS.each do |regexp| + if filename =~ regexp + base,ext = $1, $2 + return [base, ext] if options[:extensions].include?(ext.downcase) + end + end + if fallback and filename =~ EXT_REGEXPS.last + return [$1, $2] + end + [filename, ""] + end + + end + + class TempUploadedFile < RealUploadedFile # :nodoc: + + def store_upload(file) + @tmp_dir = FileColumn.generate_temp_name + @dir = File.join(tmp_base_dir, @tmp_dir) + FileUtils.mkdir(@dir) + + @filename = FileColumn::sanitize_filename(file.original_filename) + local_file_path = File.join(tmp_base_dir,@tmp_dir,@filename) + + # stored uploaded file into local_file_path + # If it was a Tempfile object, the temporary file will be + # cleaned up automatically, so we do not have to care for this + if file.respond_to?(:local_path) and file.local_path and File.exists?(file.local_path) + FileUtils.copy_file(file.local_path, local_file_path) + elsif file.respond_to?(:read) + File.open(local_file_path, "wb") { |f| f.write(file.read) } + else + raise ArgumentError.new("Do not know how to handle #{file.inspect}") + end + File.chmod(options[:permissions], local_file_path) + + if options[:fix_file_extensions] + # try to determine correct file extension and fix + # if necessary + content_type = get_content_type((file.content_type.chomp if file.content_type)) + if content_type and options[:mime_extensions][content_type] + @filename = correct_extension(@filename,options[:mime_extensions][content_type]) + end + + new_local_file_path = File.join(tmp_base_dir,@tmp_dir,@filename) + File.rename(local_file_path, new_local_file_path) unless new_local_file_path == local_file_path + local_file_path = new_local_file_path + end + + @instance[@attr] = @filename + @just_uploaded = true + end + + + # tries to identify and strip the extension of filename + # if an regular expresion from EXT_REGEXPS matches and the + # downcased extension is a known extension (in options[:extensions]) + # we'll strip this extension + def strip_extension(filename) + split_extension(filename).first + end + + def correct_extension(filename, ext) + strip_extension(filename) << ".#{ext}" + end + + def parse_temp_path(temp_path, instance_options=nil) + raise ArgumentError.new("invalid format of '#{temp_path}'") unless temp_path =~ %r{^((\d+\.)+\d+)/([^/].+)$} + @tmp_dir, @filename = $1, FileColumn.sanitize_filename($3) + @dir = File.join(tmp_base_dir, @tmp_dir) + + @instance[@attr] = @filename unless instance_options == :ignore_instance + end + + def upload(file) + # store new file + temp = clone_as TempUploadedFile + temp.store_upload(file) + + # delete old copy + delete_files + + # and return new TempUploadedFile object + temp + end + + def delete + delete_files + @instance[@attr] = "" + clone_as NoUploadedFile + end + + def assign_temp(temp_path) + return self if temp_path.nil? or temp_path.empty? + # we can ignore this since we've already received a newly uploaded file + + # however, we delete the old temporary files + temp = clone_as TempUploadedFile + temp.parse_temp_path(temp_path, :ignore_instance) + temp.delete_files + + self + end + + def temp_path + File.join(@tmp_dir, @filename) + end + + def after_save + super + + # we have a newly uploaded image, move it to the correct location + file = clone_as PermanentUploadedFile + file.move_from(File.join(tmp_base_dir, @tmp_dir), @just_uploaded) + + # delete temporary files + delete_files + + # replace with the new PermanentUploadedFile object + file + end + + def delete_files + FileUtils.rm_rf(File.join(tmp_base_dir, @tmp_dir)) + end + + def get_content_type(fallback=nil) + if options[:file_exec] + begin + content_type = `#{options[:file_exec]} -bi "#{File.join(@dir,@filename)}"`.chomp + content_type = fallback unless $?.success? + content_type.gsub!(/;.+$/,"") if content_type + content_type + rescue + fallback + end + else + fallback + end + end + + private + + def relative_path_prefix + File.join("tmp", @tmp_dir) + end + end + + + class PermanentUploadedFile < RealUploadedFile # :nodoc: + def initialize(*args) + super *args + @dir = File.join(store_dir, relative_path_prefix) + @filename = @instance[@attr] + @filename = nil if @filename.empty? + end + + def move_from(local_dir, just_uploaded) + # remove old permament dir first + # this creates a short moment, where neither the old nor + # the new files exist but we can't do much about this as + # filesystems aren't transactional. + FileUtils.rm_rf @dir + + FileUtils.mv local_dir, @dir + + @just_uploaded = just_uploaded + end + + def upload(file) + temp = clone_as TempUploadedFile + temp.store_upload(file) + temp + end + + def delete + file = clone_as NoUploadedFile + @instance[@attr] = "" + file.on_save { delete_files } + file + end + + def assign_temp(temp_path) + return nil if temp_path.nil? or temp_path.empty? + + temp = clone_as TempUploadedFile + temp.parse_temp_path(temp_path) + temp + end + + def after_destroy + delete_files + end + + def delete_files + FileUtils.rm_rf @dir + end + + private + + def relative_path_prefix + raise RuntimeError.new("Trying to access file_column, but primary key got lost.") if @instance.id.to_s.empty? + @instance.id.to_s + end + end + + # The FileColumn module allows you to easily handle file uploads. You can designate + # one or more columns of your model's table as "file columns" like this: + # + # class Entry < ActiveRecord::Base + # + # file_column :image + # end + # + # Now, by default, an uploaded file "test.png" for an entry object with primary key 42 will + # be stored in in "public/entry/image/42/test.png". The filename "test.png" will be stored + # in the record's "image" column. The "entries" table should have a +VARCHAR+ column + # named "image". + # + # The methods of this module are automatically included into ActiveRecord::Base + # as class methods, so that you can use them in your models. + # + # == Generated Methods + # + # After calling "file_column :image" as in the example above, a number of instance methods + # will automatically be generated, all prefixed by "image": + # + # * Entry#image=(uploaded_file): this will handle a newly uploaded file + # (see below). Note that + # you can simply call your upload field "entry[image]" in your view (or use the + # helper). + # * Entry#image(subdir=nil): This will return an absolute path (as a + # string) to the currently uploaded file + # or nil if no file has been uploaded + # * Entry#image_relative_path(subdir=nil): This will return a path relative to + # this file column's base directory + # as a string or nil if no file has been uploaded. This would be "42/test.png" in the example. + # * Entry#image_just_uploaded?: Returns true if a new file has been uploaded to this instance. + # You can use this in your code to perform certain actions (e. g., validation, + # custom post-processing) only on newly uploaded files. + # + # You can access the raw value of the "image" column (which will contain the filename) via the + # ActiveRecord::Base#attributes or ActiveRecord::Base#[] methods like this: + # + # entry['image'] # e.g."test.png" + # + # == Storage of uploaded files + # + # For a model class +Entry+ and a column +image+, all files will be stored under + # "public/entry/image". A sub-directory named after the primary key of the object will + # be created, so that files can be stored using their real filename. For example, a file + # "test.png" stored in an Entry object with id 42 will be stored in + # + # public/entry/image/42/test.png + # + # Files will be moved to this location in an +after_save+ callback. They will be stored in + # a temporary location previously as explained in the next section. + # + # By default, files will be created with unix permissions of 0644 (i. e., owner has + # read/write access, group and others only have read access). You can customize + # this by passing the desired mode as a :permissions options. The value + # you give here is passed directly to File::chmod, so on Unix you should + # give some octal value like 0644, for example. + # + # == Handling of form redisplay + # + # Suppose you have a form for creating a new object where the user can upload an image. The form may + # have to be re-displayed because of validation errors. The uploaded file has to be stored somewhere so + # that the user does not have to upload it again. FileColumn will store these in a temporary directory + # (called "tmp" and located under the column's base directory by default) so that it can be moved to + # the final location if the object is successfully created. If the form is never completed, though, you + # can easily remove all the images in this "tmp" directory once per day or so. + # + # So in the example above, the image "test.png" would first be stored in + # "public/entry/image/tmp//test.png" and be moved to + # "public/entry/image//test.png". + # + # This temporary location of newly uploaded files has another advantage when updating objects. If the + # update fails for some reasons (e.g. due to validations), the existing image will not be overwritten, so + # it has a kind of "transactional behaviour". + # + # == Additional Files and Directories + # + # FileColumn allows you to keep more than one file in a directory and will move/delete + # all the files and directories it finds in a model object's directory when necessary. + # + # As a convenience you can access files stored in sub-directories via the +subdir+ + # parameter if they have the same filename. + # + # Suppose your uploaded file is named "vancouver.jpg" and you want to create a + # thumb-nail and store it in the "thumb" directory. If you call + # image("thumb"), you + # will receive an absolute path for the file "thumb/vancouver.jpg" in the same + # directory "vancouver.jpg" is stored. Look at the documentation of FileColumn::Magick + # for more examples and how to create these thumb-nails automatically. + # + # == File Extensions + # + # FileColumn will try to fix the file extension of uploaded files, so that + # the files are served with the correct mime-type by your web-server. Most + # web-servers are setting the mime-type based on the file's extension. You + # can disable this behaviour by passing the :fix_file_extensions option + # with a value of +nil+ to +file_column+. + # + # In order to set the correct extension, FileColumn tries to determine + # the files mime-type first. It then uses the +MIME_EXTENSIONS+ hash to + # choose the corresponding file extension. You can override this hash + # by passing in a :mime_extensions option to +file_column+. + # + # The mime-type of the uploaded file is determined with the following steps: + # + # 1. Run the external "file" utility. You can specify the full path to + # the executable in the :file_exec option or set this option + # to +nil+ to disable this step + # + # 2. If the file utility couldn't determine the mime-type or the utility was not + # present, the content-type provided by the user's browser is used + # as a fallback. + # + # == Custom Storage Directories + # + # FileColumn's storage location is determined in the following way. All + # files are saved below the so-called "root_path" directory, which defaults to + # "RAILS_ROOT/public". For every file_column, you can set a separte "store_dir" + # option. It defaults to "model_name/attribute_name". + # + # Files will always be stored in sub-directories of the store_dir path. The + # subdirectory is named after the instance's +id+ attribute for a saved model, + # or "tmp/" for unsaved models. + # + # You can specify a custom root_path by setting the :root_path option. + # + # You can specify a custom storage_dir by setting the :storage_dir option. + # + # For setting a static storage_dir that doesn't change with respect to a particular + # instance, you assign :storage_dir a String representing a directory + # as an absolute path. + # + # If you need more fine-grained control over the storage directory, you + # can use the name of a callback-method as a symbol for the + # :store_dir option. This method has to be defined as an + # instance method in your model. It will be called without any arguments + # whenever the storage directory for an uploaded file is needed. It should return + # a String representing a directory relativeo to root_path. + # + # Uploaded files for unsaved models objects will be stored in a temporary + # directory. By default this directory will be a "tmp" directory in + # your :store_dir. You can override this via the + # :tmp_base_dir option. + module ClassMethods + + # default mapping of mime-types to file extensions. FileColumn will try to + # rename a file to the correct extension if it detects a known mime-type + MIME_EXTENSIONS = { + "image/gif" => "gif", + "image/jpeg" => "jpg", + "image/pjpeg" => "jpg", + "image/x-png" => "png", + "image/jpg" => "jpg", + "image/png" => "png", + "application/x-shockwave-flash" => "swf", + "application/pdf" => "pdf", + "application/pgp-signature" => "sig", + "application/futuresplash" => "spl", + "application/msword" => "doc", + "application/postscript" => "ps", + "application/x-bittorrent" => "torrent", + "application/x-dvi" => "dvi", + "application/x-gzip" => "gz", + "application/x-ns-proxy-autoconfig" => "pac", + "application/x-shockwave-flash" => "swf", + "application/x-tgz" => "tar.gz", + "application/x-tar" => "tar", + "application/zip" => "zip", + "audio/mpeg" => "mp3", + "audio/x-mpegurl" => "m3u", + "audio/x-ms-wma" => "wma", + "audio/x-ms-wax" => "wax", + "audio/x-wav" => "wav", + "image/x-xbitmap" => "xbm", + "image/x-xpixmap" => "xpm", + "image/x-xwindowdump" => "xwd", + "text/css" => "css", + "text/html" => "html", + "text/javascript" => "js", + "text/plain" => "txt", + "text/xml" => "xml", + "video/mpeg" => "mpeg", + "video/quicktime" => "mov", + "video/x-msvideo" => "avi", + "video/x-ms-asf" => "asf", + "video/x-ms-wmv" => "wmv" + } + + EXTENSIONS = Set.new MIME_EXTENSIONS.values + EXTENSIONS.merge %w(jpeg) + + # default options. You can override these with +file_column+'s +options+ parameter + DEFAULT_OPTIONS = { + :root_path => File.join(RAILS_ROOT, "public"), + :web_root => "", + :mime_extensions => MIME_EXTENSIONS, + :extensions => EXTENSIONS, + :fix_file_extensions => true, + :permissions => 0644, + + # path to the unix "file" executbale for + # guessing the content-type of files + :file_exec => "file" + } + + # handle the +attr+ attribute as a "file-upload" column, generating additional methods as explained + # above. You should pass the attribute's name as a symbol, like this: + # + # file_column :image + # + # You can pass in an options hash that overrides the options + # in +DEFAULT_OPTIONS+. + def file_column(attr, options={}) + options = DEFAULT_OPTIONS.merge(options) if options + + my_options = FileColumn::init_options(options, + ActiveSupport::Inflector.underscore(self.name).to_s, + attr.to_s) + + state_attr = "@#{attr}_state".to_sym + state_method = "#{attr}_state".to_sym + + define_method state_method do + result = instance_variable_get state_attr + if result.nil? + result = FileColumn::create_state(self, attr.to_s) + instance_variable_set state_attr, result + end + result + end + + private state_method + + define_method attr do |*args| + send(state_method).absolute_path *args + end + + define_method "#{attr}_relative_path" do |*args| + send(state_method).relative_path *args + end + + define_method "#{attr}_dir" do + send(state_method).absolute_dir + end + + define_method "#{attr}_relative_dir" do + send(state_method).relative_dir + end + + define_method "#{attr}=" do |file| + state = send(state_method).assign(file) + instance_variable_set state_attr, state + if state.options[:after_upload] and state.just_uploaded? + state.options[:after_upload].each do |sym| + self.send sym + end + end + end + + define_method "#{attr}_temp" do + send(state_method).temp_path + end + + define_method "#{attr}_temp=" do |temp_path| + instance_variable_set state_attr, send(state_method).assign_temp(temp_path) + end + + after_save_method = "#{attr}_after_save".to_sym + + define_method after_save_method do + instance_variable_set state_attr, send(state_method).after_save + end + + after_save after_save_method + + after_destroy_method = "#{attr}_after_destroy".to_sym + + define_method after_destroy_method do + send(state_method).after_destroy + end + after_destroy after_destroy_method + + define_method "#{attr}_just_uploaded?" do + send(state_method).just_uploaded? + end + + # this creates a closure keeping a reference to my_options + # right now that's the only way we store the options. We + # might use a class attribute as well + define_method "#{attr}_options" do + my_options + end + + private after_save_method, after_destroy_method + + FileColumn::MagickExtension::file_column(self, attr, my_options) if options[:magick] + end + + end + + private + + def self.generate_temp_name + now = Time.now + "#{now.to_i}.#{now.usec}.#{Process.pid}" + end + + def self.sanitize_filename(filename) + filename = File.basename(filename.gsub("\\", "/")) # work-around for IE + filename.gsub!(/[^a-zA-Z0-9\.\-\+_]/,"_") + filename = "_#{filename}" if filename =~ /^\.+$/ + filename = "unnamed" if filename.size == 0 + filename + end + +end + + diff --git a/vendor/plugins/file_column/lib/file_column_helper.rb b/vendor/plugins/file_column/lib/file_column_helper.rb new file mode 100644 index 000000000..f4ebe38e7 --- /dev/null +++ b/vendor/plugins/file_column/lib/file_column_helper.rb @@ -0,0 +1,150 @@ +# This module contains helper methods for displaying and uploading files +# for attributes created by +FileColumn+'s +file_column+ method. It will be +# automatically included into ActionView::Base, thereby making this module's +# methods available in all your views. +module FileColumnHelper + + # Use this helper to create an upload field for a file_column attribute. This will generate + # an additional hidden field to keep uploaded files during form-redisplays. For example, + # when called with + # + # <%= file_column_field("entry", "image") %> + # + # the following HTML will be generated (assuming the form is redisplayed and something has + # already been uploaded): + # + # + # + # + # You can use the +option+ argument to pass additional options to the file-field tag. + # + # Be sure to set the enclosing form's encoding to 'multipart/form-data', by + # using something like this: + # + # <%= form_tag {:action => "create", ...}, :multipart => true %> + def file_column_field(object, method, options={}) + result = ActionView::Helpers::InstanceTag.new(object.dup, method.to_s+"_temp", self).to_input_field_tag("hidden", {}) + result << ActionView::Helpers::InstanceTag.new(object.dup, method, self).to_input_field_tag("file", options) + end + + # Creates an URL where an uploaded file can be accessed. When called for an Entry object with + # id 42 (stored in @entry) like this + # + # <%= url_for_file_column(@entry, "image") + # + # the following URL will be produced, assuming the file "test.png" has been stored in + # the "image"-column of an Entry object stored in @entry: + # + # /entry/image/42/test.png + # + # This will produce a valid URL even for temporary uploaded files, e.g. files where the object + # they are belonging to has not been saved in the database yet. + # + # The URL produces, although starting with a slash, will be relative + # to your app's root. If you pass it to one rails' +image_tag+ + # helper, rails will properly convert it to an absolute + # URL. However, this will not be the case, if you create a link with + # the +link_to+ helper. In this case, you can pass :absolute => + # true to +options+, which will make sure, the generated URL is + # absolute on your server. Examples: + # + # <%= image_tag url_for_file_column(@entry, "image") %> + # <%= link_to "Download", url_for_file_column(@entry, "image", :absolute => true) %> + # + # If there is currently no uploaded file stored in the object's column this method will + # return +nil+. + def url_for_file_column(object, method, options=nil) + case object + when String, Symbol + object = instance_variable_get("@#{object.to_s}") + end + + # parse options + subdir = nil + absolute = false + if options + case options + when Hash + subdir = options[:subdir] + absolute = options[:absolute] + when String, Symbol + subdir = options + end + end + + relative_path = object.send("#{method}_relative_path", subdir) + return nil unless relative_path + + url = "" + url << request.relative_url_root.to_s if absolute + url << "/" + url << object.send("#{method}_options")[:base_url] << "/" + url << relative_path + end + + # Same as +url_for_file_colum+ but allows you to access different versions + # of the image that have been processed by RMagick. + # + # If your +options+ parameter is non-nil this will + # access a different version of an image that will be produced by + # RMagick. You can use the following types for +options+: + # + # * a :symbol will select a version defined in the model + # via FileColumn::Magick's :versions feature. + # * a geometry_string will dynamically create an + # image resized as specified by geometry_string. The image will + # be stored so that it does not have to be recomputed the next time the + # same version string is used. + # * some_hash will dynamically create an image + # that is created according to the options in some_hash. This + # accepts exactly the same options as Magick's version feature. + # + # The version produced by RMagick will be stored in a special sub-directory. + # The directory's name will be derived from the options you specified + # (via a hash function) but if you want + # to set it yourself, you can use the :name => name option. + # + # Examples: + # + # <%= url_for_image_column @entry, "image", "640x480" %> + # + # will produce an URL like this + # + # /entry/image/42/bdn19n/filename.jpg + # # "640x480".hash.abs.to_s(36) == "bdn19n" + # + # and + # + # <%= url_for_image_column @entry, "image", + # :size => "50x50", :crop => "1:1", :name => "thumb" %> + # + # will produce something like this: + # + # /entry/image/42/thumb/filename.jpg + # + # Hint: If you are using the same geometry string / options hash multiple times, you should + # define it in a helper to stay with DRY. Another option is to define it in the model via + # FileColumn::Magick's :versions feature and then refer to it via a symbol. + # + # The URL produced by this method is relative to your application's root URL, + # although it will start with a slash. + # If you pass this URL to rails' +image_tag+ helper, it will be converted to an + # absolute URL automatically. + # If there is currently no image uploaded, or there is a problem while loading + # the image this method will return +nil+. + def url_for_image_column(object, method, options=nil) + case object + when String, Symbol + object = instance_variable_get("@#{object.to_s}") + end + subdir = nil + if options + subdir = object.send("#{method}_state").create_magick_version_if_needed(options) + end + if subdir.nil? + nil + else + url_for_file_column(object, method, subdir) + end + end +end diff --git a/vendor/plugins/file_column/lib/file_compat.rb b/vendor/plugins/file_column/lib/file_compat.rb new file mode 100644 index 000000000..f284410a3 --- /dev/null +++ b/vendor/plugins/file_column/lib/file_compat.rb @@ -0,0 +1,28 @@ +module FileColumn + + # This bit of code allows you to pass regular old files to + # file_column. file_column depends on a few extra methods that the + # CGI uploaded file class adds. We will add the equivalent methods + # to file objects if necessary by extending them with this module. This + # avoids opening up the standard File class which might result in + # naming conflicts. + + module FileCompat # :nodoc: + def original_filename + File.basename(path) + end + + def size + File.size(path) + end + + def local_path + path + end + + def content_type + nil + end + end +end + diff --git a/vendor/plugins/file_column/lib/magick_file_column.rb b/vendor/plugins/file_column/lib/magick_file_column.rb new file mode 100644 index 000000000..c4dc06fc3 --- /dev/null +++ b/vendor/plugins/file_column/lib/magick_file_column.rb @@ -0,0 +1,260 @@ +module FileColumn # :nodoc: + + class BaseUploadedFile # :nodoc: + def transform_with_magick + if needs_transform? + begin + img = ::Magick::Image::read(absolute_path).first + rescue ::Magick::ImageMagickError + if options[:magick][:image_required] + @magick_errors ||= [] + @magick_errors << "invalid image" + end + return + end + + if options[:magick][:versions] + options[:magick][:versions].each_pair do |version, version_options| + next if version_options[:lazy] + dirname = version_options[:name] + FileUtils.mkdir File.join(@dir, dirname) + transform_image(img, version_options, absolute_path(dirname)) + end + end + if options[:magick][:size] or options[:magick][:crop] or options[:magick][:transformation] or options[:magick][:attributes] + transform_image(img, options[:magick], absolute_path) + end + + GC.start + end + end + + def create_magick_version_if_needed(version) + # RMagick might not have been loaded so far. + # We do not want to require it on every call of this method + # as this might be fairly expensive, so we just try if ::Magick + # exists and require it if not. + begin + ::Magick + rescue NameError + require 'RMagick' + end + + if version.is_a?(Symbol) + version_options = options[:magick][:versions][version] + else + version_options = MagickExtension::process_options(version) + end + + unless File.exists?(absolute_path(version_options[:name])) + begin + img = ::Magick::Image::read(absolute_path).first + rescue ::Magick::ImageMagickError + # we might be called directly from the view here + # so we just return nil if we cannot load the image + return nil + end + dirname = version_options[:name] + FileUtils.mkdir File.join(@dir, dirname) + transform_image(img, version_options, absolute_path(dirname)) + end + + version_options[:name] + end + + attr_reader :magick_errors + + def has_magick_errors? + @magick_errors and !@magick_errors.empty? + end + + private + + def needs_transform? + options[:magick] and just_uploaded? and + (options[:magick][:size] or options[:magick][:versions] or options[:magick][:transformation] or options[:magick][:attributes]) + end + + def transform_image(img, img_options, dest_path) + begin + if img_options[:transformation] + if img_options[:transformation].is_a?(Symbol) + img = @instance.send(img_options[:transformation], img) + else + img = img_options[:transformation].call(img) + end + end + if img_options[:crop] + dx, dy = img_options[:crop].split(':').map { |x| x.to_f } + w, h = (img.rows * dx / dy), (img.columns * dy / dx) + img = img.crop(::Magick::CenterGravity, [img.columns, w].min, + [img.rows, h].min, true) + end + + if img_options[:size] + img = img.change_geometry(img_options[:size]) do |c, r, i| + i.resize(c, r) + end + end + ensure + img.write(dest_path) do + if img_options[:attributes] + img_options[:attributes].each_pair do |property, value| + self.send "#{property}=", value + end + end + end + File.chmod options[:permissions], dest_path + end + end + end + + # If you are using file_column to upload images, you can + # directly process the images with RMagick, + # a ruby extension + # for accessing the popular imagemagick libraries. You can find + # more information about RMagick at http://rmagick.rubyforge.org. + # + # You can control what to do by adding a :magick option + # to your options hash. All operations are performed immediately + # after a new file is assigned to the file_column attribute (i.e., + # when a new file has been uploaded). + # + # == Resizing images + # + # To resize the uploaded image according to an imagemagick geometry + # string, just use the :size option: + # + # file_column :image, :magick => {:size => "800x600>"} + # + # If the uploaded file cannot be loaded by RMagick, file_column will + # signal a validation error for the corresponding attribute. If you + # want to allow non-image files to be uploaded in a column that uses + # the :magick option, you can set the :image_required + # attribute to +false+: + # + # file_column :image, :magick => {:size => "800x600>", + # :image_required => false } + # + # == Multiple versions + # + # You can also create additional versions of your image, for example + # thumb-nails, like this: + # file_column :image, :magick => {:versions => { + # :thumb => {:size => "50x50"}, + # :medium => {:size => "640x480>"} + # } + # + # These versions will be stored in separate sub-directories, named like the + # symbol you used to identify the version. So in the previous example, the + # image versions will be stored in "thumb", "screen" and "widescreen" + # directories, resp. + # A name different from the symbol can be set via the :name option. + # + # These versions can be accessed via FileColumnHelper's +url_for_image_column+ + # method like this: + # + # <%= url_for_image_column "entry", "image", :thumb %> + # + # == Cropping images + # + # If you wish to crop your images with a size ratio before scaling + # them according to your version geometry, you can use the :crop directive. + # file_column :image, :magick => {:versions => { + # :square => {:crop => "1:1", :size => "50x50", :name => "thumb"}, + # :screen => {:crop => "4:3", :size => "640x480>"}, + # :widescreen => {:crop => "16:9", :size => "640x360!"}, + # } + # } + # + # == Custom attributes + # + # To change some of the image properties like compression level before they + # are saved you can set the :attributes option. + # For a list of available attributes go to http://www.simplesystems.org/RMagick/doc/info.html + # + # file_column :image, :magick => { :attributes => { :quality => 30 } } + # + # == Custom transformations + # + # To perform custom transformations on uploaded images, you can pass a + # callback to file_column: + # file_column :image, :magick => + # Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) } + # + # The callback you give, receives one argument, which is an instance + # of Magick::Image, the RMagick image class. It should return a transformed + # image. Instead of passing a Proc object, you can also give a + # Symbol, the name of an instance method of your model. + # + # Custom transformations can be combined via the standard :size and :crop + # features, by using the :transformation option: + # file_column :image, :magick => { + # :transformation => Proc.new { |image| ... }, + # :size => "640x480" + # } + # + # In this case, the standard resizing operations will be performed after the + # custom transformation. + # + # Of course, custom transformations can be used in versions, as well. + # + # Note: You'll need the + # RMagick extension being installed in order to use file_column's + # imagemagick integration. + module MagickExtension + + def self.file_column(klass, attr, options) # :nodoc: + require 'RMagick' + options[:magick] = process_options(options[:magick],false) if options[:magick] + if options[:magick][:versions] + options[:magick][:versions].each_pair do |name, value| + options[:magick][:versions][name] = process_options(value, name.to_s) + end + end + state_method = "#{attr}_state".to_sym + after_assign_method = "#{attr}_magick_after_assign".to_sym + + klass.send(:define_method, after_assign_method) do + self.send(state_method).transform_with_magick + end + + options[:after_upload] ||= [] + options[:after_upload] << after_assign_method + + klass.validate do |record| + state = record.send(state_method) + if state.has_magick_errors? + state.magick_errors.each do |error| + record.errors.add attr, error + end + end + end + end + + + def self.process_options(options,create_name=true) + case options + when String then options = {:size => options} + when Proc, Symbol then options = {:transformation => options } + end + if options[:geometry] + options[:size] = options.delete(:geometry) + end + options[:image_required] = true unless options.key?(:image_required) + if options[:name].nil? and create_name + if create_name == true + hash = 0 + for key in [:size, :crop] + hash = hash ^ options[key].hash if options[key] + end + options[:name] = hash.abs.to_s(36) + else + options[:name] = create_name + end + end + options + end + + end +end diff --git a/vendor/plugins/file_column/lib/rails_file_column.rb b/vendor/plugins/file_column/lib/rails_file_column.rb new file mode 100644 index 000000000..af8c95a84 --- /dev/null +++ b/vendor/plugins/file_column/lib/rails_file_column.rb @@ -0,0 +1,19 @@ +# require this file from your "config/environment.rb" (after rails has been loaded) +# to integrate the file_column extension into rails. + +require 'file_column' +require 'file_column_helper' + + +module ActiveRecord # :nodoc: + class Base # :nodoc: + # make file_column method available in all active record decendants + include FileColumn + end +end + +module ActionView # :nodoc: + class Base # :nodoc: + include FileColumnHelper + end +end diff --git a/vendor/plugins/file_column/lib/test_case.rb b/vendor/plugins/file_column/lib/test_case.rb new file mode 100644 index 000000000..1416a1e7f --- /dev/null +++ b/vendor/plugins/file_column/lib/test_case.rb @@ -0,0 +1,124 @@ +require 'test/unit' + +# Add the methods +upload+, the setup_file_fixtures and +# teardown_file_fixtures to the class Test::Unit::TestCase. +class Test::Unit::TestCase + # Returns a +Tempfile+ object as it would have been generated on file upload. + # Use this method to create the parameters when emulating form posts with + # file fields. + # + # === Example: + # + # def test_file_column_post + # entry = { :title => 'foo', :file => upload('/tmp/foo.txt')} + # post :upload, :entry => entry + # + # # ... + # end + # + # === Parameters + # + # * path The path to the file to upload. + # * content_type The MIME type of the file. If it is :guess, + # the method will try to guess it. + def upload(path, content_type=:guess, type=:tempfile) + if content_type == :guess + case path + when /\.jpg$/ then content_type = "image/jpeg" + when /\.png$/ then content_type = "image/png" + else content_type = nil + end + end + uploaded_file(path, content_type, File.basename(path), type) + end + + # Copies the fixture files from "RAILS_ROOT/test/fixtures/file_column" into + # the temporary storage directory used for testing + # ("RAILS_ROOT/test/tmp/file_column"). Call this method in your + # setup methods to get the file fixtures (images, for example) into + # the directory used by file_column in testing. + # + # Note that the files and directories in the "fixtures/file_column" directory + # must have the same structure as you would expect in your "/public" directory + # after uploading with FileColumn. + # + # For example, the directory structure could look like this: + # + # test/fixtures/file_column/ + # `-- container + # |-- first_image + # | |-- 1 + # | | `-- image1.jpg + # | `-- tmp + # `-- second_image + # |-- 1 + # | `-- image2.jpg + # `-- tmp + # + # Your fixture file for this one "container" class fixture could look like this: + # + # first: + # id: 1 + # first_image: image1.jpg + # second_image: image1.jpg + # + # A usage example: + # + # def setup + # setup_fixture_files + # + # # ... + # end + def setup_fixture_files + tmp_path = File.join(RAILS_ROOT, "test", "tmp", "file_column") + file_fixtures = Dir.glob File.join(RAILS_ROOT, "test", "fixtures", "file_column", "*") + + FileUtils.mkdir_p tmp_path unless File.exists?(tmp_path) + FileUtils.cp_r file_fixtures, tmp_path + end + + # Removes the directory "RAILS_ROOT/test/tmp/file_column/" so the files + # copied on test startup are removed. Call this in your unit test's +teardown+ + # method. + # + # A usage example: + # + # def teardown + # teardown_fixture_files + # + # # ... + # end + def teardown_fixture_files + FileUtils.rm_rf File.join(RAILS_ROOT, "test", "tmp", "file_column") + end + + private + + def uploaded_file(path, content_type, filename, type=:tempfile) # :nodoc: + if type == :tempfile + t = Tempfile.new(File.basename(filename)) + FileUtils.copy_file(path, t.path) + else + if path + t = StringIO.new(IO.read(path)) + else + t = StringIO.new + end + end + (class << t; self; end).class_eval do + alias local_path path if type == :tempfile + define_method(:local_path) { "" } if type == :stringio + define_method(:original_filename) {filename} + define_method(:content_type) {content_type} + end + return t + end +end + +# If we are running in the "test" environment, we overwrite the default +# settings for FileColumn so that files are not uploaded into "/public/" +# in tests but rather into the directory "/test/tmp/file_column". +if RAILS_ENV == "test" + FileColumn::ClassMethods::DEFAULT_OPTIONS[:root_path] = + File.join(RAILS_ROOT, "test", "tmp", "file_column") +end diff --git a/vendor/plugins/file_column/lib/validations.rb b/vendor/plugins/file_column/lib/validations.rb new file mode 100644 index 000000000..5b961eb9c --- /dev/null +++ b/vendor/plugins/file_column/lib/validations.rb @@ -0,0 +1,112 @@ +module FileColumn + module Validations #:nodoc: + + def self.append_features(base) + super + base.extend(ClassMethods) + end + + # This module contains methods to create validations of uploaded files. All methods + # in this module will be included as class methods into ActiveRecord::Base + # so that you can use them in your models like this: + # + # class Entry < ActiveRecord::Base + # file_column :image + # validates_filesize_of :image, :in => 0..1.megabyte + # end + module ClassMethods + EXT_REGEXP = /\.([A-z0-9]+)$/ + + # This validates the file type of one or more file_columns. A list of file columns + # should be given followed by an options hash. + # + # Required options: + # * :in => list of extensions or mime types. If mime types are used they + # will be mapped into an extension via FileColumn::ClassMethods::MIME_EXTENSIONS. + # + # Examples: + # validates_file_format_of :field, :in => ["gif", "png", "jpg"] + # validates_file_format_of :field, :in => ["image/jpeg"] + def validates_file_format_of(*attrs) + + options = attrs.pop if attrs.last.is_a?Hash + raise ArgumentError, "Please include the :in option." if !options || !options[:in] + options[:in] = [options[:in]] if options[:in].is_a?String + raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Array + + validates_each(attrs, options) do |record, attr, value| + unless value.blank? + mime_extensions = record.send("#{attr}_options")[:mime_extensions] + extensions = options[:in].map{|o| mime_extensions[o] || o } + record.errors.add attr, "is not a valid format." unless extensions.include?(value.scan(EXT_REGEXP).flatten.first) + end + end + + end + + # This validates the file size of one or more file_columns. A list of file columns + # should be given followed by an options hash. + # + # Required options: + # * :in => A size range. Note that you can use ActiveSupport's + # numeric extensions for kilobytes, etc. + # + # Examples: + # validates_filesize_of :field, :in => 0..100.megabytes + # validates_filesize_of :field, :in => 15.kilobytes..1.megabyte + def validates_filesize_of(*attrs) + + options = attrs.pop if attrs.last.is_a?Hash + raise ArgumentError, "Please include the :in option." if !options || !options[:in] + raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Range + + validates_each(attrs, options) do |record, attr, value| + unless value.blank? + size = File.size(value) + record.errors.add attr, "is smaller than the allowed size range." if size < options[:in].first + record.errors.add attr, "is larger than the allowed size range." if size > options[:in].last + end + end + + end + + IMAGE_SIZE_REGEXP = /^(\d+)x(\d+)$/ + + # Validates the image size of one or more file_columns. A list of file columns + # should be given followed by an options hash. The validation will pass + # if both image dimensions (rows and columns) are at least as big as + # given in the :min option. + # + # Required options: + # * :min => minimum image dimension string, in the format NNxNN + # (columns x rows). + # + # Example: + # validates_image_size :field, :min => "1200x1800" + # + # This validation requires RMagick to be installed on your system + # to check the image's size. + def validates_image_size(*attrs) + options = attrs.pop if attrs.last.is_a?Hash + raise ArgumentError, "Please include a :min option." if !options || !options[:min] + minimums = options[:min].scan(IMAGE_SIZE_REGEXP).first.collect{|n| n.to_i} rescue [] + raise ArgumentError, "Invalid value for option :min (should be 'XXxYY')" unless minimums.size == 2 + + require 'RMagick' + + validates_each(attrs, options) do |record, attr, value| + unless value.blank? + begin + img = ::Magick::Image::read(value).first + record.errors.add('image', "is too small, must be at least #{minimums[0]}x#{minimums[1]}") if ( img.rows < minimums[1] || img.columns < minimums[0] ) + rescue ::Magick::ImageMagickError + record.errors.add('image', "invalid image") + end + img = nil + GC.start + end + end + end + end + end +end diff --git a/vendor/plugins/file_column/test/abstract_unit.rb b/vendor/plugins/file_column/test/abstract_unit.rb new file mode 100644 index 000000000..22bc53b70 --- /dev/null +++ b/vendor/plugins/file_column/test/abstract_unit.rb @@ -0,0 +1,63 @@ +require 'test/unit' +require 'rubygems' +require 'active_support' +require 'active_record' +require 'action_view' +require File.dirname(__FILE__) + '/connection' +require 'stringio' + +RAILS_ROOT = File.dirname(__FILE__) +RAILS_ENV = "" + +$: << "../lib" + +require 'file_column' +require 'file_compat' +require 'validations' +require 'test_case' + +# do not use the file executable normally in our tests as +# it may not be present on the machine we are running on +FileColumn::ClassMethods::DEFAULT_OPTIONS = + FileColumn::ClassMethods::DEFAULT_OPTIONS.merge({:file_exec => nil}) + +class ActiveRecord::Base + include FileColumn + include FileColumn::Validations +end + + +class RequestMock + attr_accessor :relative_url_root + + def initialize + @relative_url_root = "" + end +end + +class Test::Unit::TestCase + + def assert_equal_paths(expected_path, path) + assert_equal normalize_path(expected_path), normalize_path(path) + end + + + private + + def normalize_path(path) + Pathname.new(path).realpath + end + + def clear_validations + [:validate, :validate_on_create, :validate_on_update].each do |attr| + Entry.write_inheritable_attribute attr, [] + Movie.write_inheritable_attribute attr, [] + end + end + + def file_path(filename) + File.expand_path("#{File.dirname(__FILE__)}/fixtures/#{filename}") + end + + alias_method :f, :file_path +end diff --git a/vendor/plugins/file_column/test/connection.rb b/vendor/plugins/file_column/test/connection.rb new file mode 100644 index 000000000..a2f28baca --- /dev/null +++ b/vendor/plugins/file_column/test/connection.rb @@ -0,0 +1,17 @@ +print "Using native MySQL\n" +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +db = 'file_column_test' + +ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "rails", + :password => "", + :database => db, + :socket => "/var/run/mysqld/mysqld.sock" +) + +load File.dirname(__FILE__) + "/fixtures/schema.rb" diff --git a/vendor/plugins/file_column/test/file_column_helper_test.rb b/vendor/plugins/file_column/test/file_column_helper_test.rb new file mode 100644 index 000000000..ffb2c43b8 --- /dev/null +++ b/vendor/plugins/file_column/test/file_column_helper_test.rb @@ -0,0 +1,97 @@ +require File.dirname(__FILE__) + '/abstract_unit' +require File.dirname(__FILE__) + '/fixtures/entry' + +class UrlForFileColumnTest < Test::Unit::TestCase + include FileColumnHelper + + def setup + Entry.file_column :image + @request = RequestMock.new + end + + def test_url_for_file_column_with_temp_entry + @e = Entry.new(:image => upload(f("skanthak.png"))) + url = url_for_file_column("e", "image") + assert_match %r{^/entry/image/tmp/\d+(\.\d+)+/skanthak.png$}, url + end + + def test_url_for_file_column_with_saved_entry + @e = Entry.new(:image => upload(f("skanthak.png"))) + assert @e.save + + url = url_for_file_column("e", "image") + assert_equal "/entry/image/#{@e.id}/skanthak.png", url + end + + def test_url_for_file_column_works_with_symbol + @e = Entry.new(:image => upload(f("skanthak.png"))) + assert @e.save + + url = url_for_file_column(:e, :image) + assert_equal "/entry/image/#{@e.id}/skanthak.png", url + end + + def test_url_for_file_column_works_with_object + e = Entry.new(:image => upload(f("skanthak.png"))) + assert e.save + + url = url_for_file_column(e, "image") + assert_equal "/entry/image/#{e.id}/skanthak.png", url + end + + def test_url_for_file_column_should_return_nil_on_no_uploaded_file + e = Entry.new + assert_nil url_for_file_column(e, "image") + end + + def test_url_for_file_column_without_extension + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "something/unknown", "local_filename") + assert e.save + assert_equal "/entry/image/#{e.id}/local_filename", url_for_file_column(e, "image") + end +end + +class UrlForFileColumnTest < Test::Unit::TestCase + include FileColumnHelper + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::UrlHelper + + def setup + Entry.file_column :image + + # mock up some request data structures for AssetTagHelper + @request = RequestMock.new + @request.relative_url_root = "/foo/bar" + @controller = self + end + + def request + @request + end + + IMAGE_URL = %r{^/foo/bar/entry/image/.+/skanthak.png$} + def test_with_image_tag + e = Entry.new(:image => upload(f("skanthak.png"))) + html = image_tag url_for_file_column(e, "image") + url = html.scan(/src=\"(.+)\"/).first.first + + assert_match IMAGE_URL, url + end + + def test_with_link_to_tag + e = Entry.new(:image => upload(f("skanthak.png"))) + html = link_to "Download", url_for_file_column(e, "image", :absolute => true) + url = html.scan(/href=\"(.+)\"/).first.first + + assert_match IMAGE_URL, url + end + + def test_relative_url_root_not_modified + e = Entry.new(:image => upload(f("skanthak.png"))) + url_for_file_column(e, "image", :absolute => true) + + assert_equal "/foo/bar", @request.relative_url_root + end +end diff --git a/vendor/plugins/file_column/test/file_column_test.rb b/vendor/plugins/file_column/test/file_column_test.rb new file mode 100755 index 000000000..452b7815d --- /dev/null +++ b/vendor/plugins/file_column/test/file_column_test.rb @@ -0,0 +1,650 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +require File.dirname(__FILE__) + '/fixtures/entry' + +class Movie < ActiveRecord::Base +end + + +class FileColumnTest < Test::Unit::TestCase + + def setup + # we define the file_columns here so that we can change + # settings easily in a single test + + Entry.file_column :image + Entry.file_column :file + Movie.file_column :movie + + clear_validations + end + + def teardown + FileUtils.rm_rf File.dirname(__FILE__)+"/public/entry/" + FileUtils.rm_rf File.dirname(__FILE__)+"/public/movie/" + FileUtils.rm_rf File.dirname(__FILE__)+"/public/my_store_dir/" + end + + def test_column_write_method + assert Entry.new.respond_to?("image=") + end + + def test_column_read_method + assert Entry.new.respond_to?("image") + end + + def test_sanitize_filename + assert_equal "test.jpg", FileColumn::sanitize_filename("test.jpg") + assert FileColumn::sanitize_filename("../../very_tricky/foo.bar") !~ /[\\\/]/, "slashes not removed" + assert_equal "__foo", FileColumn::sanitize_filename('`*foo') + assert_equal "foo.txt", FileColumn::sanitize_filename('c:\temp\foo.txt') + assert_equal "_.", FileColumn::sanitize_filename(".") + end + + def test_default_options + e = Entry.new + assert_match %r{/public/entry/image}, e.image_options[:store_dir] + assert_match %r{/public/entry/image/tmp}, e.image_options[:tmp_base_dir] + end + + def test_assign_without_save_with_tempfile + do_test_assign_without_save(:tempfile) + end + + def test_assign_without_save_with_stringio + do_test_assign_without_save(:stringio) + end + + def do_test_assign_without_save(upload_type) + e = Entry.new + e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png", upload_type) + assert e.image.is_a?(String), "#{e.image.inspect} is not a String" + assert File.exists?(e.image) + assert FileUtils.identical?(e.image, file_path("skanthak.png")) + end + + def test_filename_preserved + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "local_filename.jpg") + assert_equal "local_filename.jpg", File.basename(e.image) + end + + def test_filename_stored_in_attribute + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + assert_equal "kerb.jpg", e["image"] + end + + def test_extension_added + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "local_filename") + assert_equal "local_filename.jpg", File.basename(e.image) + assert_equal "local_filename.jpg", e["image"] + end + + def test_no_extension_without_content_type + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "something/unknown", "local_filename") + assert_equal "local_filename", File.basename(e.image) + assert_equal "local_filename", e["image"] + end + + def test_extension_unknown_type + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "not/known", "local_filename") + assert_equal "local_filename", File.basename(e.image) + assert_equal "local_filename", e["image"] + end + + def test_extension_unknown_type_with_extension + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "not/known", "local_filename.abc") + assert_equal "local_filename.abc", File.basename(e.image) + assert_equal "local_filename.abc", e["image"] + end + + def test_extension_corrected + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "local_filename.jpeg") + assert_equal "local_filename.jpg", File.basename(e.image) + assert_equal "local_filename.jpg", e["image"] + end + + def test_double_extension + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "application/x-tgz", "local_filename.tar.gz") + assert_equal "local_filename.tar.gz", File.basename(e.image) + assert_equal "local_filename.tar.gz", e["image"] + end + + FILE_UTILITY = "/usr/bin/file" + + def test_get_content_type_with_file + Entry.file_column :image, :file_exec => FILE_UTILITY + + # run this test only if the machine we are running on + # has the file utility installed + if File.executable?(FILE_UTILITY) + e = Entry.new + file = FileColumn::TempUploadedFile.new(e, "image") + file.instance_variable_set :@dir, File.dirname(file_path("kerb.jpg")) + file.instance_variable_set :@filename, File.basename(file_path("kerb.jpg")) + + assert_equal "image/jpeg", file.get_content_type + else + puts "Warning: Skipping test_get_content_type_with_file test as '#{options[:file_exec]}' does not exist" + end + end + + def test_fix_extension_with_file + Entry.file_column :image, :file_exec => FILE_UTILITY + + # run this test only if the machine we are running on + # has the file utility installed + if File.executable?(FILE_UTILITY) + e = Entry.new(:image => uploaded_file(file_path("skanthak.png"), "", "skanthak.jpg")) + + assert_equal "skanthak.png", File.basename(e.image) + else + puts "Warning: Skipping test_fix_extension_with_file test as '#{options[:file_exec]}' does not exist" + end + end + + def test_do_not_fix_file_extensions + Entry.file_column :image, :fix_file_extensions => false + + e = Entry.new(:image => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb")) + + assert_equal "kerb", File.basename(e.image) + end + + def test_correct_extension + e = Entry.new + file = FileColumn::TempUploadedFile.new(e, "image") + + assert_equal "filename.jpg", file.correct_extension("filename.jpeg","jpg") + assert_equal "filename.tar.gz", file.correct_extension("filename.jpg","tar.gz") + assert_equal "filename.jpg", file.correct_extension("filename.tar.gz","jpg") + assert_equal "Protokoll_01.09.2005.doc", file.correct_extension("Protokoll_01.09.2005","doc") + assert_equal "strange.filenames.exist.jpg", file.correct_extension("strange.filenames.exist","jpg") + assert_equal "another.strange.one.jpg", file.correct_extension("another.strange.one.png","jpg") + end + + def test_assign_with_save + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg") + tmp_file_path = e.image + assert e.save + assert File.exists?(e.image) + assert FileUtils.identical?(e.image, file_path("kerb.jpg")) + assert_equal "#{e.id}/kerb.jpg", e.image_relative_path + assert !File.exists?(tmp_file_path), "temporary file '#{tmp_file_path}' not removed" + assert !File.exists?(File.dirname(tmp_file_path)), "temporary directory '#{File.dirname(tmp_file_path)}' not removed" + + local_path = e.image + e = Entry.find(e.id) + assert_equal local_path, e.image + end + + def test_dir_methods + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg") + e.save + + assert_equal_paths File.join(RAILS_ROOT, "public", "entry", "image", e.id.to_s), e.image_dir + assert_equal File.join(e.id.to_s), e.image_relative_dir + end + + def test_store_dir_callback + Entry.file_column :image, {:store_dir => :my_store_dir} + e = Entry.new + + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg") + assert e.save + + assert_equal_paths File.join(RAILS_ROOT, "public", "my_store_dir", e.id), e.image_dir + end + + def test_tmp_dir_with_store_dir_callback + Entry.file_column :image, {:store_dir => :my_store_dir} + e = Entry.new + e.image = upload(f("kerb.jpg")) + + assert_equal File.expand_path(File.join(RAILS_ROOT, "public", "my_store_dir", "tmp")), File.expand_path(File.join(e.image_dir,"..")) + end + + def test_invalid_store_dir_callback + Entry.file_column :image, {:store_dir => :my_store_dir_doesnt_exit} + e = Entry.new + assert_raise(ArgumentError) { + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg") + e.save + } + end + + def test_subdir_parameter + e = Entry.new + assert_nil e.image("thumb") + assert_nil e.image_relative_path("thumb") + assert_nil e.image(nil) + + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg") + + assert_equal "kerb.jpg", File.basename(e.image("thumb")) + assert_equal "kerb.jpg", File.basename(e.image_relative_path("thumb")) + + assert_equal File.join(e.image_dir,"thumb","kerb.jpg"), e.image("thumb") + assert_match %r{/thumb/kerb\.jpg$}, e.image_relative_path("thumb") + + assert_equal e.image, e.image(nil) + assert_equal e.image_relative_path, e.image_relative_path(nil) + end + + def test_cleanup_after_destroy + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + assert e.save + local_path = e.image + assert File.exists?(local_path) + assert e.destroy + assert !File.exists?(local_path), "'#{local_path}' still exists although entry was destroyed" + assert !File.exists?(File.dirname(local_path)) + end + + def test_keep_tmp_image + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + e.validation_should_fail = true + assert !e.save, "e should not save due to validation errors" + assert File.exists?(local_path = e.image) + image_temp = e.image_temp + e = Entry.new("image_temp" => image_temp) + assert_equal local_path, e.image + assert e.save + assert FileUtils.identical?(e.image, file_path("kerb.jpg")) + end + + def test_keep_tmp_image_with_existing_image + e = Entry.new("image" =>uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + assert e.save + assert File.exists?(local_path = e.image) + e = Entry.find(e.id) + e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png") + e.validation_should_fail = true + assert !e.save + temp_path = e.image_temp + e = Entry.find(e.id) + e.image_temp = temp_path + assert e.save + + assert FileUtils.identical?(e.image, file_path("skanthak.png")) + assert !File.exists?(local_path), "old image has not been deleted" + end + + def test_replace_tmp_image_temp_first + do_test_replace_tmp_image([:image_temp, :image]) + end + + def test_replace_tmp_image_temp_last + do_test_replace_tmp_image([:image, :image_temp]) + end + + def do_test_replace_tmp_image(order) + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + e.validation_should_fail = true + assert !e.save + image_temp = e.image_temp + temp_path = e.image + new_img = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png") + e = Entry.new + for method in order + case method + when :image_temp then e.image_temp = image_temp + when :image then e.image = new_img + end + end + assert e.save + assert FileUtils.identical?(e.image, file_path("skanthak.png")), "'#{e.image}' is not the expected 'skanthak.png'" + assert !File.exists?(temp_path), "temporary file '#{temp_path}' is not cleaned up" + assert !File.exists?(File.dirname(temp_path)), "temporary directory not cleaned up" + assert e.image_just_uploaded? + end + + def test_replace_image_on_saved_object + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + assert e.save + old_file = e.image + e = Entry.find(e.id) + e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png") + assert e.save + assert FileUtils.identical?(file_path("skanthak.png"), e.image) + assert old_file != e.image + assert !File.exists?(old_file), "'#{old_file}' has not been cleaned up" + end + + def test_edit_without_touching_image + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + assert e.save + e = Entry.find(e.id) + assert e.save + assert FileUtils.identical?(file_path("kerb.jpg"), e.image) + end + + def test_save_without_image + e = Entry.new + assert e.save + e.reload + assert_nil e.image + end + + def test_delete_saved_image + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + assert e.save + local_path = e.image + e.image = nil + assert_nil e.image + assert File.exists?(local_path), "file '#{local_path}' should not be deleted until transaction is saved" + assert e.save + assert_nil e.image + assert !File.exists?(local_path) + e.reload + assert e["image"].blank? + e = Entry.find(e.id) + assert_nil e.image + end + + def test_delete_tmp_image + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + local_path = e.image + e.image = nil + assert_nil e.image + assert e["image"].blank? + assert !File.exists?(local_path) + end + + def test_delete_nonexistant_image + e = Entry.new + e.image = nil + assert e.save + assert_nil e.image + end + + def test_delete_image_on_non_null_column + e = Entry.new("file" => upload(f("skanthak.png"))) + assert e.save + + local_path = e.file + assert File.exists?(local_path) + e.file = nil + assert e.save + assert !File.exists?(local_path) + end + + def test_ie_filename + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'c:\images\kerb.jpg')) + assert e.image_relative_path =~ /^tmp\/[\d\.]+\/kerb\.jpg$/, "relative path '#{e.image_relative_path}' was not as expected" + assert File.exists?(e.image) + end + + def test_just_uploaded? + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'c:\images\kerb.jpg')) + assert e.image_just_uploaded? + assert e.save + assert e.image_just_uploaded? + + e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'kerb.jpg')) + temp_path = e.image_temp + e = Entry.new("image_temp" => temp_path) + assert !e.image_just_uploaded? + assert e.save + assert !e.image_just_uploaded? + end + + def test_empty_tmp + e = Entry.new + e.image_temp = "" + assert_nil e.image + end + + def test_empty_tmp_with_image + e = Entry.new + e.image_temp = "" + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'c:\images\kerb.jpg') + local_path = e.image + assert File.exists?(local_path) + e.image_temp = "" + assert local_path, e.image + end + + def test_empty_filename + e = Entry.new + assert_equal "", e["file"] + assert_nil e.file + assert_nil e["image"] + assert_nil e.image + end + + def test_with_two_file_columns + e = Entry.new + e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg") + e.file = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png") + assert e.save + assert_match %{/entry/image/}, e.image + assert_match %{/entry/file/}, e.file + assert FileUtils.identical?(e.image, file_path("kerb.jpg")) + assert FileUtils.identical?(e.file, file_path("skanthak.png")) + end + + def test_with_two_models + e = Entry.new(:image => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")) + m = Movie.new(:movie => uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")) + assert e.save + assert m.save + assert_match %{/entry/image/}, e.image + assert_match %{/movie/movie/}, m.movie + assert FileUtils.identical?(e.image, file_path("kerb.jpg")) + assert FileUtils.identical?(m.movie, file_path("skanthak.png")) + end + + def test_no_file_uploaded + e = Entry.new + assert_nothing_raised { e.image = + uploaded_file(nil, "application/octet-stream", "", :stringio) } + assert_equal nil, e.image + end + + # when safari submits a form where no file has been + # selected, it does not transmit a content-type and + # the result is an empty string "" + def test_no_file_uploaded_with_safari + e = Entry.new + assert_nothing_raised { e.image = "" } + assert_equal nil, e.image + end + + def test_detect_wrong_encoding + e = Entry.new + assert_raise(TypeError) { e.image ="img42.jpg" } + end + + def test_serializable_before_save + e = Entry.new + e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png") + assert_nothing_raised { + flash = Marshal.dump(e) + e = Marshal.load(flash) + } + assert File.exists?(e.image) + end + + def test_should_call_after_upload_on_new_upload + Entry.file_column :image, :after_upload => [:after_assign] + e = Entry.new + e.image = upload(f("skanthak.png")) + assert e.after_assign_called? + end + + def test_should_call_user_after_save_on_save + e = Entry.new(:image => upload(f("skanthak.png"))) + assert e.save + + assert_kind_of FileColumn::PermanentUploadedFile, e.send(:image_state) + assert e.after_save_called? + end + + + def test_assign_standard_files + e = Entry.new + e.image = File.new(file_path('skanthak.png')) + + assert_equal 'skanthak.png', File.basename(e.image) + assert FileUtils.identical?(file_path('skanthak.png'), e.image) + + assert e.save + end + + + def test_validates_filesize + Entry.validates_filesize_of :image, :in => 50.kilobytes..100.kilobytes + + e = Entry.new(:image => upload(f("kerb.jpg"))) + assert e.save + + e.image = upload(f("skanthak.png")) + assert !e.save + assert e.errors.invalid?("image") + end + + def test_validates_file_format_simple + e = Entry.new(:image => upload(f("skanthak.png"))) + assert e.save + + Entry.validates_file_format_of :image, :in => ["jpg"] + + e.image = upload(f("kerb.jpg")) + assert e.save + + e.image = upload(f("mysql.sql")) + assert !e.save + assert e.errors.invalid?("image") + + end + + def test_validates_image_size + Entry.validates_image_size :image, :min => "640x480" + + e = Entry.new(:image => upload(f("kerb.jpg"))) + assert e.save + + e = Entry.new(:image => upload(f("skanthak.png"))) + assert !e.save + assert e.errors.invalid?("image") + end + + def do_permission_test(uploaded_file, permissions=0641) + Entry.file_column :image, :permissions => permissions + + e = Entry.new(:image => uploaded_file) + assert e.save + + assert_equal permissions, (File.stat(e.image).mode & 0777) + end + + def test_permissions_with_small_file + do_permission_test upload(f("skanthak.png"), :guess, :stringio) + end + + def test_permission_with_big_file + do_permission_test upload(f("kerb.jpg")) + end + + def test_permission_that_overrides_umask + do_permission_test upload(f("skanthak.png"), :guess, :stringio), 0666 + do_permission_test upload(f("kerb.jpg")), 0666 + end + + def test_access_with_empty_id + # an empty id might happen after a clone or through some other + # strange event. Since we would create a path that contains nothing + # where the id would have been, we should fail fast with an exception + # in this case + + e = Entry.new(:image => upload(f("skanthak.png"))) + assert e.save + id = e.id + + e = Entry.find(id) + + e["id"] = "" + assert_raise(RuntimeError) { e.image } + + e = Entry.find(id) + e["id"] = nil + assert_raise(RuntimeError) { e.image } + end +end + +# Tests for moving temp dir to permanent dir +class FileColumnMoveTest < Test::Unit::TestCase + + def setup + # we define the file_columns here so that we can change + # settings easily in a single test + + Entry.file_column :image + + end + + def teardown + FileUtils.rm_rf File.dirname(__FILE__)+"/public/entry/" + end + + def test_should_move_additional_files_from_tmp + e = Entry.new + e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png") + FileUtils.cp file_path("kerb.jpg"), File.dirname(e.image) + assert e.save + dir = File.dirname(e.image) + assert File.exists?(File.join(dir, "skanthak.png")) + assert File.exists?(File.join(dir, "kerb.jpg")) + end + + def test_should_move_direcotries_on_save + e = Entry.new(:image => upload(f("skanthak.png"))) + + FileUtils.mkdir( e.image_dir+"/foo" ) + FileUtils.cp file_path("kerb.jpg"), e.image_dir+"/foo/kerb.jpg" + + assert e.save + + assert File.exists?(e.image) + assert File.exists?(File.dirname(e.image)+"/foo/kerb.jpg") + end + + def test_should_overwrite_dirs_with_files_on_reupload + e = Entry.new(:image => upload(f("skanthak.png"))) + + FileUtils.mkdir( e.image_dir+"/kerb.jpg") + FileUtils.cp file_path("kerb.jpg"), e.image_dir+"/kerb.jpg/" + assert e.save + + e.image = upload(f("kerb.jpg")) + assert e.save + + assert File.file?(e.image_dir+"/kerb.jpg") + end + + def test_should_overwrite_files_with_dirs_on_reupload + e = Entry.new(:image => upload(f("skanthak.png"))) + + assert e.save + assert File.file?(e.image_dir+"/skanthak.png") + + e.image = upload(f("kerb.jpg")) + FileUtils.mkdir(e.image_dir+"/skanthak.png") + + assert e.save + assert File.file?(e.image_dir+"/kerb.jpg") + assert !File.file?(e.image_dir+"/skanthak.png") + assert File.directory?(e.image_dir+"/skanthak.png") + end + +end + diff --git a/vendor/plugins/file_column/test/fixtures/entry.rb b/vendor/plugins/file_column/test/fixtures/entry.rb new file mode 100644 index 000000000..b9f7c954d --- /dev/null +++ b/vendor/plugins/file_column/test/fixtures/entry.rb @@ -0,0 +1,32 @@ +class Entry < ActiveRecord::Base + attr_accessor :validation_should_fail + + def validate + errors.add("image","some stupid error") if @validation_should_fail + end + + def after_assign + @after_assign_called = true + end + + def after_assign_called? + @after_assign_called + end + + def after_save + @after_save_called = true + end + + def after_save_called? + @after_save_called + end + + def my_store_dir + # not really dynamic but at least it could be... + "my_store_dir" + end + + def load_image_with_rmagick(path) + Magick::Image::read(path).first + end +end diff --git a/vendor/plugins/file_column/test/fixtures/invalid-image.jpg b/vendor/plugins/file_column/test/fixtures/invalid-image.jpg new file mode 100644 index 000000000..bd4933b43 --- /dev/null +++ b/vendor/plugins/file_column/test/fixtures/invalid-image.jpg @@ -0,0 +1 @@ +this is certainly not a JPEG image diff --git a/vendor/plugins/file_column/test/fixtures/kerb.jpg b/vendor/plugins/file_column/test/fixtures/kerb.jpg new file mode 100644 index 000000000..083138e4f Binary files /dev/null and b/vendor/plugins/file_column/test/fixtures/kerb.jpg differ diff --git a/vendor/plugins/file_column/test/fixtures/mysql.sql b/vendor/plugins/file_column/test/fixtures/mysql.sql new file mode 100644 index 000000000..55143f27b --- /dev/null +++ b/vendor/plugins/file_column/test/fixtures/mysql.sql @@ -0,0 +1,25 @@ +-- MySQL dump 9.11 +-- +-- Host: localhost Database: file_column_test +-- ------------------------------------------------------ +-- Server version 4.0.24 + +-- +-- Table structure for table `entries` +-- + +DROP TABLE IF EXISTS entries; +CREATE TABLE entries ( + id int(11) NOT NULL auto_increment, + image varchar(200) default NULL, + file varchar(200) NOT NULL, + PRIMARY KEY (id) +) TYPE=MyISAM; + +DROP TABLE IF EXISTS movies; +CREATE TABLE movies ( + id int(11) NOT NULL auto_increment, + movie varchar(200) default NULL, + PRIMARY KEY (id) +) TYPE=MyISAM; + diff --git a/vendor/plugins/file_column/test/fixtures/schema.rb b/vendor/plugins/file_column/test/fixtures/schema.rb new file mode 100644 index 000000000..49b5ddbaa --- /dev/null +++ b/vendor/plugins/file_column/test/fixtures/schema.rb @@ -0,0 +1,10 @@ +ActiveRecord::Schema.define do + create_table :entries, :force => true do |t| + t.column :image, :string, :null => true + t.column :file, :string, :null => false + end + + create_table :movies, :force => true do |t| + t.column :movie, :string + end +end diff --git a/vendor/plugins/file_column/test/fixtures/skanthak.png b/vendor/plugins/file_column/test/fixtures/skanthak.png new file mode 100644 index 000000000..7415eb6e4 Binary files /dev/null and b/vendor/plugins/file_column/test/fixtures/skanthak.png differ diff --git a/vendor/plugins/file_column/test/magick_test.rb b/vendor/plugins/file_column/test/magick_test.rb new file mode 100644 index 000000000..036271927 --- /dev/null +++ b/vendor/plugins/file_column/test/magick_test.rb @@ -0,0 +1,380 @@ +require File.dirname(__FILE__) + '/abstract_unit' +require 'RMagick' +require File.dirname(__FILE__) + '/fixtures/entry' + + +class AbstractRMagickTest < Test::Unit::TestCase + def teardown + FileUtils.rm_rf File.dirname(__FILE__)+"/public/entry/" + end + + def test_truth + assert true + end + + private + + def read_image(path) + Magick::Image::read(path).first + end + + def assert_max_image_size(img, s) + assert img.columns <= s, "img has #{img.columns} columns, expected: #{s}" + assert img.rows <= s, "img has #{img.rows} rows, expected: #{s}" + assert_equal s, [img.columns, img.rows].max + end +end + +class RMagickSimpleTest < AbstractRMagickTest + def setup + Entry.file_column :image, :magick => { :geometry => "100x100" } + end + + def test_simple_resize_without_save + e = Entry.new + e.image = upload(f("kerb.jpg")) + + img = read_image(e.image) + assert_max_image_size img, 100 + end + + def test_simple_resize_with_save + e = Entry.new + e.image = upload(f("kerb.jpg")) + assert e.save + e.reload + + img = read_image(e.image) + assert_max_image_size img, 100 + end + + def test_resize_on_saved_image + Entry.file_column :image, :magick => { :geometry => "100x100" } + + e = Entry.new + e.image = upload(f("skanthak.png")) + assert e.save + e.reload + old_path = e.image + + e.image = upload(f("kerb.jpg")) + assert e.save + assert "kerb.jpg", File.basename(e.image) + assert !File.exists?(old_path), "old image '#{old_path}' still exists" + + img = read_image(e.image) + assert_max_image_size img, 100 + end + + def test_invalid_image + e = Entry.new + assert_nothing_raised { e.image = upload(f("invalid-image.jpg")) } + assert !e.valid? + end + + def test_serializable + e = Entry.new + e.image = upload(f("skanthak.png")) + assert_nothing_raised { + flash = Marshal.dump(e) + e = Marshal.load(flash) + } + assert File.exists?(e.image) + end + + def test_imagemagick_still_usable + e = Entry.new + assert_nothing_raised { + img = e.load_image_with_rmagick(file_path("skanthak.png")) + assert img.kind_of?(Magick::Image) + } + end +end + +class RMagickRequiresImageTest < AbstractRMagickTest + def setup + Entry.file_column :image, :magick => { + :size => "100x100>", + :image_required => false, + :versions => { + :thumb => "80x80>", + :large => {:size => "200x200>", :lazy => true} + } + } + end + + def test_image_required_with_image + e = Entry.new(:image => upload(f("skanthak.png"))) + assert_max_image_size read_image(e.image), 100 + assert e.valid? + end + + def test_image_required_with_invalid_image + e = Entry.new(:image => upload(f("invalid-image.jpg"))) + assert e.valid?, "did not ignore invalid image" + assert FileUtils.identical?(e.image, f("invalid-image.jpg")), "uploaded file has not been left alone" + end + + def test_versions_with_invalid_image + e = Entry.new(:image => upload(f("invalid-image.jpg"))) + assert e.valid? + + image_state = e.send(:image_state) + assert_nil image_state.create_magick_version_if_needed(:thumb) + assert_nil image_state.create_magick_version_if_needed(:large) + assert_nil image_state.create_magick_version_if_needed("300x300>") + end +end + +class RMagickCustomAttributesTest < AbstractRMagickTest + def assert_image_property(img, property, value, text = nil) + assert File.exists?(img), "the image does not exist" + assert_equal value, read_image(img).send(property), text + end + + def test_simple_attributes + Entry.file_column :image, :magick => { :attributes => { :quality => 20 } } + e = Entry.new("image" => upload(f("kerb.jpg"))) + assert_image_property e.image, :quality, 20, "the quality was not set" + end + + def test_version_attributes + Entry.file_column :image, :magick => { + :versions => { + :thumb => { :attributes => { :quality => 20 } } + } + } + e = Entry.new("image" => upload(f("kerb.jpg"))) + assert_image_property e.image("thumb"), :quality, 20, "the quality was not set" + end + + def test_lazy_attributes + Entry.file_column :image, :magick => { + :versions => { + :thumb => { :attributes => { :quality => 20 }, :lazy => true } + } + } + e = Entry.new("image" => upload(f("kerb.jpg"))) + e.send(:image_state).create_magick_version_if_needed(:thumb) + assert_image_property e.image("thumb"), :quality, 20, "the quality was not set" + end +end + +class RMagickVersionsTest < AbstractRMagickTest + def setup + Entry.file_column :image, :magick => {:geometry => "200x200", + :versions => { + :thumb => "50x50", + :medium => {:geometry => "100x100", :name => "100_100"}, + :large => {:geometry => "150x150", :lazy => true} + } + } + end + + + def test_should_create_thumb + e = Entry.new("image" => upload(f("skanthak.png"))) + + assert File.exists?(e.image("thumb")), "thumb-nail not created" + + assert_max_image_size read_image(e.image("thumb")), 50 + end + + def test_version_name_can_be_different_from_key + e = Entry.new("image" => upload(f("skanthak.png"))) + + assert File.exists?(e.image("100_100")) + assert !File.exists?(e.image("medium")) + end + + def test_should_not_create_lazy_versions + e = Entry.new("image" => upload(f("skanthak.png"))) + assert !File.exists?(e.image("large")), "lazy versions should not be created unless needed" + end + + def test_should_create_lazy_version_on_demand + e = Entry.new("image" => upload(f("skanthak.png"))) + + e.send(:image_state).create_magick_version_if_needed(:large) + + assert File.exists?(e.image("large")), "lazy version should be created on demand" + + assert_max_image_size read_image(e.image("large")), 150 + end + + def test_generated_name_should_not_change + e = Entry.new("image" => upload(f("skanthak.png"))) + + name1 = e.send(:image_state).create_magick_version_if_needed("50x50") + name2 = e.send(:image_state).create_magick_version_if_needed("50x50") + name3 = e.send(:image_state).create_magick_version_if_needed(:geometry => "50x50") + assert_equal name1, name2, "hash value has changed" + assert_equal name1, name3, "hash value has changed" + end + + def test_should_create_version_with_string + e = Entry.new("image" => upload(f("skanthak.png"))) + + name = e.send(:image_state).create_magick_version_if_needed("32x32") + + assert File.exists?(e.image(name)) + + assert_max_image_size read_image(e.image(name)), 32 + end + + def test_should_create_safe_auto_id + e = Entry.new("image" => upload(f("skanthak.png"))) + + name = e.send(:image_state).create_magick_version_if_needed("32x32") + + assert_match /^[a-zA-Z0-9]+$/, name + end +end + +class RMagickCroppingTest < AbstractRMagickTest + def setup + Entry.file_column :image, :magick => {:geometry => "200x200", + :versions => { + :thumb => {:crop => "1:1", :geometry => "50x50"} + } + } + end + + def test_should_crop_image_on_upload + e = Entry.new("image" => upload(f("skanthak.png"))) + + img = read_image(e.image("thumb")) + + assert_equal 50, img.rows + assert_equal 50, img.columns + end + +end + +class UrlForImageColumnTest < AbstractRMagickTest + include FileColumnHelper + + def setup + Entry.file_column :image, :magick => { + :versions => {:thumb => "50x50"} + } + @request = RequestMock.new + end + + def test_should_use_version_on_symbol_option + e = Entry.new(:image => upload(f("skanthak.png"))) + + url = url_for_image_column(e, "image", :thumb) + assert_match %r{^/entry/image/tmp/.+/thumb/skanthak.png$}, url + end + + def test_should_use_string_as_size + e = Entry.new(:image => upload(f("skanthak.png"))) + + url = url_for_image_column(e, "image", "50x50") + + assert_match %r{^/entry/image/tmp/.+/.+/skanthak.png$}, url + + url =~ /\/([^\/]+)\/skanthak.png$/ + dirname = $1 + + assert_max_image_size read_image(e.image(dirname)), 50 + end + + def test_should_accept_version_hash + e = Entry.new(:image => upload(f("skanthak.png"))) + + url = url_for_image_column(e, "image", :size => "50x50", :crop => "1:1", :name => "small") + + assert_match %r{^/entry/image/tmp/.+/small/skanthak.png$}, url + + img = read_image(e.image("small")) + assert_equal 50, img.rows + assert_equal 50, img.columns + end +end + +class RMagickPermissionsTest < AbstractRMagickTest + def setup + Entry.file_column :image, :magick => {:geometry => "200x200", + :versions => { + :thumb => {:crop => "1:1", :geometry => "50x50"} + } + }, :permissions => 0616 + end + + def check_permissions(e) + assert_equal 0616, (File.stat(e.image).mode & 0777) + assert_equal 0616, (File.stat(e.image("thumb")).mode & 0777) + end + + def test_permissions_with_rmagick + e = Entry.new(:image => upload(f("skanthak.png"))) + + check_permissions e + + assert e.save + + check_permissions e + end +end + +class Entry + def transform_grey(img) + img.quantize(256, Magick::GRAYColorspace) + end +end + +class RMagickTransformationTest < AbstractRMagickTest + def assert_transformed(image) + assert File.exists?(image), "the image does not exist" + assert 256 > read_image(image).number_colors, "the number of colors was not changed" + end + + def test_simple_transformation + Entry.file_column :image, :magick => { :transformation => Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) } } + e = Entry.new("image" => upload(f("skanthak.png"))) + assert_transformed(e.image) + end + + def test_simple_version_transformation + Entry.file_column :image, :magick => { + :versions => { :thumb => Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) } } + } + e = Entry.new("image" => upload(f("skanthak.png"))) + assert_transformed(e.image("thumb")) + end + + def test_complex_version_transformation + Entry.file_column :image, :magick => { + :versions => { + :thumb => { :transformation => Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) } } + } + } + e = Entry.new("image" => upload(f("skanthak.png"))) + assert_transformed(e.image("thumb")) + end + + def test_lazy_transformation + Entry.file_column :image, :magick => { + :versions => { + :thumb => { :transformation => Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) }, :lazy => true } + } + } + e = Entry.new("image" => upload(f("skanthak.png"))) + e.send(:image_state).create_magick_version_if_needed(:thumb) + assert_transformed(e.image("thumb")) + end + + def test_simple_callback_transformation + Entry.file_column :image, :magick => :transform_grey + e = Entry.new(:image => upload(f("skanthak.png"))) + assert_transformed(e.image) + end + + def test_complex_callback_transformation + Entry.file_column :image, :magick => { :transformation => :transform_grey } + e = Entry.new(:image => upload(f("skanthak.png"))) + assert_transformed(e.image) + end +end diff --git a/vendor/plugins/file_column/test/magick_view_only_test.rb b/vendor/plugins/file_column/test/magick_view_only_test.rb new file mode 100644 index 000000000..a7daa6172 --- /dev/null +++ b/vendor/plugins/file_column/test/magick_view_only_test.rb @@ -0,0 +1,21 @@ +require File.dirname(__FILE__) + '/abstract_unit' +require File.dirname(__FILE__) + '/fixtures/entry' + +class RMagickViewOnlyTest < Test::Unit::TestCase + include FileColumnHelper + + def setup + Entry.file_column :image + @request = RequestMock.new + end + + def teardown + FileUtils.rm_rf File.dirname(__FILE__)+"/public/entry/" + end + + def test_url_for_image_column_without_model_versions + e = Entry.new(:image => upload(f("skanthak.png"))) + + assert_nothing_raised { url_for_image_column e, "image", "50x50" } + end +end diff --git a/vendor/plugins/sql_session_store/LICENSE b/vendor/plugins/sql_session_store/LICENSE new file mode 100644 index 000000000..5cb5c7b95 --- /dev/null +++ b/vendor/plugins/sql_session_store/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2006-2008 Dr.-Ing. Stefan Kaes + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/plugins/sql_session_store/README b/vendor/plugins/sql_session_store/README new file mode 100755 index 000000000..07b083343 --- /dev/null +++ b/vendor/plugins/sql_session_store/README @@ -0,0 +1,60 @@ +== SqlSessionStore + +See http://railsexpress.de/blog/articles/2005/12/19/roll-your-own-sql-session-store + +Only Mysql, Postgres and Oracle are currently supported (others work, +but you won't see much performance improvement). + +== Step 1 + +If you have generated your sessions table using rake db:sessions:create, go to Step 2 + +If you're using an old version of sql_session_store, run + script/generate sql_session_store DB +where DB is mysql, postgresql or oracle + +Then run + rake migrate +or + rake db:migrate +for edge rails. + +== Step 2 + +Add the code below after the initializer config section: + + ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS. + update(:database_manager => SqlSessionStore) + +Finally, depending on your database type, add + + SqlSessionStore.session_class = MysqlSession +or + + SqlSessionStore.session_class = PostgresqlSession +or + SqlSessionStore.session_class = OracleSession + +after the initializer section in environment.rb + +== Step 3 (optional) + +If you want to use a database separate from your default one to store +your sessions, specify a configuration in your database.yml file (say +sessions), and establish the connection on SqlSession in +environment.rb: + + SqlSession.establish_connection :sessions + + +== IMPORTANT NOTES + +1. The class name SQLSessionStore has changed to SqlSessionStore to + let Rails work its autoload magic. + +2. You will need the binary drivers for Mysql or Postgresql. + These have been verified to work: + + * ruby-postgres (0.7.1.2005.12.21) with postgreql 8.1 + * ruby-mysql 2.7.1 with Mysql 4.1 + * ruby-mysql 2.7.2 with Mysql 5.0 diff --git a/vendor/plugins/sql_session_store/Rakefile b/vendor/plugins/sql_session_store/Rakefile new file mode 100755 index 000000000..0145def2f --- /dev/null +++ b/vendor/plugins/sql_session_store/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the sql_session_store plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the sql_session_store plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'SqlSessionStore' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/sql_session_store/generators/sql_session_store/USAGE b/vendor/plugins/sql_session_store/generators/sql_session_store/USAGE new file mode 100755 index 000000000..1e3f58a67 --- /dev/null +++ b/vendor/plugins/sql_session_store/generators/sql_session_store/USAGE @@ -0,0 +1,17 @@ +Description: + The sql_session_store generator creates a migration for use with + the sql session store. It takes one argument: the database + type. Only mysql and postgreql are currently supported. + +Example: + ./script/generate sql_session_store mysql + + This will create the following migration: + + db/migrate/XXX_add_sql_session.rb + + Use + + ./script/generate sql_session_store postgreql + + to get a migration for postgres. diff --git a/vendor/plugins/sql_session_store/generators/sql_session_store/sql_session_store_generator.rb b/vendor/plugins/sql_session_store/generators/sql_session_store/sql_session_store_generator.rb new file mode 100755 index 000000000..6af6bd0bc --- /dev/null +++ b/vendor/plugins/sql_session_store/generators/sql_session_store/sql_session_store_generator.rb @@ -0,0 +1,25 @@ +class SqlSessionStoreGenerator < Rails::Generator::NamedBase + def initialize(runtime_args, runtime_options = {}) + runtime_args.insert(0, 'add_sql_session') + if runtime_args.include?('postgresql') + @_database = 'postgresql' + elsif runtime_args.include?('mysql') + @_database = 'mysql' + elsif runtime_args.include?('oracle') + @_database = 'oracle' + else + puts "error: database type not given.\nvalid arguments are: mysql or postgresql" + exit + end + super + end + + def manifest + record do |m| + m.migration_template("migration.rb", 'db/migrate', + :assigns => { :migration_name => "SqlSessionStoreSetup", :database => @_database }, + :migration_file_name => "sql_session_store_setup" + ) + end + end +end diff --git a/vendor/plugins/sql_session_store/generators/sql_session_store/templates/migration.rb b/vendor/plugins/sql_session_store/generators/sql_session_store/templates/migration.rb new file mode 100755 index 000000000..512650068 --- /dev/null +++ b/vendor/plugins/sql_session_store/generators/sql_session_store/templates/migration.rb @@ -0,0 +1,38 @@ +class <%= migration_name %> < ActiveRecord::Migration + + class Session < ActiveRecord::Base; end + + def self.up + c = ActiveRecord::Base.connection + if c.tables.include?('sessions') + if (columns = Session.column_names).include?('sessid') + rename_column :sessions, :sessid, :session_id + else + add_column :sessions, :session_id, :string unless columns.include?('session_id') + add_column :sessions, :data, :text unless columns.include?('data') + if columns.include?('created_on') + rename_column :sessions, :created_on, :created_at + else + add_column :sessions, :created_at, :timestamp unless columns.include?('created_at') + end + if columns.include?('updated_on') + rename_column :sessions, :updated_on, :updated_at + else + add_column :sessions, :updated_at, :timestamp unless columns.include?('updated_at') + end + end + else + create_table :sessions, :options => '<%= database == "mysql" ? "ENGINE=MyISAM" : "" %>' do |t| + t.column :session_id, :string + t.column :data, :text + t.column :created_at, :timestamp + t.column :updated_at, :timestamp + end + add_index :sessions, :session_id, :name => 'session_id_idx' + end + end + + def self.down + raise IrreversibleMigration + end +end diff --git a/vendor/plugins/sql_session_store/init.rb b/vendor/plugins/sql_session_store/init.rb new file mode 100755 index 000000000..956151ea7 --- /dev/null +++ b/vendor/plugins/sql_session_store/init.rb @@ -0,0 +1 @@ +require 'sql_session_store' diff --git a/vendor/plugins/sql_session_store/install.rb b/vendor/plugins/sql_session_store/install.rb new file mode 100755 index 000000000..f40549dfe --- /dev/null +++ b/vendor/plugins/sql_session_store/install.rb @@ -0,0 +1,2 @@ +# Install hook code here +puts IO.read(File.join(File.dirname(__FILE__), 'README')) diff --git a/vendor/plugins/sql_session_store/lib/mysql_session.rb b/vendor/plugins/sql_session_store/lib/mysql_session.rb new file mode 100755 index 000000000..8c86384c9 --- /dev/null +++ b/vendor/plugins/sql_session_store/lib/mysql_session.rb @@ -0,0 +1,132 @@ +require 'mysql' + +# allow access to the real Mysql connection +class ActiveRecord::ConnectionAdapters::MysqlAdapter + attr_reader :connection +end + +# MysqlSession is a down to the bare metal session store +# implementation to be used with +SQLSessionStore+. It is much faster +# than the default ActiveRecord implementation. +# +# The implementation assumes that the table column names are 'id', +# 'data', 'created_at' and 'updated_at'. If you want use other names, +# you will need to change the SQL statments in the code. + +class MysqlSession + + # if you need Rails components, and you have a pages which create + # new sessions, and embed components insides this pages that need + # session access, then you *must* set +eager_session_creation+ to + # true (as of Rails 1.0). + cattr_accessor :eager_session_creation + @@eager_session_creation = false + + attr_accessor :id, :session_id, :data + + def initialize(session_id, data) + @session_id = session_id + @data = data + @id = nil + end + + class << self + + # retrieve the session table connection and get the 'raw' Mysql connection from it + def session_connection + SqlSession.connection.connection + end + + # try to find a session with a given +session_id+. returns nil if + # no such session exists. note that we don't retrieve + # +created_at+ and +updated_at+ as they are not accessed anywhyere + # outside this class + def find_session(session_id) + connection = session_connection + connection.query_with_result = true + session_id = Mysql::quote(session_id) + result = connection.query("SELECT id, data FROM sessions WHERE `session_id`='#{session_id}' LIMIT 1") + my_session = nil + # each is used below, as other methods barf on my 64bit linux machine + # I suspect this to be a bug in mysql-ruby + result.each do |row| + my_session = new(session_id, row[1]) + my_session.id = row[0] + end + result.free + my_session + end + + # create a new session with given +session_id+ and +data+ + # and save it immediately to the database + def create_session(session_id, data) + session_id = Mysql::quote(session_id) + new_session = new(session_id, data) + if @@eager_session_creation + connection = session_connection + connection.query("INSERT INTO sessions (`created_at`, `updated_at`, `session_id`, `data`) VALUES (NOW(), NOW(), '#{session_id}', '#{Mysql::quote(data)}')") + new_session.id = connection.insert_id + end + new_session + end + + # delete all sessions meeting a given +condition+. it is the + # caller's responsibility to pass a valid sql condition + def delete_all(condition=nil) + if condition + session_connection.query("DELETE FROM sessions WHERE #{condition}") + else + session_connection.query("DELETE FROM sessions") + end + end + + end # class methods + + # update session with given +data+. + # unlike the default implementation using ActiveRecord, updating of + # column `updated_at` will be done by the datbase itself + def update_session(data) + connection = self.class.session_connection + if @id + # if @id is not nil, this is a session already stored in the database + # update the relevant field using @id as key + connection.query("UPDATE sessions SET `updated_at`=NOW(), `data`='#{Mysql::quote(data)}' WHERE id=#{@id}") + else + # if @id is nil, we need to create a new session in the database + # and set @id to the primary key of the inserted record + connection.query("INSERT INTO sessions (`created_at`, `updated_at`, `session_id`, `data`) VALUES (NOW(), NOW(), '#{@session_id}', '#{Mysql::quote(data)}')") + @id = connection.insert_id + end + end + + # destroy the current session + def destroy + self.class.delete_all("session_id='#{session_id}'") + end + +end + +__END__ + +# This software is released under the MIT license +# +# Copyright (c) 2005-2008 Stefan Kaes + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/plugins/sql_session_store/lib/oracle_session.rb b/vendor/plugins/sql_session_store/lib/oracle_session.rb new file mode 100755 index 000000000..0b82f6391 --- /dev/null +++ b/vendor/plugins/sql_session_store/lib/oracle_session.rb @@ -0,0 +1,143 @@ +require 'oci8' + +# allow access to the real Oracle connection +class ActiveRecord::ConnectionAdapters::OracleAdapter + attr_reader :connection +end + +# OracleSession is a down to the bare metal session store +# implementation to be used with +SQLSessionStore+. It is much faster +# than the default ActiveRecord implementation. +# +# The implementation assumes that the table column names are 'id', +# 'session_id', 'data', 'created_at' and 'updated_at'. If you want use +# other names, you will need to change the SQL statments in the code. +# +# This table layout is compatible with ActiveRecordStore. + +class OracleSession + + # if you need Rails components, and you have a pages which create + # new sessions, and embed components insides these pages that need + # session access, then you *must* set +eager_session_creation+ to + # true (as of Rails 1.0). Not needed for Rails 1.1 and up. + cattr_accessor :eager_session_creation + @@eager_session_creation = false + + attr_accessor :id, :session_id, :data + + def initialize(session_id, data) + @session_id = session_id + @data = data + @id = nil + end + + class << self + + # retrieve the session table connection and get the 'raw' Oracle connection from it + def session_connection + SqlSession.connection.connection + end + + # try to find a session with a given +session_id+. returns nil if + # no such session exists. note that we don't retrieve + # +created_at+ and +updated_at+ as they are not accessed anywhyere + # outside this class. + def find_session(session_id) + new_session = nil + connection = session_connection + result = connection.exec("SELECT id, data FROM sessions WHERE session_id = :a and rownum=1", session_id) + + # Make sure to save the @id if we find an existing session + while row = result.fetch + new_session = new(session_id,row[1].read) + new_session.id = row[0] + end + result.close + new_session + end + + # create a new session with given +session_id+ and +data+ + # and save it immediately to the database + def create_session(session_id, data) + new_session = new(session_id, data) + if @@eager_session_creation + connection = session_connection + connection.exec("INSERT INTO sessions (id, created_at, updated_at, session_id, data)"+ + " VALUES (sessions_seq.nextval, SYSDATE, SYSDATE, :a, :b)", + session_id, data) + result = connection.exec("SELECT sessions_seq.currval FROM dual") + row = result.fetch + new_session.id = row[0].to_i + end + new_session + end + + # delete all sessions meeting a given +condition+. it is the + # caller's responsibility to pass a valid sql condition + def delete_all(condition=nil) + if condition + session_connection.exec("DELETE FROM sessions WHERE #{condition}") + else + session_connection.exec("DELETE FROM sessions") + end + end + + end # class methods + + # update session with given +data+. + # unlike the default implementation using ActiveRecord, updating of + # column `updated_at` will be done by the database itself + def update_session(data) + connection = self.class.session_connection + if @id + # if @id is not nil, this is a session already stored in the database + # update the relevant field using @id as key + connection.exec("UPDATE sessions SET updated_at = SYSDATE, data = :a WHERE id = :b", + data, @id) + else + # if @id is nil, we need to create a new session in the database + # and set @id to the primary key of the inserted record + connection.exec("INSERT INTO sessions (id, created_at, updated_at, session_id, data)"+ + " VALUES (sessions_seq.nextval, SYSDATE, SYSDATE, :a, :b)", + @session_id, data) + result = connection.exec("SELECT sessions_seq.currval FROM dual") + row = result.fetch + @id = row[0].to_i + end + end + + # destroy the current session + def destroy + self.class.delete_all("session_id='#{session_id}'") + end + +end + +__END__ + +# This software is released under the MIT license +# +# Copyright (c) 2006-2008 Stefan Kaes +# Copyright (c) 2006-2008 Tiago Macedo +# Copyright (c) 2007-2008 Nate Wiger +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/plugins/sql_session_store/lib/postgresql_session.rb b/vendor/plugins/sql_session_store/lib/postgresql_session.rb new file mode 100755 index 000000000..d922913aa --- /dev/null +++ b/vendor/plugins/sql_session_store/lib/postgresql_session.rb @@ -0,0 +1,136 @@ +require 'postgres' + +# allow access to the real Mysql connection +class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter + attr_reader :connection +end + +# PostgresqlSession is a down to the bare metal session store +# implementation to be used with +SQLSessionStore+. It is much faster +# than the default ActiveRecord implementation. +# +# The implementation assumes that the table column names are 'id', +# 'session_id', 'data', 'created_at' and 'updated_at'. If you want use +# other names, you will need to change the SQL statments in the code. +# +# This table layout is compatible with ActiveRecordStore. + +class PostgresqlSession + + # if you need Rails components, and you have a pages which create + # new sessions, and embed components insides these pages that need + # session access, then you *must* set +eager_session_creation+ to + # true (as of Rails 1.0). Not needed for Rails 1.1 and up. + cattr_accessor :eager_session_creation + @@eager_session_creation = false + + attr_accessor :id, :session_id, :data + + def initialize(session_id, data) + @session_id = session_id + @data = data + @id = nil + end + + class << self + + # retrieve the session table connection and get the 'raw' Postgresql connection from it + def session_connection + SqlSession.connection.connection + end + + # try to find a session with a given +session_id+. returns nil if + # no such session exists. note that we don't retrieve + # +created_at+ and +updated_at+ as they are not accessed anywhyere + # outside this class. + def find_session(session_id) + connection = session_connection + # postgres adds string delimiters when quoting, so strip them off + session_id = PGconn::quote(session_id)[1..-2] + result = connection.query("SELECT id, data FROM sessions WHERE session_id='#{session_id}' LIMIT 1") + my_session = nil + # each is used below, as other methods barf on my 64bit linux machine + # I suspect this to be a bug in mysql-ruby + result.each do |row| + my_session = new(session_id, row[1]) + my_session.id = row[0] + end + result.clear + my_session + end + + # create a new session with given +session_id+ and +data+ + # and save it immediately to the database + def create_session(session_id, data) + # postgres adds string delimiters when quoting, so strip them off + session_id = PGconn::quote(session_id)[1..-2] + new_session = new(session_id, data) + if @@eager_session_creation + connection = session_connection + connection.query("INSERT INTO sessions (\"created_at\", \"updated_at\", \"session_id\", \"data\") VALUES (NOW(), NOW(), '#{session_id}', #{PGconn::quote(data)})") + new_session.id = connection.lastval + end + new_session + end + + # delete all sessions meeting a given +condition+. it is the + # caller's responsibility to pass a valid sql condition + def delete_all(condition=nil) + if condition + session_connection.query("DELETE FROM sessions WHERE #{condition}") + else + session_connection.query("DELETE FROM sessions") + end + end + + end # class methods + + # update session with given +data+. + # unlike the default implementation using ActiveRecord, updating of + # column `updated_at` will be done by the database itself + def update_session(data) + connection = self.class.session_connection + if @id + # if @id is not nil, this is a session already stored in the database + # update the relevant field using @id as key + connection.query("UPDATE sessions SET \"updated_at\"=NOW(), \"data\"=#{PGconn::quote(data)} WHERE id=#{@id}") + else + # if @id is nil, we need to create a new session in the database + # and set @id to the primary key of the inserted record + connection.query("INSERT INTO sessions (\"created_at\", \"updated_at\", \"session_id\", \"data\") VALUES (NOW(), NOW(), '#{@session_id}', #{PGconn::quote(data)})") + @id = connection.lastval rescue connection.query("select lastval()").first[0] + end + end + + # destroy the current session + def destroy + self.class.delete_all("session_id=#{PGconn.quote(session_id)}") + end + +end + +__END__ + +# This software is released under the MIT license +# +# Copyright (c) 2006-2008 Stefan Kaes + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/plugins/sql_session_store/lib/sql_session.rb b/vendor/plugins/sql_session_store/lib/sql_session.rb new file mode 100644 index 000000000..19d2ad51e --- /dev/null +++ b/vendor/plugins/sql_session_store/lib/sql_session.rb @@ -0,0 +1,27 @@ +# An ActiveRecord class which corresponds to the database table +# +sessions+. Functions +find_session+, +create_session+, +# +update_session+ and +destroy+ constitute the interface to class +# +SqlSessionStore+. + +class SqlSession < ActiveRecord::Base + # this class should not be reloaded + def self.reloadable? + false + end + + # retrieve session data for a given +session_id+ from the database, + # return nil if no such session exists + def self.find_session(session_id) + find :first, :conditions => "session_id='#{session_id}'" + end + + # create a new session with given +session_id+ and +data+ + def self.create_session(session_id, data) + new(:session_id => session_id, :data => data) + end + + # update session data and store it in the database + def update_session(data) + update_attribute('data', data) + end +end diff --git a/vendor/plugins/sql_session_store/lib/sql_session_store.rb b/vendor/plugins/sql_session_store/lib/sql_session_store.rb new file mode 100755 index 000000000..8b0ff156f --- /dev/null +++ b/vendor/plugins/sql_session_store/lib/sql_session_store.rb @@ -0,0 +1,116 @@ +require 'active_record' +require 'cgi' +require 'cgi/session' +begin + require 'base64' +rescue LoadError +end + +# +SqlSessionStore+ is a stripped down, optimized for speed version of +# class +ActiveRecordStore+. + +class SqlSessionStore + + # The class to be used for creating, retrieving and updating sessions. + # Defaults to SqlSessionStore::Session, which is derived from +ActiveRecord::Base+. + # + # In order to achieve acceptable performance you should implement + # your own session class, similar to the one provided for Myqsl. + # + # Only functions +find_session+, +create_session+, + # +update_session+ and +destroy+ are required. See file +mysql_session.rb+. + + cattr_accessor :session_class + @@session_class = SqlSession + + # Create a new SqlSessionStore instance. + # + # +session+ is the session for which this instance is being created. + # + # +option+ is currently ignored as no options are recognized. + + def initialize(session, option=nil) + if @session = @@session_class.find_session(session.session_id) + @data = unmarshalize(@session.data) + else + @session = @@session_class.create_session(session.session_id, marshalize({})) + @data = {} + end + end + + # Update the database and disassociate the session object + def close + if @session + @session.update_session(marshalize(@data)) + @session = nil + end + end + + # Delete the current session, disassociate and destroy session object + def delete + if @session + @session.destroy + @session = nil + end + end + + # Restore session data from the session object + def restore + if @session + @data = unmarshalize(@session.data) + end + end + + # Save session data in the session object + def update + if @session + @session.update_session(marshalize(@data)) + end + end + + private + if defined?(Base64) + def unmarshalize(data) + Marshal.load(Base64.decode64(data)) + end + + def marshalize(data) + Base64.encode64(Marshal.dump(data)) + end + else + def unmarshalize(data) + Marshal.load(data.unpack("m").first) + end + + def marshalize(data) + [Marshal.dump(data)].pack("m") + end + end + +end + +__END__ + +# This software is released under the MIT license +# +# Copyright (c) 2005-2008 Stefan Kaes + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/plugins/sql_session_store/lib/sqlite_session.rb b/vendor/plugins/sql_session_store/lib/sqlite_session.rb new file mode 100755 index 000000000..822b23231 --- /dev/null +++ b/vendor/plugins/sql_session_store/lib/sqlite_session.rb @@ -0,0 +1,133 @@ +require 'sqlite3' + +# allow access to the real Sqlite connection +#class ActiveRecord::ConnectionAdapters::SQLiteAdapter +# attr_reader :connection +#end + +# SqliteSession is a down to the bare metal session store +# implementation to be used with +SQLSessionStore+. It is much faster +# than the default ActiveRecord implementation. +# +# The implementation assumes that the table column names are 'id', +# 'data', 'created_at' and 'updated_at'. If you want use other names, +# you will need to change the SQL statments in the code. + +class SqliteSession + + # if you need Rails components, and you have a pages which create + # new sessions, and embed components insides this pages that need + # session access, then you *must* set +eager_session_creation+ to + # true (as of Rails 1.0). + cattr_accessor :eager_session_creation + @@eager_session_creation = false + + attr_accessor :id, :session_id, :data + + def initialize(session_id, data) + @session_id = session_id + @data = data + @id = nil + end + + class << self + + # retrieve the session table connection and get the 'raw' Sqlite connection from it + def session_connection + SqlSession.connection.instance_variable_get(:@connection) + end + + # try to find a session with a given +session_id+. returns nil if + # no such session exists. note that we don't retrieve + # +created_at+ and +updated_at+ as they are not accessed anywhyere + # outside this class + def find_session(session_id) + connection = session_connection + session_id = SQLite3::Database.quote(session_id) + result = connection.execute("SELECT id, data FROM sessions WHERE `session_id`='#{session_id}' LIMIT 1") + my_session = nil + # each is used below, as other methods barf on my 64bit linux machine + # I suspect this to be a bug in sqlite-ruby + result.each do |row| + my_session = new(session_id, row[1]) + my_session.id = row[0] + end +# result.free + my_session + end + + # create a new session with given +session_id+ and +data+ + # and save it immediately to the database + def create_session(session_id, data) + session_id = SQLite3::Database.quote(session_id) + new_session = new(session_id, data) + if @@eager_session_creation + connection = session_connection + connection.execute("INSERT INTO sessions ('id', `created_at`, `updated_at`, `session_id`, `data`) VALUES (NULL, datetime('now'), datetime('now'), '#{session_id}', '#{SQLite3::Database.quote(data)}')") + new_session.id = connection.last_insert_row_id() + end + new_session + end + + # delete all sessions meeting a given +condition+. it is the + # caller's responsibility to pass a valid sql condition + def delete_all(condition=nil) + if condition + session_connection.execute("DELETE FROM sessions WHERE #{condition}") + else + session_connection.execute("DELETE FROM sessions") + end + end + + end # class methods + + # update session with given +data+. + # unlike the default implementation using ActiveRecord, updating of + # column `updated_at` will be done by the database itself + def update_session(data) + connection = SqlSession.connection.instance_variable_get(:@connection) #self.class.session_connection + if @id + # if @id is not nil, this is a session already stored in the database + # update the relevant field using @id as key + connection.execute("UPDATE sessions SET `updated_at`=datetime('now'), `data`='#{SQLite3::Database.quote(data)}' WHERE id=#{@id}") + else + # if @id is nil, we need to create a new session in the database + # and set @id to the primary key of the inserted record + connection.execute("INSERT INTO sessions ('id', `created_at`, `updated_at`, `session_id`, `data`) VALUES (NULL, datetime('now'), datetime('now'), '#{@session_id}', '#{SQLite3::Database.quote(data)}')") + @id = connection.last_insert_row_id() + end + end + + # destroy the current session + def destroy + connection = SqlSession.connection.instance_variable_get(:@connection) + connection.execute("delete from sessions where session_id='#{session_id}'") + end + +end + +__END__ + +# This software is released under the MIT license +# +# Copyright (c) 2005-2008 Stefan Kaes +# Copyright (c) 2006-2008 Ted X Toth + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.