schema_plus 0.1.0.pre2 → 0.1.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -4,14 +4,18 @@
4
4
 
5
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
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>
7
+ For added rails DRYness see also the gems
8
+ {+schema_associations+}[http://rubygems.org/gems/schema_associations] and
9
+ {+schema_validations+}[http://rubygems.org/gems/schema_associations]
10
+ <b>IMPORTANT PRERELEASE NOTE: <i>schema_validations is not yet separate
11
+ gems, it's currently bundled in here. It's not documented yet though.</i></b>
8
12
 
9
13
  == Compatibility
10
14
 
11
15
  SchemaPlus supports all combinations of:
12
16
  * rails 3.0 or 3.1
13
17
  * MRI ruby 1.8.7 or 1.9.2
14
- * Postgres, MySQL (using mysql or mysql2 gem), or Sqlite3
18
+ * PostgreSQL, MySQL (using mysql or mysql2 gem), or Sqlite3
15
19
 
16
20
  == Installation
17
21
 
@@ -25,7 +29,8 @@ or in a Gemfile
25
29
 
26
30
  == Features
27
31
 
28
- Here some examples that show off the high points. For full details see the RDoc documentation.
32
+ Here some examples that show off the high points. For full details see the
33
+ {RDoc documentation}[http://rubydoc.info/gems/schema_plus].
29
34
 
30
35
  === Indexes
31
36
 
data/lib/schema_plus.rb CHANGED
@@ -11,7 +11,6 @@ require 'schema_plus/active_record/connection_adapters/abstract_adapter'
11
11
  require 'schema_plus/active_record/connection_adapters/column'
12
12
  require 'schema_plus/active_record/connection_adapters/foreign_key_definition'
13
13
  require 'schema_plus/active_record/connection_adapters/index_definition'
14
- require 'schema_plus/active_record/associations'
15
14
  require 'schema_plus/railtie' if defined?(Rails)
16
15
 
17
16
  module SchemaPlus
@@ -26,7 +25,7 @@ module SchemaPlus
26
25
  end
27
26
  end
28
27
 
29
- # This global configuation options for SchmeaPlus.
28
+ # This global configuation options for SchemaPlus.
30
29
  # Set them in +config/initializers/schema_plus.rb+ using:
31
30
  #
32
31
  # SchemaPlus.setup do |config|
@@ -34,7 +33,7 @@ module SchemaPlus
34
33
  # end
35
34
  #
36
35
  # The options are grouped into subsets based on area of functionality.
37
- # See Config::ForeignKeys, Config::Associations, Config::Validations
36
+ # See Config::ForeignKeys, Config::Validations
38
37
  #
39
38
  class Config < Valuable
40
39
 
@@ -81,63 +80,6 @@ module SchemaPlus
81
80
  end
82
81
  has_value :foreign_keys, :klass => ForeignKeys, :default => ForeignKeys.new
83
82
 
84
- # This set of configuration options control SchemaPlus's automatic
85
- # association behavior. Set them in
86
- # +config/initializers/schema_plus.rb+ using:
87
- #
88
- # SchemaPlus.setup do |config|
89
- # config.associations.auto_create = ...
90
- # end
91
- #
92
- class Associations < Valuable
93
-
94
- ##
95
- # :attr_accessor: auto_create
96
- #
97
- # Whether to automatically create associations based on foreign keys.
98
- # Boolean, default is +true+.
99
- has_value :auto_create, :klass => :boolean, :default => true
100
-
101
- ##
102
- # :attr_accessor: concise_names
103
- #
104
- # Whether to use concise naming (strip out common prefixes from class names).
105
- # Boolean, default is +true+.
106
- has_value :concise_names, :klass => :boolean, :default => true
107
-
108
- ##
109
- # :attr_accessor: except
110
- #
111
- # List of association names to exclude from automatic creation.
112
- # Value is a single name, an array of names, or +nil+. Default is +nil+.
113
- has_value :except, :default => nil
114
-
115
- ##
116
- # :attr_accessor: only
117
- #
118
- # List of association names to include in automatic creation.
119
- # Value is a single name, and array of names, or +nil+. Default is +nil+.
120
- has_value :only, :default => nil
121
-
122
- ##
123
- # :attr_accessor: except_type
124
- #
125
- # List of association types to exclude from automatic creation.
126
- # Value is one or an array of +:belongs_to+, +:has_many+, +:has_one+, and/or
127
- # +:has_and_belongs_to_many+, or +nil+. Default is +nil+.
128
- has_value :except_type, :default => nil
129
-
130
- ##
131
- # :attr_accessor: only_type
132
- #
133
- # List of association types to include from automatic creation.
134
- # Value is one or an array of +:belongs_to+, +:has_many+, +:has_one+, and/or
135
- # +:has_and_belongs_to_many+, or +nil+. Default is +nil+.
136
- has_value :only_type, :default => nil
137
-
138
- end
139
- has_value :associations, :klass => Associations, :default => Associations.new
140
-
141
83
  # This set of configuration options control SchemaPlus's automatic
142
84
  # validations behavior. Set them in
143
85
  # +config/initializers/schema_plus.rb+ using:
@@ -246,3 +188,5 @@ module SchemaPlus
246
188
  end
247
189
 
248
190
  end
191
+
192
+ SchemaPlus.insert unless defined? Rails::Railtie
@@ -7,7 +7,6 @@ module SchemaPlus
7
7
  module Base
8
8
  def self.included(base) #:nodoc:
9
9
  base.extend(ClassMethods)
10
- base.extend(SchemaPlus::ActiveRecord::Associations)
11
10
  base.extend(SchemaPlus::ActiveRecord::Validations)
12
11
  end
13
12
 
@@ -1,3 +1,3 @@
1
1
  module SchemaPlus
2
- VERSION = "0.1.0.pre2"
2
+ VERSION = "0.1.0.pre3"
3
3
  end
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Ronen Barzel", "Michał Łomnicki"]
10
10
  s.email = ["ronen@barzel.org", "michal.lomnicki@gmail.com"]
11
11
  s.homepage = "https://github.com/ronen/schema_plus"
12
- s.summary = "Provides ActiveRecord support for foreign keys, database defined validations and associations."
13
- s.description = "SchemaPlus is an ActiveRecord extension that provides support for defining foreign keys and indexes in database migrations and schemas, as well as for defining model validations and associations based on the database."
12
+ s.summary = "Enhances ActiveRecord schema mechanism, including more DRY index creation and support for foreign key constraints and views."
13
+ s.description = "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."
14
14
 
15
15
  s.rubyforge_project = "schema_plus"
16
16
 
@@ -6,118 +6,136 @@ require 'models/comment'
6
6
 
7
7
  describe "Foreign Key" do
8
8
 
9
- before(:all) do
10
- load_core_schema
11
- end
12
-
13
9
  let(:migration) { ::ActiveRecord::Migration }
14
10
 
15
- if SchemaPlusHelpers.sqlite3?
11
+ context "created with table" do
12
+ before(:all) do
13
+ load_auto_schema
14
+ end
16
15
 
17
- it "raises an exception when attempting to add" do
18
- expect {
19
- add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
20
- }.should raise_error(NotImplementedError)
16
+ it "should report foreign key constraints" do
17
+ Comment.foreign_keys.collect(&:column_names).flatten.should == [ "user_id" ]
21
18
  end
22
19
 
23
- it "raises an exception when attempting to remove" do
24
- expect {
25
- remove_foreign_key(:posts, "dummy")
26
- }.should raise_error(NotImplementedError)
20
+ it "should report reverse foreign key constraints" do
21
+ User.reverse_foreign_keys.collect(&:column_names).flatten.should == [ "user_id" ]
27
22
  end
28
23
 
29
- else
24
+ end
30
25
 
31
- context "when is added", "posts(author_id)" do
26
+ context "modification" do
32
27
 
33
- before(:each) do
34
- add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
35
- end
28
+ before(:all) do
29
+ load_core_schema
30
+ end
36
31
 
37
- after(:each) do
38
- fk = Post.foreign_keys.detect { |fk| fk.column_names == %w[author_id] }
39
- remove_foreign_key(:posts, fk.name)
40
- end
32
+ if SchemaPlusHelpers.sqlite3?
41
33
 
42
- it "references users(id)" do
43
- Post.should reference(:users, :id).on(:author_id)
34
+ it "raises an exception when attempting to add" do
35
+ expect {
36
+ add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
37
+ }.should raise_error(NotImplementedError)
44
38
  end
45
39
 
46
- it "cascades on update" do
47
- Post.should reference(:users).on_update(:cascade)
40
+ it "raises an exception when attempting to remove" do
41
+ expect {
42
+ remove_foreign_key(:posts, "dummy")
43
+ }.should raise_error(NotImplementedError)
48
44
  end
49
45
 
50
- it "restricts on delete" do
51
- Post.should reference(:users).on_delete(:restrict)
52
- end
46
+ else
53
47
 
54
- it "is available in Post.foreign_keys" do
55
- Post.foreign_keys.collect(&:column_names).should include(%w[author_id])
56
- end
48
+ context "when is added", "posts(author_id)" do
57
49
 
58
- it "is available in User.reverse_foreign_keys" do
59
- User.reverse_foreign_keys.collect(&:column_names).should include(%w[author_id])
60
- end
50
+ before(:each) do
51
+ add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
52
+ end
61
53
 
62
- end
54
+ after(:each) do
55
+ fk = Post.foreign_keys.detect { |fk| fk.column_names == %w[author_id] }
56
+ remove_foreign_key(:posts, fk.name)
57
+ end
63
58
 
64
- context "when is dropped", "comments(post_id)" do
59
+ it "references users(id)" do
60
+ Post.should reference(:users, :id).on(:author_id)
61
+ end
65
62
 
66
- let(:foreign_key_name) { fk = Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] } and fk.name }
63
+ it "cascades on update" do
64
+ Post.should reference(:users).on_update(:cascade)
65
+ end
67
66
 
68
- before(:each) do
69
- remove_foreign_key(:comments, foreign_key_name)
70
- end
67
+ it "restricts on delete" do
68
+ Post.should reference(:users).on_delete(:restrict)
69
+ end
71
70
 
72
- after(:each) do
73
- add_foreign_key(:comments, :post_id, :posts, :id)
74
- end
71
+ it "is available in Post.foreign_keys" do
72
+ Post.foreign_keys.collect(&:column_names).should include(%w[author_id])
73
+ end
75
74
 
76
- it "doesn't reference posts(id)" do
77
- Comment.should_not reference(:posts).on(:post_id)
78
- end
75
+ it "is available in User.reverse_foreign_keys" do
76
+ User.reverse_foreign_keys.collect(&:column_names).should include(%w[author_id])
77
+ end
79
78
 
80
- it "is no longer available in Post.foreign_keys" do
81
- Comment.foreign_keys.collect(&:column_names).should_not include(%w[post_id])
82
79
  end
83
80
 
84
- it "is no longer available in User.reverse_foreign_keys" do
85
- Post.reverse_foreign_keys.collect(&:column_names).should_not include(%w[post_id])
86
- end
81
+ context "when is dropped", "comments(post_id)" do
87
82
 
88
- end
83
+ let(:foreign_key_name) { fk = Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] } and fk.name }
84
+
85
+ before(:each) do
86
+ remove_foreign_key(:comments, foreign_key_name)
87
+ end
88
+
89
+ after(:each) do
90
+ add_foreign_key(:comments, :post_id, :posts, :id)
91
+ end
89
92
 
90
- context "when referencing column and column is removed" do
93
+ it "doesn't reference posts(id)" do
94
+ Comment.should_not reference(:posts).on(:post_id)
95
+ end
91
96
 
92
- let(:foreign_key_name) { Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] }.name }
97
+ it "is no longer available in Post.foreign_keys" do
98
+ Comment.foreign_keys.collect(&:column_names).should_not include(%w[post_id])
99
+ end
100
+
101
+ it "is no longer available in User.reverse_foreign_keys" do
102
+ Post.reverse_foreign_keys.collect(&:column_names).should_not include(%w[post_id])
103
+ end
93
104
 
94
- it "should remove foreign keys" do
95
- remove_foreign_key(:comments, foreign_key_name)
96
- Post.reverse_foreign_keys.collect { |fk| fk.column_names == %w[post_id] && fk.table_name == "comments" }.should be_empty
97
105
  end
98
106
 
99
- end
107
+ context "when referencing column and column is removed" do
100
108
 
101
- context "when table name is a rerved word" do
102
- before(:each) do
103
- migration.suppress_messages do
104
- migration.create_table :references, :force => true do |t|
105
- t.integer :post_id
106
- end
109
+ let(:foreign_key_name) { Comment.foreign_keys.detect { |definition| definition.column_names == %w[post_id] }.name }
110
+
111
+ it "should remove foreign keys" do
112
+ remove_foreign_key(:comments, foreign_key_name)
113
+ Post.reverse_foreign_keys.collect { |fk| fk.column_names == %w[post_id] && fk.table_name == "comments" }.should be_empty
107
114
  end
115
+
108
116
  end
109
117
 
110
- it "can add, detect, and remove a foreign key without error" do
111
- migration.suppress_messages do
112
- expect {
113
- migration.add_foreign_key(:references, :post_id, :posts, :id)
114
- foreign_key = migration.foreign_keys(:references).detect{|definition| definition.column_names == ["post_id"]}
115
- migration.remove_foreign_key(:references, foreign_key.name)
116
- }.should_not raise_error
118
+ context "when table name is a rerved word" do
119
+ before(:each) do
120
+ migration.suppress_messages do
121
+ migration.create_table :references, :force => true do |t|
122
+ t.integer :post_id
123
+ end
124
+ end
125
+ end
126
+
127
+ it "can add, detect, and remove a foreign key without error" do
128
+ migration.suppress_messages do
129
+ expect {
130
+ migration.add_foreign_key(:references, :post_id, :posts, :id)
131
+ foreign_key = migration.foreign_keys(:references).detect{|definition| definition.column_names == ["post_id"]}
132
+ migration.remove_foreign_key(:references, foreign_key.name)
133
+ }.should_not raise_error
134
+ end
117
135
  end
118
136
  end
119
- end
120
137
 
138
+ end
121
139
  end
122
140
 
123
141
  protected
data/spec/spec_helper.rb CHANGED
@@ -12,8 +12,6 @@ require 'active_record'
12
12
  require 'schema_plus'
13
13
  require 'connection'
14
14
 
15
- SchemaPlus.insert
16
-
17
15
  Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
18
16
 
19
17
  RSpec.configure do |config|
@@ -4,8 +4,6 @@ describe "Validations" do
4
4
 
5
5
  before(:all) do
6
6
  define_schema
7
- # TODO: it should work regardless of auto-associations
8
- SchemaPlus.config.associations.auto_create = false
9
7
  end
10
8
 
11
9
  after(:each) do
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: schema_plus
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: 6
5
- version: 0.1.0.pre2
5
+ version: 0.1.0.pre3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ronen Barzel
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2011-07-11 00:00:00 -07:00
14
+ date: 2011-07-17 00:00:00 -07:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -164,7 +164,6 @@ files:
164
164
  - Rakefile
165
165
  - init.rb
166
166
  - lib/schema_plus.rb
167
- - lib/schema_plus/active_record/associations.rb
168
167
  - lib/schema_plus/active_record/base.rb
169
168
  - lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb
170
169
  - lib/schema_plus/active_record/connection_adapters/column.rb
@@ -184,7 +183,6 @@ files:
184
183
  - schema_plus.gemspec
185
184
  - schema_plus.gemspec.rails3.0
186
185
  - schema_plus.gemspec.rails3.1
187
- - spec/association_spec.rb
188
186
  - spec/connections/mysql/connection.rb
189
187
  - spec/connections/mysql2/connection.rb
190
188
  - spec/connections/postgresql/connection.rb
@@ -240,7 +238,6 @@ signing_key:
240
238
  specification_version: 3
241
239
  summary: Enhances ActiveRecord schema mechanism, including more DRY index creation and support for foreign key constraints and views.
242
240
  test_files:
243
- - spec/association_spec.rb
244
241
  - spec/connections/mysql/connection.rb
245
242
  - spec/connections/mysql2/connection.rb
246
243
  - spec/connections/postgresql/connection.rb
@@ -1,211 +0,0 @@
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
@@ -1,529 +0,0 @@
1
- # encoding: utf-8
2
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
-
4
- describe ActiveRecord::Base do
5
- include SchemaPlusHelpers
6
-
7
- after(:each) do
8
- remove_all_models
9
- end
10
-
11
- around(:each) do |example|
12
- with_fk_auto_create(&example)
13
- end
14
-
15
- context "in basic case" do
16
- before(:each) do
17
- create_tables(
18
- "posts", {}, {},
19
- "comments", {}, { :post_id => {} }
20
- )
21
- class Post < ActiveRecord::Base ; end
22
- class Comment < ActiveRecord::Base ; end
23
- end
24
- it "should create belongs_to association" do
25
- reflection = Comment.reflect_on_association(:post)
26
- reflection.should_not be_nil
27
- reflection.macro.should == :belongs_to
28
- reflection.options[:class_name].should == "Post"
29
- reflection.options[:foreign_key].should == "post_id"
30
- end
31
- it "should create has_many association" do
32
- reflection = Post.reflect_on_association(:comments)
33
- reflection.should_not be_nil
34
- reflection.macro.should == :has_many
35
- reflection.options[:class_name].should == "Comment"
36
- reflection.options[:foreign_key].should == "post_id"
37
- end
38
- it "shouldn't raise an exception when model is instantiated" do
39
- expect { Post.new }.should_not raise_error
40
- end
41
- end
42
-
43
- it "should override auto_create negatively" do
44
- with_associations_auto_create(true) do
45
- create_tables(
46
- "posts", {}, {},
47
- "comments", {}, { :post_id => {} }
48
- )
49
- class Post < ActiveRecord::Base
50
- schema_plus :associations => { :auto_create => false }
51
- end
52
- class Comment < ActiveRecord::Base ; end
53
- Post.reflect_on_association(:comments).should be_nil
54
- Comment.reflect_on_association(:post).should_not be_nil
55
- end
56
- end
57
-
58
- context "with multiple associations of all types" do
59
- before(:each) do
60
- create_tables(
61
- "owners", {}, {},
62
- "colors", {}, {},
63
- "widgets", {}, {
64
- :owner_id => {},
65
- },
66
- "parts", {}, { :widget_id => {} },
67
- "manifests", {}, { :widget_id => { :index => {:unique => true}} },
68
- "colors_widgets", {:id => false}, { :widget_id => {}, :color_id => {} }
69
- )
70
- end
71
-
72
- def check_reflections(hash)
73
- hash.each do |key, val|
74
- reflection = Widget.reflect_on_association(key)
75
- case val
76
- when true then reflection.should_not be_nil
77
- else reflection.should be_nil
78
- end
79
- end
80
- end
81
-
82
- it "should default as expected" do
83
- class Widget < ActiveRecord::Base ; end
84
- check_reflections(:owner => true, :colors => true, :parts => true, :manifest => true)
85
- end
86
-
87
- it "should respect :only" do
88
- class Widget < ActiveRecord::Base
89
- schema_plus :associations => { :only => :owner }
90
- end
91
- check_reflections(:owner => true, :colors => false, :parts => false, :manifest => false)
92
- end
93
-
94
- it "should respect :except" do
95
- class Widget < ActiveRecord::Base
96
- schema_plus :associations => { :except => :owner }
97
- end
98
- check_reflections(:owner => false, :colors => true, :parts => true, :manifest => true)
99
- end
100
-
101
- it "should respect :only_type :belongs_to" do
102
- class Widget < ActiveRecord::Base
103
- schema_plus :associations => { :only_type => :belongs_to }
104
- end
105
- check_reflections(:owner => true, :colors => false, :parts => false, :manifest => false)
106
- end
107
-
108
- it "should respect :except_type :belongs_to" do
109
- class Widget < ActiveRecord::Base
110
- schema_plus :associations => { :except_type => :belongs_to }
111
- end
112
- check_reflections(:owner => false, :colors => true, :parts => true, :manifest => true)
113
- end
114
-
115
- it "should respect :only_type :has_many" do
116
- class Widget < ActiveRecord::Base
117
- schema_plus :associations => { :only_type => :has_many }
118
- end
119
- check_reflections(:owner => false, :colors => false, :parts => true, :manifest => false)
120
- end
121
-
122
- it "should respect :except_type :has_many" do
123
- class Widget < ActiveRecord::Base
124
- schema_plus :associations => { :except_type => :has_many }
125
- end
126
- check_reflections(:owner => true, :colors => true, :parts => false, :manifest => true)
127
- end
128
-
129
- it "should respect :only_type :has_one" do
130
- class Widget < ActiveRecord::Base
131
- schema_plus :associations => { :only_type => :has_one }
132
- end
133
- check_reflections(:owner => false, :colors => false, :parts => false, :manifest => true)
134
- end
135
-
136
- it "should respect :except_type :has_one" do
137
- class Widget < ActiveRecord::Base
138
- schema_plus :associations => { :except_type => :has_one }
139
- end
140
- check_reflections(:owner => true, :colors => true, :parts => true, :manifest => false)
141
- end
142
-
143
- it "should respect :only_type :has_and_belongs_to_many" do
144
- class Widget < ActiveRecord::Base
145
- schema_plus :associations => { :only_type => :has_and_belongs_to_many }
146
- end
147
- check_reflections(:owner => false, :colors => true, :parts => false, :manifest => false)
148
- end
149
-
150
- it "should respect :except_type :has_and_belongs_to_many" do
151
- class Widget < ActiveRecord::Base
152
- schema_plus :associations => { :except_type => :has_and_belongs_to_many }
153
- end
154
- check_reflections(:owner => true, :colors => false, :parts => true, :manifest => true)
155
- end
156
-
157
- end
158
-
159
- it "should override auto_create positively" do
160
- with_associations_auto_create(false) do
161
- create_tables(
162
- "posts", {}, {},
163
- "comments", {}, { :post_id => {} }
164
- )
165
- class Post < ActiveRecord::Base
166
- schema_plus :associations => { :auto_create => true }
167
- end
168
- class Comment < ActiveRecord::Base ; end
169
- Post.reflect_on_association(:comments).should_not be_nil
170
- Comment.reflect_on_association(:post).should be_nil
171
- end
172
- end
173
-
174
-
175
- context "with unique index" do
176
- before(:each) do
177
- create_tables(
178
- "posts", {}, {},
179
- "comments", {}, { :post_id => {:index => { :unique => true} } }
180
- )
181
- class Post < ActiveRecord::Base ; end
182
- class Comment < ActiveRecord::Base ; end
183
- end
184
- it "should create has_one association" do
185
- reflection = Post.reflect_on_association(:comment)
186
- reflection.should_not be_nil
187
- reflection.macro.should == :has_one
188
- reflection.options[:class_name].should == "Comment"
189
- reflection.options[:foreign_key].should == "post_id"
190
- end
191
- end
192
-
193
- context "with prefixed column names" do
194
- before(:each) do
195
- create_tables(
196
- "posts", {}, {},
197
- "comments", {}, { :subject_post_id => { :references => :posts} }
198
- )
199
- class Post < ActiveRecord::Base ; end
200
- class Comment < ActiveRecord::Base ; end
201
- end
202
- it "should name belongs_to according to column" do
203
- reflection = Comment.reflect_on_association(:subject_post)
204
- reflection.should_not be_nil
205
- reflection.macro.should == :belongs_to
206
- reflection.options[:class_name].should == "Post"
207
- reflection.options[:foreign_key].should == "subject_post_id"
208
- end
209
-
210
- it "should name has_many using 'as column'" do
211
- reflection = Post.reflect_on_association(:comments_as_subject)
212
- reflection.should_not be_nil
213
- reflection.macro.should == :has_many
214
- reflection.options[:class_name].should == "Comment"
215
- reflection.options[:foreign_key].should == "subject_post_id"
216
- end
217
- end
218
-
219
- context "with suffixed column names" do
220
- before(:each) do
221
- create_tables(
222
- "posts", {}, {},
223
- "comments", {}, { :post_cited => { :references => :posts} }
224
- )
225
- class Post < ActiveRecord::Base ; end
226
- class Comment < ActiveRecord::Base ; end
227
- end
228
- it "should name belongs_to according to column" do
229
- reflection = Comment.reflect_on_association(:post_cited)
230
- reflection.should_not be_nil
231
- reflection.macro.should == :belongs_to
232
- reflection.options[:class_name].should == "Post"
233
- reflection.options[:foreign_key].should == "post_cited"
234
- end
235
-
236
- it "should name has_many using 'as column'" do
237
- reflection = Post.reflect_on_association(:comments_as_cited)
238
- reflection.should_not be_nil
239
- reflection.macro.should == :has_many
240
- reflection.options[:class_name].should == "Comment"
241
- reflection.options[:foreign_key].should == "post_cited"
242
- end
243
- end
244
-
245
- context "with arbitrary column names" do
246
- before(:each) do
247
- create_tables(
248
- "posts", {}, {},
249
- "comments", {}, { :subject => {:references => :posts} }
250
- )
251
- class Post < ActiveRecord::Base ; end
252
- class Comment < ActiveRecord::Base ; end
253
- end
254
- it "should name belongs_to according to column" do
255
- reflection = Comment.reflect_on_association(:subject)
256
- reflection.should_not be_nil
257
- reflection.macro.should == :belongs_to
258
- reflection.options[:class_name].should == "Post"
259
- reflection.options[:foreign_key].should == "subject"
260
- end
261
-
262
- it "should name has_many using 'as column'" do
263
- reflection = Post.reflect_on_association(:comments_as_subject)
264
- reflection.should_not be_nil
265
- reflection.macro.should == :has_many
266
- reflection.options[:class_name].should == "Comment"
267
- reflection.options[:foreign_key].should == "subject"
268
- end
269
- end
270
-
271
-
272
- context "with position" do
273
- before(:each) do
274
- create_tables(
275
- "posts", {}, {},
276
- "comments", {}, { :post_id => {}, :position => {} }
277
- )
278
- class Post < ActiveRecord::Base ; end
279
- class Comment < ActiveRecord::Base ; end
280
- end
281
- it "should create ordered has_many association" do
282
- reflection = Post.reflect_on_association(:comments)
283
- reflection.should_not be_nil
284
- reflection.macro.should == :has_many
285
- reflection.options[:class_name].should == "Comment"
286
- reflection.options[:foreign_key].should == "post_id"
287
- reflection.options[:order].to_s.should == "position"
288
- end
289
- end
290
-
291
- context "regarding parent-child relationships" do
292
-
293
- let (:migration) {ActiveRecord::Migration}
294
-
295
- before(:each) do
296
- create_tables(
297
- "nodes", {:foreign_keys => {:auto_index => false}}, { :parent_id => {} }
298
- )
299
- end
300
-
301
- it "should use children as the inverse of parent" do
302
- class Node < ActiveRecord::Base ; end
303
- reflection = Node.reflect_on_association(:children)
304
- reflection.should_not be_nil
305
- end
306
-
307
- it "should use child as the singular inverse of parent" do
308
- migration.suppress_messages do
309
- migration.add_index(:nodes, :parent_id, :unique => true)
310
- end
311
- class Node < ActiveRecord::Base ; end
312
- reflection = Node.reflect_on_association(:child)
313
- reflection.should_not be_nil
314
- end
315
- end
316
-
317
-
318
- context "regarding concise names" do
319
-
320
- def prefix_one
321
- create_tables(
322
- "posts", {}, {},
323
- "post_comments", {}, { :post_id => {} }
324
- )
325
- Object.const_set(:Post, Class.new(ActiveRecord::Base))
326
- Object.const_set(:PostComment, Class.new(ActiveRecord::Base))
327
- end
328
-
329
- def suffix_one
330
- create_tables(
331
- "posts", {}, {},
332
- "comment_posts", {}, { :post_id => {} }
333
- )
334
- Object.const_set(:Post, Class.new(ActiveRecord::Base))
335
- Object.const_set(:CommentPost, Class.new(ActiveRecord::Base))
336
- end
337
-
338
- def prefix_both
339
- create_tables(
340
- "blog_page_posts", {}, {},
341
- "blog_page_comments", {}, { :blog_page_post_id => {} }
342
- )
343
- Object.const_set(:BlogPagePost, Class.new(ActiveRecord::Base))
344
- Object.const_set(:BlogPageComment, Class.new(ActiveRecord::Base))
345
- end
346
-
347
- it "should use concise association name for one prefix" do
348
- with_associations_config(:auto_create => true, :concise_names => true) do
349
- prefix_one
350
- reflection = Post.reflect_on_association(:comments)
351
- reflection.should_not be_nil
352
- reflection.macro.should == :has_many
353
- reflection.options[:class_name].should == "PostComment"
354
- reflection.options[:foreign_key].should == "post_id"
355
- end
356
- end
357
-
358
- it "should use concise association name for one suffix" do
359
- with_associations_config(:auto_create => true, :concise_names => true) do
360
- suffix_one
361
- reflection = Post.reflect_on_association(:comments)
362
- reflection.should_not be_nil
363
- reflection.macro.should == :has_many
364
- reflection.options[:class_name].should == "CommentPost"
365
- reflection.options[:foreign_key].should == "post_id"
366
- end
367
- end
368
-
369
- it "should use concise association name for shared prefixes" do
370
- with_associations_config(:auto_create => true, :concise_names => true) do
371
- prefix_both
372
- reflection = BlogPagePost.reflect_on_association(:comments)
373
- reflection.should_not be_nil
374
- reflection.macro.should == :has_many
375
- reflection.options[:class_name].should == "BlogPageComment"
376
- reflection.options[:foreign_key].should == "blog_page_post_id"
377
- end
378
- end
379
-
380
- it "should use full names and not concise names when so configured" do
381
- with_associations_config(:auto_create => true, :concise_names => false) do
382
- prefix_one
383
- reflection = Post.reflect_on_association(:post_comments)
384
- reflection.should_not be_nil
385
- reflection.macro.should == :has_many
386
- reflection.options[:class_name].should == "PostComment"
387
- reflection.options[:foreign_key].should == "post_id"
388
- reflection = Post.reflect_on_association(:comments)
389
- reflection.should be_nil
390
- end
391
- end
392
-
393
- it "should use concise names and not full names when so configured" do
394
- with_associations_config(:auto_create => true, :concise_names => true) do
395
- prefix_one
396
- reflection = Post.reflect_on_association(:comments)
397
- reflection.should_not be_nil
398
- reflection.macro.should == :has_many
399
- reflection.options[:class_name].should == "PostComment"
400
- reflection.options[:foreign_key].should == "post_id"
401
- reflection = Post.reflect_on_association(:post_comments)
402
- reflection.should be_nil
403
- end
404
- end
405
-
406
-
407
- end
408
-
409
- context "with joins table" do
410
- before(:each) do
411
- create_tables(
412
- "posts", {}, {},
413
- "tags", {}, {},
414
- "posts_tags", {:id => false}, { :post_id => {}, :tag_id => {}}
415
- )
416
- class Post < ActiveRecord::Base ; end
417
- class Tag < ActiveRecord::Base ; end
418
- end
419
- it "should create has_and_belongs_to_many association" do
420
- reflection = Post.reflect_on_association(:tags)
421
- reflection.should_not be_nil
422
- reflection.macro.should == :has_and_belongs_to_many
423
- reflection.options[:class_name].should == "Tag"
424
- reflection.options[:join_table].should == "posts_tags"
425
- end
426
- end
427
-
428
- context "regarding existing methods" do
429
- before(:each) do
430
- create_tables(
431
- "types", {}, {},
432
- "posts", {}, {:type_id => {}}
433
- )
434
- end
435
- it "should define association normally if no existing method is defined" do
436
- class Type < ActiveRecord::Base ; end
437
- Type.reflect_on_association(:posts).should_not be_nil # sanity check for this context
438
- end
439
- it "should not define association over existing public method" do
440
- class Type < ActiveRecord::Base
441
- def posts
442
- :existing
443
- end
444
- end
445
- Type.reflect_on_association(:posts).should be_nil
446
- end
447
- it "should not define association over existing private method" do
448
- class Type < ActiveRecord::Base
449
- private
450
- def posts
451
- :existing
452
- end
453
- end
454
- Type.reflect_on_association(:posts).should be_nil
455
- end
456
- it "should define association :type over (deprecated) kernel method" do
457
- class Post < ActiveRecord::Base ; end
458
- Post.reflect_on_association(:type).should_not be_nil
459
- end
460
- it "should not define association :type over model method" do
461
- class Post < ActiveRecord::Base
462
- def type
463
- :existing
464
- end
465
- end
466
- Post.reflect_on_association(:type).should be_nil
467
- end
468
- end
469
-
470
- context "regarding relations" do
471
- before(:each) do
472
- create_tables(
473
- "posts", {}, {},
474
- "comments", {}, { :post_id => {} }
475
- )
476
- class Post < ActiveRecord::Base ; end
477
- class Comment < ActiveRecord::Base ; end
478
- end
479
-
480
- it "should define associations before needed by relation" do
481
- Post.joins(:comments).all
482
- expect { Post.joins(:comments).all }.should_not raise_error
483
-
484
- end
485
-
486
- end
487
-
488
- protected
489
-
490
- def with_fk_auto_create(value = true, &block)
491
- save = SchemaPlus.config.foreign_keys.auto_create
492
- begin
493
- SchemaPlus.config.foreign_keys.auto_create = value
494
- yield
495
- ensure
496
- SchemaPlus.config.foreign_keys.auto_create = save
497
- end
498
- end
499
-
500
- def with_associations_auto_create(value, &block)
501
- with_associations_config(:auto_create => value, &block)
502
- end
503
-
504
- def with_associations_config(opts, &block)
505
- save = Hash[opts.keys.collect{|key| [key, SchemaPlus.config.associations.send(key)]}]
506
- begin
507
- SchemaPlus.config.associations.update_attributes(opts)
508
- yield
509
- ensure
510
- SchemaPlus.config.associations.update_attributes(save)
511
- end
512
- end
513
-
514
- def create_tables(*table_defs)
515
- ActiveRecord::Migration.suppress_messages do
516
- ActiveRecord::Base.connection.tables.each do |table|
517
- ActiveRecord::Migration.drop_table table
518
- end
519
- table_defs.each_slice(3) do |table_name, opts, columns_with_options|
520
- ActiveRecord::Migration.create_table table_name, opts do |t|
521
- columns_with_options.each_pair do |column, options|
522
- t.integer column, options
523
- end
524
- end
525
- end
526
- end
527
- end
528
-
529
- end