voraz-dr_nic_magic_models 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGELOG +9 -0
  2. data/History.txt +33 -0
  3. data/Manifest.txt +56 -0
  4. data/README +0 -0
  5. data/Rakefile +149 -0
  6. data/install.rb +30 -0
  7. data/lib/base.rb +12 -0
  8. data/lib/connection_adapters/abstract/schema_statements.rb +0 -0
  9. data/lib/connection_adapters/abstract_adapter.rb +32 -0
  10. data/lib/connection_adapters/mysql_adapter.rb +42 -0
  11. data/lib/connection_adapters/postgresql_adapter.rb +45 -0
  12. data/lib/dr_nic_magic_models/inflector.rb +14 -0
  13. data/lib/dr_nic_magic_models/magic_model.rb +135 -0
  14. data/lib/dr_nic_magic_models/schema.rb +270 -0
  15. data/lib/dr_nic_magic_models/validations.rb +46 -0
  16. data/lib/dr_nic_magic_models/version.rb +9 -0
  17. data/lib/dr_nic_magic_models.rb +34 -0
  18. data/lib/module.rb +33 -0
  19. data/lib/rails.rb +19 -0
  20. data/scripts/txt2html +66 -0
  21. data/scripts/txt2js +58 -0
  22. data/test/abstract_unit.rb +70 -0
  23. data/test/connections/native_mysql/connection.rb +14 -0
  24. data/test/connections/native_postgresql/connection.rb +12 -0
  25. data/test/connections/native_sqlite/connection.rb +10 -0
  26. data/test/dummy_test.rb +13 -0
  27. data/test/env_test.rb +10 -0
  28. data/test/fixtures/.DS_Store +0 -0
  29. data/test/fixtures/adjectives.yml +3 -0
  30. data/test/fixtures/adjectives_fun_users.yml +3 -0
  31. data/test/fixtures/db_definitions/mysql.drop.sql +4 -0
  32. data/test/fixtures/db_definitions/mysql.sql +58 -0
  33. data/test/fixtures/db_definitions/postgresql.sql +56 -0
  34. data/test/fixtures/db_definitions/sqlite.sql +49 -0
  35. data/test/fixtures/fun_users.yml +14 -0
  36. data/test/fixtures/group_memberships.yml +4 -0
  37. data/test/fixtures/group_tag.yml +11 -0
  38. data/test/fixtures/groups.yml +12 -0
  39. data/test/foreign_keys_test.rb +0 -0
  40. data/test/fun_user_plus.rb +2 -0
  41. data/test/invisible_model_access_test.rb +71 -0
  42. data/test/invisible_model_assoc_test.rb +61 -0
  43. data/test/invisible_model_classes_test.rb +23 -0
  44. data/test/magic_module_test.rb +20 -0
  45. data/test/test_existing_model.rb +20 -0
  46. data/test.db +0 -0
  47. data/website/index.html +409 -0
  48. data/website/index.txt +291 -0
  49. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  50. data/website/stylesheets/screen.css +106 -0
  51. data/website/template.js +3 -0
  52. data/website/template.rhtml +55 -0
  53. data/website/version-raw.js +3 -0
  54. data/website/version-raw.txt +2 -0
  55. data/website/version.js +4 -0
  56. data/website/version.txt +3 -0
  57. metadata +123 -0
data/CHANGELOG ADDED
@@ -0,0 +1,9 @@
1
+ * 0.2.5 * - Initial public release
2
+ - ActiveRecords can now be auto-created in memory when first referenced
3
+ from their table name, without an explicit class definition.
4
+ - ActiveRecords will automatically include validates_presence_of on
5
+ each field with :null => false
6
+ - ActiveRecords will automatically generate simple has_many, has_one,
7
+ belongs_to assocations based upon assumed foreign keys. E.g.
8
+ foreign key to products table is assumed to be product_id.
9
+
data/History.txt ADDED
@@ -0,0 +1,33 @@
1
+ == TRUNK 2007-06-30
2
+
3
+ * Allows ActiveRecord::StatementInvalid for attempts to find pluralised tables that don't exist
4
+ * Refactored Rakefile into namespaces by database
5
+
6
+ == 0.9.2 / 2007-4-30
7
+
8
+ * 3 major bugfixes:
9
+ * #generate_validations now works if you haven't already created a connection to the database; previously
10
+ validations wouldn't get created until you had already established the connection; now it does it for
11
+ you if its not already established
12
+ * Associations can be generated via the assignment methods, e.g. @membership.group= will generate the "belongs_to :group" association now. This allows the website tutorial to work correctly! Yay. That is, you can now do: Membership.create(:person => person, :group => group)
13
+ * has_many's should work now
14
+
15
+ == 0.9.1 / 2007-4-11
16
+
17
+ * 1 minor enhancement:
18
+ * ActiveRecord::Base includes all the magic model functionality via the MagicModel module
19
+ * Existing ARs can get magic validation via #generate_validations call
20
+ * Website tutorial works :D
21
+
22
+ == 0.9.0 / 2007-4-9
23
+
24
+ * 1 major enhancement:
25
+ * Support for dynamic loading of classes again
26
+ * 2 new DB supported:
27
+ * Tests run on sqlite (no fk support)
28
+ * Tests run on postgresql (fk support)
29
+ * Including FK bug fix
30
+ * Many fixes that I've lost track of
31
+ * History.txt to keep track of changes like these
32
+ * Using Hoe for Rakefile
33
+ * Use modules to specify common table prefixes
data/Manifest.txt ADDED
@@ -0,0 +1,56 @@
1
+ CHANGELOG
2
+ History.txt
3
+ Manifest.txt
4
+ README
5
+ Rakefile
6
+ install.rb
7
+ lib/base.rb
8
+ lib/connection_adapters/abstract/schema_statements.rb
9
+ lib/connection_adapters/abstract_adapter.rb
10
+ lib/connection_adapters/mysql_adapter.rb
11
+ lib/connection_adapters/postgresql_adapter.rb
12
+ lib/dr_nic_magic_models.rb
13
+ lib/dr_nic_magic_models/inflector.rb
14
+ lib/dr_nic_magic_models/magic_model.rb
15
+ lib/dr_nic_magic_models/schema.rb
16
+ lib/dr_nic_magic_models/validations.rb
17
+ lib/dr_nic_magic_models/version.rb
18
+ lib/module.rb
19
+ lib/rails.rb
20
+ scripts/txt2html
21
+ scripts/txt2js
22
+ test.db
23
+ test/abstract_unit.rb
24
+ test/connections/native_mysql/connection.rb
25
+ test/connections/native_postgresql/connection.rb
26
+ test/connections/native_sqlite/connection.rb
27
+ test/dummy_test.rb
28
+ test/env_test.rb
29
+ test/fixtures/.DS_Store
30
+ test/fixtures/adjectives.yml
31
+ test/fixtures/adjectives_fun_users.yml
32
+ test/fixtures/db_definitions/mysql.drop.sql
33
+ test/fixtures/db_definitions/mysql.sql
34
+ test/fixtures/db_definitions/postgresql.sql
35
+ test/fixtures/db_definitions/sqlite.sql
36
+ test/fixtures/fun_users.yml
37
+ test/fixtures/group_memberships.yml
38
+ test/fixtures/group_tag.yml
39
+ test/fixtures/groups.yml
40
+ test/foreign_keys_test.rb
41
+ test/fun_user_plus.rb
42
+ test/invisible_model_access_test.rb
43
+ test/invisible_model_assoc_test.rb
44
+ test/invisible_model_classes_test.rb
45
+ test/magic_module_test.rb
46
+ test/test_existing_model.rb
47
+ website/index.html
48
+ website/index.txt
49
+ website/javascripts/rounded_corners_lite.inc.js
50
+ website/stylesheets/screen.css
51
+ website/template.js
52
+ website/template.rhtml
53
+ website/version-raw.js
54
+ website/version-raw.txt
55
+ website/version.js
56
+ website/version.txt
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,149 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/packagetask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'hoe'
10
+ require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models', 'version')
11
+
12
+ AUTHOR = "nicwilliams" # can also be an array of Authors
13
+ EMAIL = "drnicwilliams@gmail.com"
14
+ DESCRIPTION = "Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!"
15
+ GEM_NAME = "dr_nic_magic_models" # what ppl will type to install your gem
16
+ RUBYFORGE_PROJECT = "magicmodels" # The unix name for your project
17
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
+
19
+
20
+ NAME = "magic_multi_connections"
21
+ REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
22
+ VERS = ENV['VERSION'] || (DrNicMagicModels::VERSION::STRING + (REV ? ".#{REV}" : ""))
23
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
24
+ RDOC_OPTS = ['--quiet', '--title', "dr_nic_magic_models documentation",
25
+ "--opname", "index.html",
26
+ "--line-numbers",
27
+ "--main", "README",
28
+ "--inline-source"]
29
+
30
+ class Hoe
31
+ def extra_deps
32
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
33
+ end
34
+ end
35
+
36
+ # Generate all the Rake tasks
37
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
38
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
39
+ p.author = AUTHOR
40
+ p.description = DESCRIPTION
41
+ p.email = EMAIL
42
+ p.summary = DESCRIPTION
43
+ p.url = HOMEPATH
44
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
45
+ p.test_globs = ["test/**/test_*.rb"]
46
+ p.clean_globs = CLEAN #An array of file patterns to delete on clean.
47
+
48
+ # == Optional
49
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
50
+
51
+ #p.extra_deps - An array of rubygem dependencies.
52
+ #p.spec_extras - A hash of extra values to set in the gemspec.
53
+ end
54
+
55
+ # Run the unit tests
56
+ ADAPTERS = %w( sqlite mysql postgresql ) # UNTESTED - postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase )
57
+ for adapter in ADAPTERS
58
+ Rake::TestTask.new("test_#{adapter}") { |t|
59
+ t.libs << "test" << "test/connections/native_#{adapter}"
60
+ t.pattern = "test/*_test{,_#{adapter}}.rb"
61
+ t.verbose = true
62
+ }
63
+ end
64
+
65
+ task :default do
66
+ puts "To run tests, call the explicit test_<adapter> task"
67
+ end
68
+
69
+ SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
70
+
71
+ namespace :mysql do
72
+ desc 'Build the MySQL test databases'
73
+ task :build_databases do
74
+ puts File.join(SCHEMA_PATH, 'mysql.sql')
75
+ user = 'root'
76
+ sh %{ mysqladmin -u #{user} -p create "#{GEM_NAME}_unittest" }
77
+ sh %{ mysql -u #{user} -p "#{GEM_NAME}_unittest" < #{File.join(SCHEMA_PATH, 'mysql.sql')} }
78
+ end
79
+
80
+ desc 'Drop the MySQL test databases'
81
+ task :drop_databases do
82
+ user = 'root'
83
+ sh %{ mysqladmin -u #{user} -p -f drop "#{GEM_NAME}_unittest" }
84
+ end
85
+
86
+ desc 'Rebuild the MySQL test databases'
87
+
88
+ task :rebuild_databases => [:drop_databases, :build_databases]
89
+
90
+ task :test => :test_mysql
91
+ end
92
+
93
+ namespace :sqlite do
94
+ desc 'Build the sqlite test databases'
95
+ task :build_databases do
96
+ file = File.join(SCHEMA_PATH, 'sqlite.sql')
97
+ cmd = "sqlite3 test.db < #{file}"
98
+ puts cmd
99
+ sh %{ #{cmd} }
100
+ end
101
+
102
+ desc 'Drop the sqlite test databases'
103
+ task :drop_databases do
104
+ sh %{ rm -f test.db }
105
+ end
106
+
107
+ desc 'Rebuild the sqlite test databases'
108
+ task :rebuild_databases => [:drop_databases, :build_databases]
109
+
110
+ task :test => :test_sqlite
111
+ end
112
+
113
+ namespace :postgresql do
114
+ desc 'Build the PostgreSQL test databases'
115
+ task :build_databases do
116
+ sh %{ createdb "#{GEM_NAME}_unittest" }
117
+ sh %{ psql "#{GEM_NAME}_unittest" -f #{File.join(SCHEMA_PATH, 'postgresql.sql')} }
118
+ end
119
+
120
+ desc 'Drop the PostgreSQL test databases'
121
+ task :drop_databases do
122
+ sh %{ dropdb "#{GEM_NAME}_unittest" }
123
+ end
124
+
125
+ desc 'Rebuild the PostgreSQL test databases'
126
+ task :rebuild_databases => [:drop_databases, :build_databases]
127
+
128
+ task :test => :test_postgresql
129
+ end
130
+
131
+
132
+ desc 'Generate website files'
133
+ task :website_generate do
134
+ sh %{ ruby scripts/txt2html website/index.txt > website/index.html }
135
+ sh %{ ruby scripts/txt2js website/version.txt > website/version.js }
136
+ sh %{ ruby scripts/txt2js website/version-raw.txt > website/version-raw.js }
137
+ end
138
+
139
+ desc 'Upload website files to rubyforge'
140
+ task :website_upload do
141
+ config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
142
+ host = "#{config["username"]}@rubyforge.org"
143
+ remote_dir = "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
144
+ local_dir = 'website'
145
+ sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
146
+ end
147
+
148
+ desc 'Generate and upload website files'
149
+ task :website => [:website_generate, :website_upload]
data/install.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ # this was adapted from rdoc's install.rb by ways of Log4r
8
+
9
+ $sitedir = CONFIG["sitelibdir"]
10
+ unless $sitedir
11
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
12
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
13
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
14
+ if !$sitedir
15
+ $sitedir = File.join($libdir, "site_ruby")
16
+ elsif $sitedir !~ Regexp.quote(version)
17
+ $sitedir = File.join($sitedir, version)
18
+ end
19
+ end
20
+
21
+ # the acual gruntwork
22
+ Dir.chdir("lib")
23
+
24
+ Find.find("dr_nic_magic_models", "dr_nic_magic_models.rb") { |f|
25
+ if f[-3..-1] == ".rb"
26
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
27
+ else
28
+ File::makedirs(File.join($sitedir, *f.split(/\//)))
29
+ end
30
+ }
data/lib/base.rb ADDED
@@ -0,0 +1,12 @@
1
+ #TODO: Use :dependent for FK cascade?
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ class << self
6
+ public
7
+ def get_unique_index_columns
8
+ self.connection.indexes(self.table_name, "#{self.name} Indexes").select { |index| index.unique && index.columns.size == 1 }.map{ |index| index.columns.first }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module ActiveRecord
3
+ module ConnectionAdapters # :nodoc:
4
+
5
+ # Generic holder for foreign key constraint meta-data from the database schema.
6
+ class ForeignKeyConstraint < Struct.new(:name, :table, :foreign_key, :reference_table, :reference_column, :on_update, :on_delete)
7
+ end
8
+
9
+ class AbstractAdapter
10
+
11
+ # Does this adapter support the ability to fetch foreign key information?
12
+ # Backend specific, as the abstract adapter always returns +false+.
13
+ def supports_fetch_foreign_keys?
14
+ false
15
+ end
16
+
17
+ def foreign_key_constraints(table, name = nil)
18
+ raise NotImplementedError, "foreign_key_constraints is not implemented for #{self.class}"
19
+ end
20
+
21
+ def remove_foreign_key_constraint(table_name, constraint_name)
22
+ raise NotImplementedError, "rename_table is not implemented for #{self.class}"
23
+ end
24
+
25
+ protected
26
+ def symbolize_foreign_key_constraint_action(constraint_action)
27
+ return nil if constraint_action.nil?
28
+ constraint_action.downcase.gsub(/\s/, '_').to_sym
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ # Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class MysqlAdapter < AbstractAdapter
6
+ def supports_fetch_foreign_keys?
7
+ true
8
+ end
9
+
10
+ def foreign_key_constraints(table, name = nil)
11
+ constraints = []
12
+ execute("SHOW CREATE TABLE #{table}", name).each do |row|
13
+ row[1].each do |create_line|
14
+ if create_line.strip =~ /CONSTRAINT `([^`]+)` FOREIGN KEY \(`([^`]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)([^,]*)/
15
+ constraint = ForeignKeyConstraint.new(Regexp.last_match(1), table, Regexp.last_match(2), Regexp.last_match(3), Regexp.last_match(4), nil, nil)
16
+
17
+ constraint_params = {}
18
+
19
+ unless Regexp.last_match(5).nil?
20
+ Regexp.last_match(5).strip.split('ON ').each do |param|
21
+ constraint_params[Regexp.last_match(1).upcase] = Regexp.last_match(2).strip.upcase if param.strip =~ /([^ ]+) (.+)/
22
+ end
23
+ end
24
+
25
+ constraint.on_update = symbolize_foreign_key_constraint_action(constraint_params['UPDATE']) if constraint_params.include? 'UPDATE'
26
+ constraint.on_delete = symbolize_foreign_key_constraint_action(constraint_params['DELETE']) if constraint_params.include? 'DELETE'
27
+
28
+ constraints << constraint
29
+ end
30
+ end
31
+ end
32
+
33
+ constraints
34
+ end
35
+
36
+ def remove_foreign_key_constraint(table_name, constraint_name)
37
+ execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{constraint_name}"
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ # Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class PostgreSQLAdapter < AbstractAdapter
6
+
7
+ def supports_fetch_foreign_keys?
8
+ true
9
+ end
10
+
11
+ def foreign_key_constraints(table, name = nil)
12
+
13
+
14
+ sql = "SELECT conname, pg_catalog.pg_get_constraintdef(oid) AS consrc FROM pg_catalog.pg_constraint WHERE contype='f' "
15
+ sql += "AND conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='#{table}')"
16
+
17
+ result = query(sql, name)
18
+
19
+ keys = []
20
+ re = /(?i)^FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)(?: ON UPDATE (\w+))?(?: ON DELETE (\w+))?$/
21
+ result.each do |row|
22
+ # pg_catalog.pg_get_constraintdef returns a string like this:
23
+ # FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
24
+ if match = re.match(row[1])
25
+
26
+ keys << ForeignKeyConstraint.new(row[0],
27
+ table,
28
+ match[1],
29
+ match[2],
30
+ match[3],
31
+ symbolize_foreign_key_constraint_action(match[4]),
32
+ symbolize_foreign_key_constraint_action(match[5]))
33
+ end
34
+ end
35
+
36
+ keys
37
+ end
38
+
39
+ def remove_foreign_key_constraint(table_name, constraint_name)
40
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}"
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ module DrNicMagicModels
2
+ class Inflector
3
+ def table_names ; DrNicMagicModels::Schema.table_names; end
4
+ def tables ; DrNicMagicModels::Schema.tables; end
5
+ def models ; DrNicMagicModels::Schema.model; end
6
+
7
+ def class_name(table_name)
8
+ ActiveRecord::Base.class_name(table_name)
9
+ end
10
+
11
+ def post_class_creation(klass)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,135 @@
1
+ # Mixed into a class that is dynamically created, unless
2
+ # the class was created by the Schema.load_schema process
3
+ # which builds the whole class, thus no magicalness is
4
+ # needed
5
+ module DrNicMagicModels::MagicModel
6
+ def self.append_features(base)
7
+ super
8
+ base.send(:include, InstanceMethods)
9
+ class << base
10
+ # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
11
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
12
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
13
+ def reflect_on_association(association)
14
+ unless reflections[association]
15
+ # See if an assocation can be generated
16
+ self.new.send(association) rescue nil
17
+ end
18
+ reflections[association].is_a?(ActiveRecord::Reflection::AssociationReflection) ? reflections[association] : nil
19
+ end
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+
25
+ def method_missing(method, *args, &block)
26
+ begin
27
+ super
28
+ rescue
29
+ if unknown_method? method
30
+ result = find_belongs_to method, *args, &block
31
+ result = find_has_some method, *args, &block if not result
32
+ result = find_has_some_indirect method, *args, &block if not result
33
+ return result if result
34
+ end
35
+ add_known_unknown method
36
+ raise
37
+ end
38
+ end
39
+
40
+ def add_known_unknown(method)
41
+ @known_unknowns ||= {}
42
+ @known_unknowns[method] = true
43
+ end
44
+
45
+ def unknown_method?(method)
46
+ @known_unknowns.nil? or @known_unknowns.include? method
47
+ end
48
+
49
+ def find_belongs_to(method, *args, &block)
50
+ method_clean = clean_method method
51
+ fkc =
52
+ begin
53
+ self.class.connection.foreign_key_constraints(self.class.table_name, method_clean)
54
+ rescue NotImplementedError
55
+ nil
56
+ end
57
+ if !fkc.nil? && fkc.length > 0
58
+ foreign_key = fkc.first.foreign_key
59
+ options = {:dependent => :destroy,
60
+ :foreign_key => fkc.first.foreign_key,
61
+ :class_name => self.class.class_name(fkc.first.reference_table)}
62
+ else
63
+ foreign_key = self.class.columns.select {|column| column.name == method_clean.to_s.foreign_key}.first
64
+ end
65
+ options ||= {}
66
+ return add_belongs_to(method, method_clean, options, *args, &block) if foreign_key
67
+ end
68
+
69
+ def add_belongs_to(method, method_clean, options, *args, &block)
70
+ self.class.send 'belongs_to', method_clean.to_sym, options rescue puts $!
71
+ self.send(method, *args, &block)
72
+ end
73
+
74
+ def find_has_some(method, *args, &block)
75
+ method_clean = clean_method method
76
+ fkc = [method_clean.to_s.pluralize, method_clean.to_s.singularize].inject({}) do |pair, table_name|
77
+ fkc = begin
78
+ self.class.connection.foreign_key_constraints(table_name)
79
+ rescue NotImplementedError
80
+ nil
81
+ rescue ActiveRecord::StatementInvalid
82
+ nil
83
+ end
84
+ pair[table_name] = fkc if not fkc.blank?
85
+ pair
86
+ end
87
+ if not fkc.blank?
88
+ # assumes there is only one table found - that schema doesn't have a singular and plural table of same name
89
+ foreign_key = fkc.values.first.find {|fk| fk.reference_table == self.class.table_name}
90
+ if foreign_key
91
+ foreign_key = foreign_key.foreign_key
92
+ table_name = fkc.keys.first
93
+ klass = Module.const_get table_name.singularize.camelize rescue nil
94
+ options = {:foreign_key => foreign_key, :class_name => klass.name}
95
+ end
96
+ end
97
+ unless foreign_key
98
+ klass = Module.const_get method_clean.to_s.downcase.singularize.camelize rescue nil
99
+ foreign_key = klass.columns.select {|column| column.name == self.class.name.foreign_key}.first if klass
100
+ end
101
+ options ||= {}
102
+ return add_has_some(method, method_clean, options, *args, &block) if foreign_key
103
+ end
104
+
105
+ def add_has_some(method, method_clean, options, *args, &block)
106
+ association = method_clean.singularize == method_clean ? 'has_one' : 'has_many'
107
+ self.class.send association, method_clean.to_sym, options rescue puts $!
108
+ self.send(method, *args, &block)
109
+ end
110
+
111
+ def find_has_some_indirect(method, *args, &block)
112
+ klass = Module.const_get method.to_s.downcase.singularize.camelize rescue return
113
+ join_table = nil
114
+ self.connection.tables.each do |table|
115
+ unless [self.class.table_name, klass.table_name].include? table
116
+ columns = self.connection.columns(table).map(&:name)
117
+ join_table = table if columns.include?(self.class.to_s.foreign_key) and columns.include?(klass.to_s.foreign_key)
118
+ end
119
+ break if join_table
120
+ end
121
+ return add_has_some_through(join_table, method, *args, &block) if join_table
122
+ end
123
+
124
+ def add_has_some_through(join_table, method, *args, &block)
125
+ self.class.send 'has_many', method, :through => join_table.to_sym
126
+ self.send(method, *args, &block)
127
+ end
128
+
129
+ private
130
+
131
+ def clean_method(method)
132
+ method.to_s.gsub(/=$/,'') # remove any = from the end of the method name
133
+ end
134
+ end
135
+ end