schema_associations 0.1.0.pre3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -8,7 +8,18 @@ columns, keeping your model class definitions simple and DRY. That's great
8
8
  for simple data columns, but where it falls down is when your table
9
9
  contains references to other tables: then the "accessors" you need are the
10
10
  +belongs_to+, +has_one+, +has_many+, and +has_and_belongs_to_many+
11
- associations -- and you need to put them into your model class definitions by hand, which isn't so DRY.
11
+ associations -- and you need to put them into your model class definitions
12
+ by hand. In fact, for every relation, you need to define two associations, such as
13
+
14
+ class Post < ActiveRecord::Base
15
+ has_many :commens
16
+ end
17
+
18
+ class Comment < ActiveReocrd::Base
19
+ belongs_to :post
20
+ end
21
+
22
+ ....which isn't so DRY.
12
23
 
13
24
  Enter the SchemaAssociations gem. It extends ActiveRecord to automatically
14
25
  define the appropriate associations based on foreign key constraints in the
@@ -21,14 +32,17 @@ automatically defines foreign key constraints. So the common case is simple --
21
32
 
22
33
  create_table :comments do |t|
23
34
  t.integer post_id
24
- # ... whatever ...
25
35
  end
26
36
 
27
- Then SchemaAssociations will define the corresponding associations:
37
+ Then all you need for your models is:
28
38
 
29
- Post.has_many :comments
30
- Comment.belongs_to :post
39
+ class Post < ActiveRecord::Base
40
+ end
41
+
42
+ class Comment < ActiveRecord::Base
43
+ end
31
44
 
45
+ and SchemaAssociations defines the appropriate associations under the hood
32
46
 
33
47
  == What if I want something special?
34
48
 
@@ -37,18 +51,178 @@ to pass special options. SchemaAssociations won't clobber any existing
37
51
  definitions.
38
52
 
39
53
  You can also control the behavior with various options, globally via
40
- SchemaAssociations.setup or per-model via #schema_associations. See
54
+ SchemaAssociations::setup or per-model via SchemaAssociations::ActiveRecord#schema_associations. See
41
55
  SchemaAssociations::Config for the available options.
42
56
 
43
- == Full details
57
+ == Full Details
44
58
 
45
59
  === The basics
46
60
 
47
- === Multiple references
61
+ The common cases work entirely as you'd expect. For a one-to-many
62
+ relationship using standard naming conventions:
63
+
64
+ # migration:
65
+
66
+ create_table :comments do |t|
67
+ t.integer post_id
68
+ end
69
+
70
+ # models:
71
+
72
+ class Post < ActiveRecord::Base
73
+ has_many :comments
74
+ end
75
+
76
+ class Comment < ActiveReocrd::Base
77
+ belongs_to :post
78
+ end
79
+
80
+ For a one-to-one relationship:
81
+
82
+ # migration:
83
+
84
+ create_table :comments do |t|
85
+ t.integer post_id, :index => :unique # (using the :index option provided by schema_plus )
86
+ end
87
+
88
+ # models:
89
+
90
+ class Post < ActiveRecord::Base
91
+ has_one :comment
92
+ end
93
+
94
+ class Comment < ActiveReocrd::Base
95
+ belongs_to :post
96
+ end
97
+
98
+ And for many-to-many relationships:
99
+
100
+ # migration:
101
+
102
+ create_table :groups_members do |t|
103
+ integer :group_id
104
+ integer :member_id
105
+ end
106
+
107
+ class Group < ActiveReocrd::Base
108
+ has_and_belongs_to_many :members
109
+ end
110
+
111
+ class Member < ActiveRecord::Base
112
+ has_and_belongs_to_many :groups
113
+ end
114
+
115
+ === Unusual names, multiple references
116
+
117
+ Sometimes you want or need to deviate from the simple naming conventions. In
118
+ this case, the +belongs_to+ relationship name is taken from the name of the
119
+ foreign key column, and the +has_many+ or +has_one+ is named by the referencing
120
+ table, suffixed with "as" the relationship name. An example should make this clear...
121
+
122
+ Suppose your company hires interns, and each intern is assigned a manager and a
123
+ mentor, who are regular employees.
124
+
125
+ create_table :interns do |t|
126
+ t.integer :manager_id, :references => :employees
127
+ t.integer :mentor_id, :references => :employees
128
+ end
129
+
130
+ SchemaAssociations defines a +belongs_to+ association for each reference, named according to
131
+ the column:
132
+
133
+ class Intern < ActiveRecord::Base
134
+ belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id"
135
+ belongs_to :mentor, :class_name => "Employee", :foreign_key => "mentor_id"
136
+ end
137
+
138
+ And the corresponding +has_many+ association each gets a suffix to indicate which one relation it refers to:
139
+
140
+ class Employee < ActiveRecord::Base
141
+ has_many :interns_as_manager, :class_name => "Intern", :foreign_key => "manager_id"
142
+ has_many :interns_as_mentor, :class_name => "Intern", :foreign_key => "mentor_id"
143
+ end
144
+
145
+ === Special case for trees
146
+
147
+ If your forward relation is named "parent", SchemaAssociations names the reverse
148
+ relation "child" or "children". That is, if you have:
149
+
150
+ create_table :nodes
151
+ t.integer :parent_id # schema_plus assumes it's a reference to this table
152
+ end
153
+
154
+ Then SchemaAssociations will define
155
+
156
+ class Node < ActiveRecord::Base
157
+ belongs_to :parent, :class_name => "Node", :foreign_key => "parent_id"
158
+ has_many :children, :class_name => "Node", :foreign_key => "parent_id"
159
+ end
48
160
 
49
161
  === Concise names
50
162
 
51
- === How do I know what it did?
163
+ For modularity in your tables and classes, you might use a common prefix for
164
+ related objects. For example, you may have widgets each of which has a color,
165
+ and might have one base that has a top color and a bottom color, from the same
166
+ set of colors.
167
+
168
+ create_table :widget_colors |t|
169
+ end
170
+
171
+ create_table :widgets do |t|
172
+ t.integer :widget_color_id
173
+ end
174
+
175
+ create_table :widget_base
176
+ t.integer :widget_id, :index => :unique
177
+ t.integer :top_widget_color_id, :references => :widget_colors
178
+ t.integer :bottom_widget_color_id, :references => :widget_colors
179
+ end
180
+
181
+ Using the full name for the associations would make your code verbose and not quite DRY:
182
+
183
+ @widget.widget_color
184
+ @widget.widget_base.top_widget_color
185
+
186
+ Instead, by default, SchemaAssociations uses concise names: shared leading
187
+ words are removed from the association name. So instead of the above, your
188
+ code looks like:
189
+
190
+ @widget.color
191
+ @widget.base.top_color
192
+
193
+ i.e. these associations would be defined:
194
+
195
+ class WidgetColor < ActiveRecord::Base
196
+ has_many :widgets, :class_name => "Widget", :foreign_key => "widget_color_id"
197
+ has_many :bases_as_top, :class_name => "WidgetBase", :foreign_key => "top_widget_color_id"
198
+ has_many :bases_as_bottom, :class_name => "WidgetBase", :foreign_key => "bottom_widget_color_id"
199
+ end
200
+
201
+ class Widget < ActiveRecord::Base
202
+ belongs_to :color, :class_name => "WidgetColor", :foreign_key => "widget_color_id"
203
+ has_one :base, :class_name => "WidgetBase", :foreign_key => "widget_base_id"
204
+ end
205
+
206
+ class WidgetBase < ActiveRecord::Base
207
+ belongs_to :top_color, :class_name => "WidgetColor", :foreign_key => "top_widget_color_id"
208
+ belongs_to :bottom_color, :class_name => "WidgetColor", :foreign_key => "bottom_widget_color_id"
209
+ belongs_to :widget, :class_name => "Widget", :foreign_key => "widget_id"
210
+ end
211
+
212
+ If you like the formality of using full names for the asociations, you can turn off concise names globally or per-model, see SchemaAssociations::Config
213
+
214
+ == How do I know what it did?
215
+
216
+ If you're curious (or dubious) about what associations SchemaAssociations defines, you can check the log file. For every assocation that SchemaAssociations defines, it generates an info entry such as
217
+
218
+ [schema_associations] Post.has_many :comments, :class_name "Comment", :foreign_key "comment_id"
219
+
220
+ which shows the exact method definition call.
221
+
222
+ SchemaAssociations defines the associations lazily, only creating them when
223
+ they're first needed. So you may need to search through the log file to find
224
+ them all (and some may not be defined at all if they were never needed for the
225
+ use cases that you logged).
52
226
 
53
227
  == Compatibility
54
228
 
@@ -66,14 +240,6 @@ or in a Gemfile
66
240
 
67
241
  gem "schema_associations"
68
242
 
69
- == History
70
-
71
- * SchemaAssociations is derived from the "Red Hill On Rails" plugin
72
- foreign_key_associations originally created by harukizaemon
73
- (https://github.com/harukizaemon)
74
-
75
- * SchemaAssociations was created in 2011 by Michal Lomnicki and Ronen Barzel
76
-
77
243
  == Testing
78
244
 
79
245
  SchemaAssociations is tested using rspec, sqlite3, and rvm, with some
@@ -85,13 +251,19 @@ full combo of tests, after you've forked & cloned:
85
251
  $ ./runspecs # as many times as you like
86
252
 
87
253
  You can also pick a specific version of rails and ruby to use, such as:
88
- $ export SCHEMA_ASSOCIATIONS_RAILS_VERSION
89
- $ SCHEMA_ASSOCIATIONS_RAILS_VERSION=3.1
90
254
  $ rvm use 1.9.2
255
+ $ export SCHEMA_ASSOCIATIONS_RAILS_VERSION=3.1
256
+ $ bundle update rails --local
91
257
  $ rake spec
92
258
 
93
259
  If you're running ruby 1.9.2, code coverage results will be in coverage/index.html -- it should be at 100% coverage.
94
260
 
261
+ == History
262
+
263
+ * SchemaAssociations is derived from the "Red Hill On Rails" plugin foreign_key_associations originally created by harukizaemon (https://github.com/harukizaemon)
264
+
265
+ * SchemaAssociations was created in 2011 by Michal Lomnicki and Ronen Barzel
266
+
95
267
  == License
96
268
 
97
269
  This plugin is released under the MIT license.
@@ -79,80 +79,22 @@ module SchemaAssociations
79
79
  return unless fk.column_names.size == 1
80
80
 
81
81
  referencing_table_name ||= fk.table_name
82
-
83
82
  column_name = fk.column_names.first
84
- reference_name = column_name.sub(/_id$/, '')
83
+
85
84
  references_name = fk.references_table_name.singularize
86
85
  referencing_name = referencing_table_name.singularize
87
86
 
88
- references_class_name = references_name.classify
89
87
  referencing_class_name = referencing_name.classify
88
+ references_class_name = references_name.classify
90
89
 
91
- references_concise = _concise_name(references_name, referencing_name)
92
- referencing_concise = _concise_name(referencing_name, references_name)
93
-
94
- case reference_name
95
- when 'parent'
96
- belongs_to = 'parent'
97
- belongs_to_concise = 'parent'
98
-
99
- has_one = 'child'
100
- has_one_concise = 'child'
101
-
102
- has_many = 'children'
103
- has_many_concise = 'children'
104
-
105
- when references_name
106
- belongs_to = references_name
107
- belongs_to_concise = references_concise
108
-
109
- has_one = referencing_name
110
- has_one_concise = referencing_concise
111
-
112
- has_many = referencing_name.pluralize
113
- has_many_concise = referencing_concise.pluralize
114
-
115
- when /(.*)_#{references_name}$/, /(.*)_#{references_concise}$/
116
- label = $1
117
- belongs_to = "#{label}_#{references_name}"
118
- belongs_to_concise = "#{label}_#{references_concise}"
119
-
120
- has_one = "#{referencing_name}_as_#{label}"
121
- has_one_concise = "#{referencing_concise}_as_#{label}"
122
-
123
- has_many = "#{referencing_name.pluralize}_as_#{label}"
124
- has_many_concise = "#{referencing_concise.pluralize}_as_#{label}"
125
-
126
- when /^#{references_name}_(.*)$/, /^#{references_concise}_(.*)$/
127
- label = $1
128
- belongs_to = "#{references_name}_#{label}"
129
- belongs_to_concise = "#{references_concise}_#{label}"
130
-
131
- has_one = "#{referencing_name}_as_#{label}"
132
- has_one_concise = "#{referencing_concise}_as_#{label}"
133
-
134
- has_many = "#{referencing_name.pluralize}_as_#{label}"
135
- has_many_concise = "#{referencing_concise.pluralize}_as_#{label}"
136
-
137
- else
138
- belongs_to = reference_name
139
- belongs_to_concise = reference_name
140
-
141
- has_one = "#{referencing_name}_as_#{reference_name}"
142
- has_one_concise = "#{referencing_concise}_as_#{reference_name}"
143
-
144
- has_many = "#{referencing_name.pluralize}_as_#{reference_name}"
145
- has_many_concise = "#{referencing_concise.pluralize}_as_#{reference_name}"
146
- end
90
+ names = _determine_association_names(column_name.sub(/_id$/, ''), referencing_name, references_name)
147
91
 
148
92
  case macro
149
93
  when :has_and_belongs_to_many
150
- name = has_many
151
- name_concise = has_many_concise
94
+ name = names[:has_many]
152
95
  opts = {:class_name => referencing_class_name, :join_table => fk.table_name, :foreign_key => column_name}
153
96
  when :belongs_to
154
- name = belongs_to
155
- name_concise = belongs_to_concise
97
+ name = names[:belongs_to]
156
98
  opts = {:class_name => references_class_name, :foreign_key => column_name}
157
99
  when :has_one_or_many
158
100
  opts = {:class_name => referencing_class_name, :foreign_key => column_name}
@@ -163,25 +105,66 @@ module SchemaAssociations
163
105
  # harder to debug.
164
106
  if connection.indexes(referencing_table_name, "#{referencing_table_name} Indexes").any?{|index| index.unique && index.columns == [column_name]}
165
107
  macro = :has_one
166
- name = has_one
167
- name_concise = has_one_concise
108
+ name = names[:has_one]
168
109
  else
169
110
  macro = :has_many
170
- name = has_many
171
- name_concise = has_many_concise
111
+ name = names[:has_many]
172
112
  if connection.columns(referencing_table_name, "#{referencing_table_name} Columns").any?{ |col| col.name == 'position' }
173
113
  opts[:order] = :position
174
114
  end
175
115
  end
176
116
  end
177
- name = name_concise if _use_concise_name?
178
- name = name.to_sym
179
117
  if (_filter_association(macro, name) && !_method_exists?(name))
180
118
  logger.info "[schema_associations] #{self.name || self.table_name.classify}.#{macro} #{name.inspect}, #{opts.inspect[1...-1]}"
181
119
  send macro, name, opts.dup
182
120
  end
183
121
  end
184
122
 
123
+ def _determine_association_names(reference_name, referencing_name, references_name)
124
+
125
+ references_concise = _concise_name(references_name, referencing_name)
126
+ referencing_concise = _concise_name(referencing_name, references_name)
127
+
128
+ if _use_concise_name?
129
+ references = references_concise
130
+ referencing = referencing_concise
131
+ else
132
+ references = references_name
133
+ referencing = referencing_name
134
+ end
135
+
136
+ case reference_name
137
+ when 'parent'
138
+ belongs_to = 'parent'
139
+ has_one = 'child'
140
+ has_many = 'children'
141
+
142
+ when references_name
143
+ belongs_to = references
144
+ has_one = referencing
145
+ has_many = referencing.pluralize
146
+
147
+ when /(.*)_#{references_name}$/, /(.*)_#{references_concise}$/
148
+ label = $1
149
+ belongs_to = "#{label}_#{references}"
150
+ has_one = "#{referencing}_as_#{label}"
151
+ has_many = "#{referencing.pluralize}_as_#{label}"
152
+
153
+ when /^#{references_name}_(.*)$/, /^#{references_concise}_(.*)$/
154
+ label = $1
155
+ belongs_to = "#{references}_#{label}"
156
+ has_one = "#{referencing}_as_#{label}"
157
+ has_many = "#{referencing.pluralize}_as_#{label}"
158
+
159
+ else
160
+ belongs_to = reference_name
161
+ has_one = "#{referencing}_as_#{reference_name}"
162
+ has_many = "#{referencing.pluralize}_as_#{reference_name}"
163
+ end
164
+
165
+ { :belongs_to => belongs_to.to_sym, :has_one => has_one.to_sym, :has_many => has_many.to_sym }
166
+ end
167
+
185
168
  def _concise_name(string, other) #:nodoc:
186
169
  case
187
170
  when string =~ /^#{other}_(.*)$/ then $1
@@ -1,3 +1,3 @@
1
1
  module SchemaAssociations
2
- VERSION = "0.1.0.pre3"
2
+ VERSION = "0.1.0"
3
3
  end
data/runspecs CHANGED
@@ -14,14 +14,23 @@ OptionParser.new do |opts|
14
14
  end
15
15
  end.parse!
16
16
 
17
- cmd = if options [:install]
18
- 'bundle update'
19
- else
20
- 'bundle update --local | grep rails \\n rake spec'
21
- end
17
+ cmds = if options [:install]
18
+ ['bundle update']
19
+ else
20
+ ['bundle update --local rails | grep rails', 'rake spec']
21
+ end
22
22
 
23
+ n = 1
24
+ total = RUBY_VERSIONS.size * RAILS_VERSIONS.size
23
25
  RUBY_VERSIONS.each do |ruby|
24
26
  RAILS_VERSIONS.each do |rails|
25
- system "echo 'PS1="" SCHEMA_ASSOCIATION_RAILS_VERSION=#{rails} \\n rvm use #{ruby} \\n #{cmd} \\n exit' | bash -i" or abort "aborting #{$0}"
27
+ puts "\n\n*** ruby version #{ruby} - rails version #{rails} [#{n} of #{total}]\n\n"
28
+ n += 1
29
+ allcmds = []
30
+ allcmds << "rvm use #{ruby}"
31
+ allcmds << "export SCHEMA_VALIDATIONS_RAILS_VERSION=#{rails}"
32
+ allcmds += cmds
33
+ allcmds << 'exit'
34
+ system %Q{echo '#{allcmds.join(' \n ')}' | bash -i} or abort "aborting #{$0}"
26
35
  end
27
36
  end
@@ -518,8 +518,8 @@ describe ActiveRecord::Base do
518
518
  begin
519
519
  SchemaAssociations.setup do |config|
520
520
  config.update_attributes(opts)
521
- yield
522
521
  end
522
+ yield
523
523
  ensure
524
524
  SchemaAssociations.config.update_attributes(save)
525
525
  end
metadata CHANGED
@@ -1,8 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schema_associations
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: 6
5
- version: 0.1.0.pre3
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
6
10
  platform: ruby
7
11
  authors:
8
12
  - Ronen Barzel
@@ -11,17 +15,18 @@ autorequire:
11
15
  bindir: bin
12
16
  cert_chain: []
13
17
 
14
- date: 2011-07-21 00:00:00 -07:00
18
+ date: 2011-07-26 00:00:00 +02:00
15
19
  default_executable:
16
20
  dependencies:
17
21
  - !ruby/object:Gem::Dependency
18
22
  name: schema_plus
19
23
  prerelease: false
20
24
  requirement: &id001 !ruby/object:Gem::Requirement
21
- none: false
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
25
30
  version: "0"
26
31
  type: :runtime
27
32
  version_requirements: *id001
@@ -29,10 +34,13 @@ dependencies:
29
34
  name: rake
30
35
  prerelease: false
31
36
  requirement: &id002 !ruby/object:Gem::Requirement
32
- none: false
33
37
  requirements:
34
38
  - - ~>
35
39
  - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ - 8
43
+ - 7
36
44
  version: 0.8.7
37
45
  type: :development
38
46
  version_requirements: *id002
@@ -40,10 +48,11 @@ dependencies:
40
48
  name: rspec
41
49
  prerelease: false
42
50
  requirement: &id003 !ruby/object:Gem::Requirement
43
- none: false
44
51
  requirements:
45
52
  - - ">="
46
53
  - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
47
56
  version: "0"
48
57
  type: :development
49
58
  version_requirements: *id003
@@ -51,10 +60,11 @@ dependencies:
51
60
  name: sqlite3
52
61
  prerelease: false
53
62
  requirement: &id004 !ruby/object:Gem::Requirement
54
- none: false
55
63
  requirements:
56
64
  - - ">="
57
65
  - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
58
68
  version: "0"
59
69
  type: :development
60
70
  version_requirements: *id004
@@ -62,10 +72,11 @@ dependencies:
62
72
  name: simplecov
63
73
  prerelease: false
64
74
  requirement: &id005 !ruby/object:Gem::Requirement
65
- none: false
66
75
  requirements:
67
76
  - - ">="
68
77
  - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
69
80
  version: "0"
70
81
  type: :development
71
82
  version_requirements: *id005
@@ -73,24 +84,14 @@ dependencies:
73
84
  name: simplecov-gem-adapter
74
85
  prerelease: false
75
86
  requirement: &id006 !ruby/object:Gem::Requirement
76
- none: false
77
87
  requirements:
78
88
  - - ">="
79
89
  - !ruby/object:Gem::Version
90
+ segments:
91
+ - 0
80
92
  version: "0"
81
93
  type: :development
82
94
  version_requirements: *id006
83
- - !ruby/object:Gem::Dependency
84
- name: ruby-debug19
85
- prerelease: false
86
- requirement: &id007 !ruby/object:Gem::Requirement
87
- none: false
88
- requirements:
89
- - - ">="
90
- - !ruby/object:Gem::Version
91
- version: "0"
92
- type: :development
93
- version_requirements: *id007
94
95
  description: SchemaAssociations extends ActiveRecord to automatically create associations by inspecting the database schema. This is more more DRY than the standard behavior, for which in addition to specifying the foreign key in the migration, you must also specify complementary associations in two model files (e.g. a :belongs_to and a :has_many).
95
96
  email:
96
97
  - ronen@barzel.org
@@ -127,25 +128,25 @@ rdoc_options: []
127
128
  require_paths:
128
129
  - lib
129
130
  required_ruby_version: !ruby/object:Gem::Requirement
130
- none: false
131
131
  requirements:
132
132
  - - ">="
133
133
  - !ruby/object:Gem::Version
134
+ segments:
135
+ - 0
134
136
  version: "0"
135
137
  required_rubygems_version: !ruby/object:Gem::Requirement
136
- none: false
137
138
  requirements:
138
- - - ">"
139
+ - - ">="
139
140
  - !ruby/object:Gem::Version
140
- version: 1.3.1
141
+ segments:
142
+ - 0
143
+ version: "0"
141
144
  requirements: []
142
145
 
143
146
  rubyforge_project: schema_associations
144
- rubygems_version: 1.6.2
147
+ rubygems_version: 1.3.6
145
148
  signing_key:
146
149
  specification_version: 3
147
150
  summary: ActiveRecord extension that automatically (DRY) creates associations based on the schema
148
- test_files:
149
- - spec/association_spec.rb
150
- - spec/connection.rb
151
- - spec/spec_helper.rb
151
+ test_files: []
152
+