schema_plus 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +25 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +25 -0
- data/README.rdoc +147 -0
- data/Rakefile +70 -0
- data/init.rb +1 -0
- data/lib/schema_plus/active_record/associations.rb +211 -0
- data/lib/schema_plus/active_record/base.rb +81 -0
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +96 -0
- data/lib/schema_plus/active_record/connection_adapters/column.rb +55 -0
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +115 -0
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +51 -0
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +111 -0
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +163 -0
- data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +39 -0
- data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +78 -0
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +130 -0
- data/lib/schema_plus/active_record/migration.rb +220 -0
- data/lib/schema_plus/active_record/schema.rb +27 -0
- data/lib/schema_plus/active_record/schema_dumper.rb +122 -0
- data/lib/schema_plus/active_record/validations.rb +139 -0
- data/lib/schema_plus/railtie.rb +12 -0
- data/lib/schema_plus/version.rb +3 -0
- data/lib/schema_plus.rb +248 -0
- data/schema_plus.gemspec +37 -0
- data/schema_plus.gemspec.rails3.0 +36 -0
- data/schema_plus.gemspec.rails3.1 +36 -0
- data/spec/association_spec.rb +529 -0
- data/spec/connections/mysql/connection.rb +18 -0
- data/spec/connections/mysql2/connection.rb +18 -0
- data/spec/connections/postgresql/connection.rb +15 -0
- data/spec/connections/sqlite3/connection.rb +14 -0
- data/spec/foreign_key_definition_spec.rb +23 -0
- data/spec/foreign_key_spec.rb +142 -0
- data/spec/index_definition_spec.rb +139 -0
- data/spec/index_spec.rb +71 -0
- data/spec/migration_spec.rb +405 -0
- data/spec/models/comment.rb +2 -0
- data/spec/models/post.rb +2 -0
- data/spec/models/user.rb +2 -0
- data/spec/references_spec.rb +78 -0
- data/spec/schema/auto_schema.rb +23 -0
- data/spec/schema/core_schema.rb +21 -0
- data/spec/schema_dumper_spec.rb +167 -0
- data/spec/schema_spec.rb +71 -0
- data/spec/spec_helper.rb +59 -0
- data/spec/support/extensions/active_model.rb +13 -0
- data/spec/support/helpers.rb +16 -0
- data/spec/support/matchers/automatic_foreign_key_matchers.rb +2 -0
- data/spec/support/matchers/have_index.rb +52 -0
- data/spec/support/matchers/reference.rb +66 -0
- data/spec/support/reference.rb +66 -0
- data/spec/validations_spec.rb +294 -0
- data/spec/views_spec.rb +140 -0
- metadata +269 -0
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
.*.sw?
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
|
21
|
+
## PROJECT::SPECIFIC
|
22
|
+
.rvmrc
|
23
|
+
*.log
|
24
|
+
*.sqlite3
|
25
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2006 RedHill Consulting, Pty. Ltd.
|
2
|
+
Copyright (c) 2009 Michal Lomnicki & Ronen Barzel
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
Except as contained in this notice, the name(s) of the above copyright
|
16
|
+
holders shall not be used in advertising or otherwise to promote the sale,
|
17
|
+
use or other dealings in this Software without prior written authorization.
|
18
|
+
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
22
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
23
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
24
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
= SchemaPlus
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
SchemaPlus is an ActiveRecord extension that provides enhanced capabilities for schema definition and querying, including: enhanced and more DRY index capabilities, support and automation for foreign key constraints, and support for views.
|
6
|
+
|
7
|
+
For added rails DRYness see also the gems {+schema_associations+}[http://rubygems.org/gems/schema_associations] and {+schema_validations+}[http://rubygems.org/gems/schema_associations] <b>IMPORTANT PRERELEASE NOTE: <i>Those are not yet separate gems, they are currently bundled in here. They're not documented yet though.</i></b>
|
8
|
+
|
9
|
+
== Compatibility
|
10
|
+
|
11
|
+
SchemaPlus supports all combinations of:
|
12
|
+
* rails 3.0 or 3.1
|
13
|
+
* MRI ruby 1.8.7 or 1.9.2
|
14
|
+
* Postgres, MySQL (using mysql or mysql2 gem), or Sqlite3
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
Install from http://rubygems.org via
|
19
|
+
|
20
|
+
$ gem install "schema_plus"
|
21
|
+
|
22
|
+
or in a Gemfile
|
23
|
+
|
24
|
+
gem "schema_plus"
|
25
|
+
|
26
|
+
== Features
|
27
|
+
|
28
|
+
Here some examples that show off the high points. For full details see the RDoc documentation.
|
29
|
+
|
30
|
+
=== Indexes
|
31
|
+
|
32
|
+
With standard rails migrations, you specify indexes separately from the table definition:
|
33
|
+
|
34
|
+
# Standard Rails approach...
|
35
|
+
create_table :parts do |t|
|
36
|
+
t.string :name
|
37
|
+
t.string :product_code
|
38
|
+
end
|
39
|
+
|
40
|
+
add_index :parts, :name # index repeats table and column names and is defined separately
|
41
|
+
add_index :parts, :product_code, :unique => true
|
42
|
+
|
43
|
+
But with SchemaPlus rather than specify your outside your table definition you can specify your indexes when you define each column:
|
44
|
+
|
45
|
+
# More DRY way...
|
46
|
+
create_table :parts do |t|
|
47
|
+
t.string :name, :index => true
|
48
|
+
t.string :product_code, :index => :unique
|
49
|
+
end
|
50
|
+
|
51
|
+
Options can be provided index using a hash, for example:
|
52
|
+
|
53
|
+
t.string :product_code, :index => { :unique => true, :name => "my_index_name" }
|
54
|
+
|
55
|
+
You can also create multi-column indexes, for example:
|
56
|
+
|
57
|
+
t.string :first_name
|
58
|
+
t.string :last_name, :index => { :with => :first_name }
|
59
|
+
|
60
|
+
t.string :country_code
|
61
|
+
t.string :area_code
|
62
|
+
t.string :local_number :index => { :width => [:country_code, :area_code], :unique => true }
|
63
|
+
|
64
|
+
If you're using Postgresql, SchemaPlus provides support for conditions, expressions, index methods, and case-insensitive indexes; see doc at SchemaPlus::ActiveRecord::ConnectionAdapters::PostgresqlAdapter and SchemaPlus::ActiveRecord::ConnectionAdapters::IndexDefinition
|
65
|
+
|
66
|
+
And when you query column information using ActiveRecord::Base#columns, SchemaPlus analogously provides index information relevant to each column: which indexes reference the column, whether the column must be unique, etc. See doc at SchemaPlus::ActiveRecord::ConnectionAdapters::Column
|
67
|
+
|
68
|
+
=== Foreign Key Constraints
|
69
|
+
|
70
|
+
SchemaPlus adds support for foreign key constraints. In fact, for the
|
71
|
+
common convention that you name a column with suffix +_id+ to indicate that
|
72
|
+
it's a foreign key, SchemaPlus automatically defines the appropriate
|
73
|
+
constraint.
|
74
|
+
|
75
|
+
You can explicitly specify foreign key constraints, or override the
|
76
|
+
automatic ones, using the +:references+ option to specify the table
|
77
|
+
name (and optionally that table's key column name, if it's not +id+).
|
78
|
+
|
79
|
+
Here are some examples:
|
80
|
+
|
81
|
+
t.integer :author_id # automatically references table 'authors', key id
|
82
|
+
t.integer :parent_id # special name parent_id automatically references its own table (for tree nodes)
|
83
|
+
t.integer :author, :references => :authors # non-conventional column name needs :references for a constraint
|
84
|
+
t.integer :author_id, :refences => :authors # same as automatic behavior
|
85
|
+
t.integer :author_id, :refences => [:authors, :id] # same as automatic behavior
|
86
|
+
t.integer :author_id, :references => :people # override table name
|
87
|
+
t.integer :author_id, :references => [:people, :ssn] # override table name and key
|
88
|
+
t.integer :author_id, :referencs => nil # don't create a constraint
|
89
|
+
|
90
|
+
|
91
|
+
You can also modify the behavior using +:on_delete+, +:on_update+, and +:deferrable+
|
92
|
+
|
93
|
+
t.integer :author_id, :on_delete => :cascade
|
94
|
+
|
95
|
+
The foreign key behavior can be configured globally (see Config) or per-table (see create_table).
|
96
|
+
|
97
|
+
To examine your foreign key constraints, connection.foreign_keys returns a
|
98
|
+
list of foreign key constraints defined for a given table, and
|
99
|
+
connection.reverse_foreign_keys returns a list of foreign key constraints
|
100
|
+
that reference a given table. See SchemaPlus::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.
|
101
|
+
|
102
|
+
=== Views
|
103
|
+
|
104
|
+
SchemaPlus provides support for creating and dropping views. For example:
|
105
|
+
|
106
|
+
create_view :uncommented_posts, "SELECT * FROM posts LEFT OUTER JOIN comments ON comments.post_id = posts.id WHERE comments.id IS NULL"
|
107
|
+
drop_view :uncommented_posts
|
108
|
+
|
109
|
+
ActiveRecord works with views the same as with ordinary tables. That is, for the above view you can define
|
110
|
+
|
111
|
+
class UncommentedPosts < ActiveRecord::Base
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
== History
|
116
|
+
|
117
|
+
* SchemaPlus is derived from several "Red Hill On Rails" plugins
|
118
|
+
originally created by harukizaemon (https://github.com/harukizaemon)
|
119
|
+
with later contributions from
|
120
|
+
* Michał Łomnicki (https://github.com/mlomnicki)
|
121
|
+
* François Beausoleil (https://github.com/francois)
|
122
|
+
* Greg Barnett (https://github.com/greg-barnett)
|
123
|
+
* Ronen Barzel (https://github.com/ronen)
|
124
|
+
|
125
|
+
* SchemaPlus was created in 2011 by Michal Lomnicki and Ronen Barzel
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
== Testing
|
130
|
+
|
131
|
+
SchemaPlus is tested using rspec. To run the tests, after you've forked & cloned: Make sure you have Postgresql and MySQL running. Create database user "schema_plus" with permissions for database "schema_plus_unittest". Then:
|
132
|
+
|
133
|
+
$ cd schema_plus
|
134
|
+
$ bundle install
|
135
|
+
$ rake postgresql:build_databases
|
136
|
+
$ rake mysql:build_databases
|
137
|
+
$ rake postgresql:spec # to run postgresql tests only
|
138
|
+
$ rake mysql:spec # to run mysql tests only
|
139
|
+
$ rake mysql2:spec # to run mysql2 tests only
|
140
|
+
$ rake sqlite3:spec # to run sqlite3 tests only
|
141
|
+
$ rake spec # run all tests
|
142
|
+
|
143
|
+
If you're running ruby 1.9.2, code coverage results will be in coverage/index.html -- it should be at 100% coverage.
|
144
|
+
|
145
|
+
== License
|
146
|
+
|
147
|
+
This plugin is released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
%w[postgresql mysql mysql2 sqlite3].each do |adapter|
|
6
|
+
namespace adapter do
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
8
|
+
spec.rspec_opts = "-Ispec/connections/#{adapter}"
|
9
|
+
spec.fail_on_error = false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Run postgresql, mysql2 and sqlite3 tests'
|
15
|
+
task :spec do
|
16
|
+
%w[postgresql mysql mysql2 sqlite3].each do |adapter|
|
17
|
+
Rake::Task["#{adapter}:spec"].invoke
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :spec
|
22
|
+
|
23
|
+
require 'rake/rdoctask'
|
24
|
+
Rake::RDocTask.new do |rdoc|
|
25
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
26
|
+
|
27
|
+
rdoc.rdoc_dir = 'rdoc'
|
28
|
+
rdoc.title = "schema_plus #{version}"
|
29
|
+
rdoc.rdoc_files.include('README*')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :postgresql do
|
34
|
+
desc 'Build the PostgreSQL test databases'
|
35
|
+
task :build_databases do
|
36
|
+
%x( createdb -E UTF8 schema_plus_unittest )
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'Drop the PostgreSQL test databases'
|
40
|
+
task :drop_databases do
|
41
|
+
%x( dropdb schema_plus_unittest )
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Rebuild the PostgreSQL test databases'
|
45
|
+
task :rebuild_databases => [:drop_databases, :build_databases]
|
46
|
+
end
|
47
|
+
|
48
|
+
task :build_postgresql_databases => 'postgresql:build_databases'
|
49
|
+
task :drop_postgresql_databases => 'postgresql:drop_databases'
|
50
|
+
task :rebuild_postgresql_databases => 'postgresql:rebuild_databases'
|
51
|
+
|
52
|
+
MYSQL_DB_USER = 'schema_plus'
|
53
|
+
namespace :mysql do
|
54
|
+
desc 'Build the MySQL test databases'
|
55
|
+
task :build_databases do
|
56
|
+
%x( echo "create DATABASE schema_plus_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'Drop the MySQL test databases'
|
60
|
+
task :drop_databases do
|
61
|
+
%x( mysqladmin --user=#{MYSQL_DB_USER} -f drop schema_plus_unittest )
|
62
|
+
end
|
63
|
+
|
64
|
+
desc 'Rebuild the MySQL test databases'
|
65
|
+
task :rebuild_databases => [:drop_databases, :build_databases]
|
66
|
+
end
|
67
|
+
|
68
|
+
task :build_mysql_databases => 'mysql:build_databases'
|
69
|
+
task :drop_mysql_databases => 'mysql:drop_databases'
|
70
|
+
task :rebuild_mysql_databases => 'mysql:rebuild_databases'
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'schema_plus' unless defined?(SchemaPlus)
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module SchemaPlus
|
4
|
+
module ActiveRecord
|
5
|
+
module Associations
|
6
|
+
|
7
|
+
module Relation #:nodoc:
|
8
|
+
def self.included(base)
|
9
|
+
base.alias_method_chain :initialize, :schema_plus
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_with_schema_plus(klass, *args)
|
13
|
+
klass.send :_load_schema_plus_associations
|
14
|
+
initialize_without_schema_plus(klass, *args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.extended(base) #:nodoc:
|
19
|
+
class << base
|
20
|
+
alias_method_chain :reflect_on_association, :schema_plus
|
21
|
+
alias_method_chain :reflect_on_all_associations, :schema_plus
|
22
|
+
end
|
23
|
+
::ActiveRecord::Relation.send :include, Relation
|
24
|
+
end
|
25
|
+
|
26
|
+
def reflect_on_association_with_schema_plus(*args) #:nodoc:
|
27
|
+
_load_schema_plus_associations
|
28
|
+
reflect_on_association_without_schema_plus(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reflect_on_all_associations_with_schema_plus(*args) #:nodoc:
|
32
|
+
_load_schema_plus_associations
|
33
|
+
reflect_on_all_associations_without_schema_plus(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def define_attribute_methods(*args) #:nodoc:
|
37
|
+
super
|
38
|
+
_load_schema_plus_associations
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def _load_schema_plus_associations #:nodoc:
|
44
|
+
return if @schema_plus_associations_loaded
|
45
|
+
@schema_plus_associations_loaded = true
|
46
|
+
return unless schema_plus_config.associations.auto_create?
|
47
|
+
|
48
|
+
reverse_foreign_keys.each do | foreign_key |
|
49
|
+
if foreign_key.table_name =~ /^#{table_name}_(.*)$/ || foreign_key.table_name =~ /^(.*)_#{table_name}$/
|
50
|
+
other_table = $1
|
51
|
+
if other_table == other_table.pluralize and connection.columns(foreign_key.table_name).any?{|col| col.name == "#{other_table.singularize}_id"}
|
52
|
+
_define_association(:has_and_belongs_to_many, foreign_key, other_table)
|
53
|
+
else
|
54
|
+
_define_association(:has_one_or_many, foreign_key)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
_define_association(:has_one_or_many, foreign_key)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
foreign_keys.each do | foreign_key |
|
62
|
+
_define_association(:belongs_to, foreign_key)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def _define_association(macro, fk, referencing_table_name = nil) #:nodoc:
|
67
|
+
return unless fk.column_names.size == 1
|
68
|
+
|
69
|
+
referencing_table_name ||= fk.table_name
|
70
|
+
|
71
|
+
column_name = fk.column_names.first
|
72
|
+
reference_name = column_name.sub(/_id$/, '')
|
73
|
+
references_name = fk.references_table_name.singularize
|
74
|
+
referencing_name = referencing_table_name.singularize
|
75
|
+
|
76
|
+
references_class_name = references_name.classify
|
77
|
+
referencing_class_name = referencing_name.classify
|
78
|
+
|
79
|
+
references_concise = _concise_name(references_name, referencing_name)
|
80
|
+
referencing_concise = _concise_name(referencing_name, references_name)
|
81
|
+
|
82
|
+
case reference_name
|
83
|
+
when 'parent'
|
84
|
+
belongs_to = 'parent'
|
85
|
+
belongs_to_concise = 'parent'
|
86
|
+
|
87
|
+
has_one = 'child'
|
88
|
+
has_one_concise = 'child'
|
89
|
+
|
90
|
+
has_many = 'children'
|
91
|
+
has_many_concise = 'children'
|
92
|
+
|
93
|
+
when references_name
|
94
|
+
belongs_to = references_name
|
95
|
+
belongs_to_concise = references_concise
|
96
|
+
|
97
|
+
has_one = referencing_name
|
98
|
+
has_one_concise = referencing_concise
|
99
|
+
|
100
|
+
has_many = referencing_name.pluralize
|
101
|
+
has_many_concise = referencing_concise.pluralize
|
102
|
+
|
103
|
+
when /(.*)_#{references_name}$/, /(.*)_#{references_concise}$/
|
104
|
+
label = $1
|
105
|
+
belongs_to = "#{label}_#{references_name}"
|
106
|
+
belongs_to_concise = "#{label}_#{references_concise}"
|
107
|
+
|
108
|
+
has_one = "#{referencing_name}_as_#{label}"
|
109
|
+
has_one_concise = "#{referencing_concise}_as_#{label}"
|
110
|
+
|
111
|
+
has_many = "#{referencing_name.pluralize}_as_#{label}"
|
112
|
+
has_many_concise = "#{referencing_concise.pluralize}_as_#{label}"
|
113
|
+
|
114
|
+
when /^#{references_name}_(.*)$/, /^#{references_concise}_(.*)$/
|
115
|
+
label = $1
|
116
|
+
belongs_to = "#{references_name}_#{label}"
|
117
|
+
belongs_to_concise = "#{references_concise}_#{label}"
|
118
|
+
|
119
|
+
has_one = "#{referencing_name}_as_#{label}"
|
120
|
+
has_one_concise = "#{referencing_concise}_as_#{label}"
|
121
|
+
|
122
|
+
has_many = "#{referencing_name.pluralize}_as_#{label}"
|
123
|
+
has_many_concise = "#{referencing_concise.pluralize}_as_#{label}"
|
124
|
+
|
125
|
+
else
|
126
|
+
belongs_to = reference_name
|
127
|
+
belongs_to_concise = reference_name
|
128
|
+
|
129
|
+
has_one = "#{referencing_name}_as_#{reference_name}"
|
130
|
+
has_one_concise = "#{referencing_concise}_as_#{reference_name}"
|
131
|
+
|
132
|
+
has_many = "#{referencing_name.pluralize}_as_#{reference_name}"
|
133
|
+
has_many_concise = "#{referencing_concise.pluralize}_as_#{reference_name}"
|
134
|
+
end
|
135
|
+
|
136
|
+
case macro
|
137
|
+
when :has_and_belongs_to_many
|
138
|
+
name = has_many
|
139
|
+
name_concise = has_many_concise
|
140
|
+
opts = {:class_name => referencing_class_name, :join_table => fk.table_name, :foreign_key => column_name}
|
141
|
+
when :belongs_to
|
142
|
+
name = belongs_to
|
143
|
+
name_concise = belongs_to_concise
|
144
|
+
opts = {:class_name => references_class_name, :foreign_key => column_name}
|
145
|
+
when :has_one_or_many
|
146
|
+
opts = {:class_name => referencing_class_name, :foreign_key => column_name}
|
147
|
+
# use connection.indexes and connection.colums rather than class
|
148
|
+
# methods of the referencing class because using the class
|
149
|
+
# methods would require getting the class -- which might trigger
|
150
|
+
# an autoload which could start some recursion making things much
|
151
|
+
# harder to debug.
|
152
|
+
if connection.indexes(referencing_table_name, "#{referencing_table_name} Indexes").any?{|index| index.unique && index.columns == [column_name]}
|
153
|
+
macro = :has_one
|
154
|
+
name = has_one
|
155
|
+
name_concise = has_one_concise
|
156
|
+
else
|
157
|
+
macro = :has_many
|
158
|
+
name = has_many
|
159
|
+
name_concise = has_many_concise
|
160
|
+
if connection.columns(referencing_table_name, "#{referencing_table_name} Columns").any?{ |col| col.name == 'position' }
|
161
|
+
opts[:order] = :position
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
name = name_concise if _use_concise_name?
|
166
|
+
name = name.to_sym
|
167
|
+
if (_filter_association(macro, name) && !_method_exists?(name))
|
168
|
+
logger.info "SchemaPlus associations: #{self.name || self.table_name.classify}.#{macro} #{name.inspect}, #{opts.inspect[1...-1]}"
|
169
|
+
send macro, name, opts.dup
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def _concise_name(string, other) #:nodoc:
|
174
|
+
case
|
175
|
+
when string =~ /^#{other}_(.*)$/ then $1
|
176
|
+
when string =~ /(.*)_#{other}$/ then $1
|
177
|
+
when leader = _common_leader(string,other) then string[leader.length, string.length-leader.length]
|
178
|
+
else string
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def _common_leader(string, other) #:nodoc:
|
183
|
+
leader = nil
|
184
|
+
other.split('_').each do |part|
|
185
|
+
test = "#{leader}#{part}_"
|
186
|
+
break unless string.start_with? test
|
187
|
+
leader = test
|
188
|
+
end
|
189
|
+
return leader
|
190
|
+
end
|
191
|
+
|
192
|
+
def _use_concise_name? #:nodoc:
|
193
|
+
schema_plus_config.associations.concise_names?
|
194
|
+
end
|
195
|
+
|
196
|
+
def _filter_association(macro, name) #:nodoc:
|
197
|
+
config = schema_plus_config.associations
|
198
|
+
return false if config.only and not Array.wrap(config.only).include?(name)
|
199
|
+
return false if config.except and Array.wrap(config.except).include?(name)
|
200
|
+
return false if config.only_type and not Array.wrap(config.only_type).include?(macro)
|
201
|
+
return false if config.except_type and Array.wrap(config.except_type).include?(macro)
|
202
|
+
return true
|
203
|
+
end
|
204
|
+
|
205
|
+
def _method_exists?(name) #:nodoc:
|
206
|
+
method_defined?(name) || private_method_defined?(name) and not (name == :type && [Object, Kernel].include?(instance_method(:type).owner))
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|