immigrant 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jon Jensen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,40 @@
1
+ = Immigrant
2
+
3
+ Immigrant gives {Foreigner}[https://github.com/matthuhiggins/foreigner] a
4
+ migration generator so you can effortlessly add missing foreign keys. This is
5
+ particularly helpful when you decide to add keys to an established Rails app.
6
+
7
+ Like Foreigner, Immigrant requires Rails 3.0 or greater.
8
+
9
+ == Installation
10
+
11
+ Add the following to your Gemfile:
12
+
13
+ gem 'immigrant'
14
+
15
+ == Usage
16
+
17
+ rails generate immigration AddKeys
18
+
19
+ This will create a migration named AddKeys which will have add_foreign_key
20
+ statements for any missing foreign keys. Immigrant infers missing ones by
21
+ evaluating the associations in your models (e.g. belongs_to, has_many, etc.).
22
+ Only missing keys will be added; existing ones will never be altered or
23
+ removed.
24
+
25
+ == Considerations
26
+
27
+ If the data in your tables is bad, then the migration will fail to run
28
+ (obviously). IOW, ensure you don't have orphaned records *before* you try to
29
+ add foreign keys.
30
+
31
+ == Known Issues
32
+
33
+ Immigrant currently only looks for foreign keys in ActiveRecord::Base's
34
+ database. So if a model is using a different database connection and it has
35
+ foreign keys, Immigrant will incorrectly include them again in the generated
36
+ migration.
37
+
38
+ == License
39
+
40
+ Copyright (c) 2012 Jon Jensen, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rake'
2
+
3
+ desc 'Default: run unit tests.'
4
+ task :default => :test
5
+
6
+ require 'rake/testtask'
7
+ desc 'Test the immigrant plugin.'
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'lib'
10
+ t.libs << 'test'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Creates a new foreign key migration based on your current associations.
3
+ Pass the migration name, either CamelCased or under_scored.
4
+
5
+ A migration class is generated in db/migrate prefixed by a timestamp of the
6
+ current date and time. It will contain add_foreign_key calls to create any
7
+ foreign keys that do not already exist (inferred from your model
8
+ associations and current foreign keys in the database). If there are no
9
+ missing foreign keys, no migration will be created.
10
+
11
+ Example:
12
+ `rails generate immigration AddMissingForeignKeys`
13
+
14
+ If the current date is Apr 1, 2012 and the current time 02:03:04, this
15
+ creates the AddMissingForeignKeys migration
16
+ db/migrate/20120401020304_add_missing_foreign_keys.rb with appropriate
17
+ add_foreign_key calls in the Change migration. If on Rails < 3.1, they
18
+ will be in the Up migration, and corresponding remove_foreign_key calls
19
+ will be in the Down migration.
@@ -0,0 +1,20 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ class ImmigrationGenerator < ActiveRecord::Generators::Base
4
+ def create_immigration_file
5
+ @keys, warnings = Immigrant.infer_keys
6
+ warnings.values.each{ |warning| $stderr.puts "WARNING: #{warning}" }
7
+ @keys.each do |key|
8
+ next unless key.options[:dependent] == :delete
9
+ $stderr.puts "NOTICE: #{key.options[:name]} has ON DELETE CASCADE. You should remove the :dependent option from the association to take advantage of this."
10
+ end
11
+ if @keys.present?
12
+ template = ActiveRecord::VERSION::STRING < "3.1." ? "immigration-pre-3.1.rb" : "immigration.rb"
13
+ migration_template template, "db/migrate/#{file_name}.rb"
14
+ else
15
+ puts "Nothing to do"
16
+ end
17
+ end
18
+
19
+ source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
20
+ end
@@ -0,0 +1,13 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ <% @keys.each do |key| -%>
4
+ <%= key.to_ruby(:add) %>
5
+ <%- end -%>
6
+ end
7
+
8
+ def self.down
9
+ <% @keys.each do |key| -%>
10
+ <%= key.to_ruby(:remove) %>
11
+ <%- end -%>
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ <% @keys.each do |key| -%>
4
+ <%= key.to_ruby(:add) %>
5
+ <%- end -%>
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module Immigrant
2
+ # add some useful stuff to foreigner's ForeignKeyDefinition
3
+ # TODO: get it in foreigner so we don't need to monkey patch
4
+ module ForeignKeyDefinition
5
+ def initialize(from_table, to_table, options, *args)
6
+ options ||= {}
7
+ options[:name] ||= "#{from_table}_#{options[:column]}_fk"
8
+ super(from_table, to_table, options, *args)
9
+ end
10
+
11
+ def hash_key
12
+ [from_table, options[:column]]
13
+ end
14
+
15
+ def to_ruby(action = :add)
16
+ if action == :add
17
+ # not DRY ... guts of this are copied from Foreigner :(
18
+ parts = [ ('add_foreign_key ' + from_table.inspect) ]
19
+ parts << to_table.inspect
20
+ parts << (':name => ' + options[:name].inspect)
21
+
22
+ if options[:column] != "#{to_table.singularize}_id"
23
+ parts << (':column => ' + options[:column].inspect)
24
+ end
25
+ if options[:primary_key] != 'id'
26
+ parts << (':primary_key => ' + options[:primary_key].inspect)
27
+ end
28
+ if options[:dependent].present?
29
+ parts << (':dependent => ' + options[:dependent].inspect)
30
+ end
31
+ parts.join(', ')
32
+ else
33
+ "remove_foreign_key #{from_table.inspect}, " \
34
+ ":name => #{options[:name].inspect}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ module Immigrant
2
+ def self.load
3
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.instance_eval do
4
+ include Immigrant::ForeignKeyDefinition
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Immigrant
2
+ class Railtie < Rails::Railtie
3
+ initializer 'immigrant.load' do
4
+ # TODO: implement hook in Foreigner and use that instead
5
+ ActiveSupport.on_load :active_record do
6
+ Immigrant.load
7
+ end
8
+ end
9
+ generators do
10
+ require "generators/immigration_generator"
11
+ end
12
+ end
13
+ end
data/lib/immigrant.rb ADDED
@@ -0,0 +1,141 @@
1
+ require 'active_support/all'
2
+ require 'foreigner'
3
+
4
+ module Immigrant
5
+ extend ActiveSupport::Autoload
6
+ autoload :Loader
7
+ autoload :ForeignKeyDefinition
8
+
9
+ class << self
10
+ def infer_keys(db_keys = current_foreign_keys, classes = model_classes)
11
+ database_keys = db_keys.inject({}) { |hash, foreign_key|
12
+ hash[foreign_key.hash_key] = foreign_key
13
+ hash
14
+ }
15
+ model_keys, warnings = model_keys(classes)
16
+ new_keys = []
17
+ model_keys.keys.each do |hash_key|
18
+ foreign_key = model_keys[hash_key]
19
+ # if the foreign key exists in the db, we call it good (even if
20
+ # the name is different or :dependent doesn't match), though
21
+ # we do warn on clearly broken stuff
22
+ if current_key = database_keys[hash_key]
23
+ if current_key.to_table != foreign_key.to_table || current_key.options[:primary_key] != foreign_key.options[:primary_key]
24
+ warnings[hash_key] = "Skipping #{foreign_key.from_table}.#{foreign_key.options[:column]}: its association references a different key/table than its current foreign key"
25
+ end
26
+ else
27
+ new_keys << foreign_key
28
+ end
29
+ end
30
+ [new_keys.sort_by{ |key| key.options[:name] }, warnings]
31
+ end
32
+
33
+ private
34
+
35
+ def current_foreign_keys
36
+ ActiveRecord::Base.connection.tables.map{ |table|
37
+ ActiveRecord::Base.connection.foreign_keys(table)
38
+ }.flatten
39
+ end
40
+
41
+ def model_classes
42
+ classes = []
43
+ pattern = /^\s*(has_one|has_many|has_and_belongs_to_many|belongs_to)\s/
44
+ Dir['app/models/*.rb'].each do |model|
45
+ class_name = model.sub(/\A.*\/(.*?)\.rb\z/, '\1').camelize
46
+ begin
47
+ klass = class_name.constantize
48
+ rescue SyntaxError, LoadError
49
+ if File.read(model) =~ pattern
50
+ raise "unable to load #{class_name} and its associations"
51
+ end
52
+ next
53
+ end
54
+ classes << klass if klass < ActiveRecord::Base
55
+ end
56
+ classes
57
+ end
58
+
59
+ def model_keys(classes)
60
+ # see what the models say there should be
61
+ foreign_keys = {}
62
+ warnings = {}
63
+ classes.map{ |klass|
64
+ foreign_keys_for(klass)
65
+ }.flatten.uniq.each do |foreign_key|
66
+ # we may have inferred it from several different places, e.g.
67
+ # Bar.belongs_to :foo
68
+ # Foo.has_many :bars
69
+ # Foo.has_many :bazzes, :class_name => Bar
70
+ # we need to make sure everything is legit and see if any of them
71
+ # specify :dependent => :delete
72
+ if current_key = foreign_keys[foreign_key.hash_key]
73
+ if current_key.to_table != foreign_key.to_table || current_key.options[:primary_key] != foreign_key.options[:primary_key]
74
+ warnings[foreign_key.hash_key] ||= "Skipping #{foreign_key.from_table}.#{foreign_key.options[:column]}: it has multiple associations referencing different keys/tables."
75
+ next
76
+ else
77
+ next unless foreign_key.options[:dependent]
78
+ end
79
+ end
80
+ foreign_keys[foreign_key.hash_key] = foreign_key
81
+ end
82
+ warnings.keys.each { |hash_key| foreign_keys.delete(hash_key) }
83
+ [foreign_keys, warnings]
84
+ end
85
+
86
+ def foreign_keys_for(klass)
87
+ fk_method = ActiveRecord::VERSION::STRING < '3.1.' ? :primary_key_name : :foreign_key
88
+
89
+ klass.reflections.values.reject{ |reflection|
90
+ # some associations can just be ignored, since:
91
+ # 1. we aren't going to parse SQL
92
+ # 2. foreign keys for :through associations will be handled by their
93
+ # component has_one/has_many/belongs_to associations
94
+ # 3. :polymorphic(/:as) associations can't have foreign keys
95
+ (reflection.options.keys & [:finder_sql, :through, :polymorphic, :as]).present?
96
+ }.map { |reflection|
97
+ begin
98
+ case reflection.macro
99
+ when :belongs_to
100
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
101
+ klass.table_name, reflection.klass.table_name,
102
+ :column => reflection.send(fk_method).to_s,
103
+ :primary_key => reflection.klass.primary_key.to_s,
104
+ # although belongs_to can specify :dependent, it doesn't make
105
+ # sense from a foreign key perspective
106
+ :dependent => nil
107
+ )
108
+ when :has_one, :has_many
109
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
110
+ reflection.klass.table_name, klass.table_name,
111
+ :column => reflection.send(fk_method).to_s,
112
+ :primary_key => klass.primary_key.to_s,
113
+ :dependent => [:delete, :delete_all].include?(reflection.options[:dependent]) && reflection.options[:conditions].nil? ? :delete : nil
114
+ )
115
+ when :has_and_belongs_to_many
116
+ [
117
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
118
+ reflection.options[:join_table], klass.table_name,
119
+ :column => reflection.send(fk_method).to_s,
120
+ :primary_key => klass.primary_key.to_s,
121
+ :dependent => nil
122
+ ),
123
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
124
+ reflection.options[:join_table], reflection.klass.table_name,
125
+ :column => reflection.association_foreign_key.to_s,
126
+ :primary_key => reflection.klass.primary_key.to_s,
127
+ :dependent => nil
128
+ )
129
+ ]
130
+ end
131
+ rescue NameError # e.g. belongs_to :oops_this_is_not_a_table
132
+ []
133
+ end
134
+ }.flatten
135
+ end
136
+
137
+ end
138
+ end
139
+
140
+ require 'immigrant/loader'
141
+ require 'immigrant/railtie' if defined?(Rails)
data/test/helper.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:default)
3
+
4
+ require 'test/unit'
5
+ require 'active_record'
6
+
7
+ require 'foreigner'
8
+ class Foreigner::Adapter
9
+ def self.configured_name; "dummy_adapter"; end
10
+ end
11
+ Foreigner.load
12
+
13
+ require 'immigrant'
14
+ Immigrant.load
15
+
16
+ module TestMethods
17
+ def foreign_key_definition(*args)
18
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(*args)
19
+ end
20
+ end
@@ -0,0 +1,314 @@
1
+ require 'helper'
2
+
3
+ class ImmigrantTest < ActiveSupport::TestCase
4
+ include TestMethods
5
+
6
+ class MockModel < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ class << self
9
+ def primary_key
10
+ connection.primary_key(table_name)
11
+ end
12
+ def connection
13
+ @connection ||= MockConnection.new
14
+ end
15
+ end
16
+ end
17
+
18
+ class MockConnection
19
+ def supports_primary_key? # AR <3.2
20
+ true
21
+ end
22
+ def primary_key(table)
23
+ table !~ /s_.*s\z/ ? 'id' : nil
24
+ end
25
+ end
26
+
27
+ def teardown
28
+ subclasses = ActiveSupport::DescendantsTracker.direct_descendants(MockModel)
29
+ subclasses.each do |subclass|
30
+ ImmigrantTest.send(:remove_const, subclass.to_s.sub(/.*::/, ''))
31
+ end
32
+ subclasses.replace([])
33
+ end
34
+
35
+
36
+ # basic scenarios
37
+
38
+ test 'belongs_to should generate a foreign key' do
39
+ class Author < MockModel; end
40
+ class Book < MockModel
41
+ belongs_to :guy, :class_name => 'Author', :foreign_key => 'author_id'
42
+ end
43
+
44
+ assert_equal(
45
+ [foreign_key_definition(
46
+ 'books', 'authors',
47
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
48
+ )],
49
+ Immigrant.infer_keys([], [Author, Book]).first
50
+ )
51
+ end
52
+
53
+ test 'has_one should generate a foreign key' do
54
+ class Author < MockModel
55
+ has_one :piece_de_resistance, :class_name => 'Book', :order => "id DESC"
56
+ end
57
+ class Book < MockModel; end
58
+
59
+ assert_equal(
60
+ [foreign_key_definition(
61
+ 'books', 'authors',
62
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
63
+ )],
64
+ Immigrant.infer_keys([], [Author, Book]).first
65
+ )
66
+ end
67
+
68
+ test 'has_one :dependent => :delete should generate a foreign key with :dependent => :delete' do
69
+ class Author < MockModel
70
+ has_one :book, :order => "id DESC", :dependent => :delete
71
+ end
72
+ class Book < MockModel; end
73
+
74
+ assert_equal(
75
+ [foreign_key_definition(
76
+ 'books', 'authors',
77
+ :column => 'author_id', :primary_key => 'id', :dependent => :delete
78
+ )],
79
+ Immigrant.infer_keys([], [Author, Book]).first
80
+ )
81
+ end
82
+
83
+ test 'has_many should generate a foreign key' do
84
+ class Author < MockModel
85
+ has_many :babies, :class_name => 'Book'
86
+ end
87
+ class Book < MockModel; end
88
+
89
+ assert_equal(
90
+ [foreign_key_definition(
91
+ 'books', 'authors',
92
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
93
+ )],
94
+ Immigrant.infer_keys([], [Author, Book]).first
95
+ )
96
+ end
97
+
98
+ test 'has_many :dependent => :delete_all should generate a foreign key with :dependent => :delete' do
99
+ class Author < MockModel
100
+ has_many :books, :dependent => :delete_all
101
+ end
102
+ class Book < MockModel; end
103
+
104
+ assert_equal(
105
+ [foreign_key_definition(
106
+ 'books', 'authors',
107
+ :column => 'author_id', :primary_key => 'id', :dependent => :delete
108
+ )],
109
+ Immigrant.infer_keys([], [Author, Book]).first
110
+ )
111
+ end
112
+
113
+ test 'has_and_belongs_to_many should generate two foreign keys' do
114
+ class Author < MockModel
115
+ has_and_belongs_to_many :fans
116
+ end
117
+ class Fan < MockModel; end
118
+
119
+ assert_equal(
120
+ [foreign_key_definition(
121
+ 'authors_fans', 'authors',
122
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
123
+ ),
124
+ foreign_key_definition(
125
+ 'authors_fans', 'fans',
126
+ :column => 'fan_id', :primary_key => 'id', :dependent => nil
127
+ )],
128
+ Immigrant.infer_keys([], [Author, Fan]).first
129
+ )
130
+ end
131
+
132
+ test 'conditional has_one/has_many associations should ignore :dependent' do
133
+ class Author < MockModel
134
+ has_many :articles, :conditions => "published", :dependent => :delete_all
135
+ has_one :favorite_book, :class_name => 'Book',
136
+ :conditions => "most_awesome", :dependent => :delete
137
+ end
138
+ class Book < MockModel; end
139
+ class Article < MockModel; end
140
+
141
+ assert_equal(
142
+ [foreign_key_definition(
143
+ 'articles', 'authors',
144
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
145
+ ),
146
+ foreign_key_definition(
147
+ 'books', 'authors',
148
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
149
+ )],
150
+ Immigrant.infer_keys([], [Article, Author, Book]).first
151
+ )
152
+ end
153
+
154
+
155
+ # (no) duplication
156
+
157
+ test 'STI should not generate duplicate foreign keys' do
158
+ class Company < MockModel; end
159
+ class Employee < MockModel
160
+ belongs_to :company
161
+ end
162
+ class Manager < Employee; end
163
+
164
+ assert(Manager.reflections.present?)
165
+ assert_equal(
166
+ [foreign_key_definition(
167
+ 'employees', 'companies',
168
+ :column => 'company_id', :primary_key => 'id', :dependent => nil
169
+ )],
170
+ Immigrant.infer_keys([], [Company, Employee, Manager]).first
171
+ )
172
+ end
173
+
174
+ test 'complementary associations should not generate duplicate foreign keys' do
175
+ class Author < MockModel
176
+ has_many :books
177
+ end
178
+ class Book < MockModel
179
+ belongs_to :author
180
+ end
181
+
182
+ assert_equal(
183
+ [foreign_key_definition(
184
+ 'books', 'authors',
185
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
186
+ )],
187
+ Immigrant.infer_keys([], [Author, Book]).first
188
+ )
189
+ end
190
+
191
+ test 'redundant associations should not generate duplicate foreign keys' do
192
+ class Author < MockModel
193
+ has_many :books
194
+ has_many :favorite_books, :class_name => 'Book', :conditions => "awesome"
195
+ has_many :bad_books, :class_name => 'Book', :conditions => "amateur_hour"
196
+ end
197
+ class Book < MockModel; end
198
+
199
+ assert_equal(
200
+ [foreign_key_definition(
201
+ 'books', 'authors',
202
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
203
+ )],
204
+ Immigrant.infer_keys([], [Author, Book]).first
205
+ )
206
+ end
207
+
208
+
209
+ # skipped associations
210
+
211
+ test 'associations should not generate foreign keys if they already exist, even if :dependent/name are different' do
212
+ database_keys = [
213
+ foreign_key_definition(
214
+ 'articles', 'authors',
215
+ :column => 'author_id', :primary_key => 'id', :dependent => nil,
216
+ :name => "doesn't_matter"
217
+ ),
218
+ foreign_key_definition(
219
+ 'books', 'authors', :column => 'author_id', :primary_key => 'id',
220
+ :dependent => :delete
221
+ )
222
+ ]
223
+
224
+ class Author < MockModel
225
+ has_many :articles
226
+ has_one :favorite_book, :class_name => 'Book',
227
+ :conditions => "most_awesome"
228
+ end
229
+ class Book < MockModel; end
230
+ class Article < MockModel; end
231
+
232
+ assert_equal(
233
+ [],
234
+ Immigrant.infer_keys(database_keys, [Article, Author, Book]).first
235
+ )
236
+ end
237
+
238
+ test 'finder_sql associations should not generate foreign keys' do
239
+ class Author < MockModel
240
+ has_many :books, :finder_sql => <<-SQL
241
+ SELECT *
242
+ FROM books
243
+ WHERE author_id = \#{id}
244
+ ORDER BY RANDOM() LIMIT 5'
245
+ SQL
246
+ end
247
+ class Book < MockModel; end
248
+
249
+ assert_equal(
250
+ [],
251
+ Immigrant.infer_keys([], [Author, Book]).first
252
+ )
253
+ end
254
+
255
+ test 'polymorphic associations should not generate foreign keys' do
256
+ class Property < MockModel
257
+ belongs_to :owner, :polymorphic => true
258
+ end
259
+ class Person < MockModel
260
+ has_many :properties, :as => :owner
261
+ end
262
+ class Corporation < MockModel
263
+ has_many :properties, :as => :owner
264
+ end
265
+
266
+ assert_equal(
267
+ [],
268
+ Immigrant.infer_keys([], [Corporation, Person, Property]).first
269
+ )
270
+ end
271
+
272
+ test 'has_many :through should not generate foreign keys' do
273
+ class Author < MockModel
274
+ has_many :authors_fans
275
+ has_many :fans, :through => :authors_fans
276
+ end
277
+ class AuthorsFan < MockModel
278
+ belongs_to :author
279
+ belongs_to :fan
280
+ end
281
+ class Fan < MockModel
282
+ has_many :authors_fans
283
+ has_many :authors, :through => :authors_fans
284
+ end
285
+
286
+ assert_equal(
287
+ [foreign_key_definition(
288
+ 'authors_fans', 'authors',
289
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
290
+ ),
291
+ foreign_key_definition(
292
+ 'authors_fans', 'fans',
293
+ :column => 'fan_id', :primary_key => 'id', :dependent => nil
294
+ )],
295
+ Immigrant.infer_keys([], [Author, AuthorsFan, Fan]).first
296
+ )
297
+ end
298
+
299
+ test 'broken associations should not cause errors' do
300
+ class Author < MockModel; end
301
+ class Book < MockModel
302
+ belongs_to :author
303
+ belongs_to :invalid
304
+ end
305
+
306
+ assert_equal(
307
+ [foreign_key_definition(
308
+ 'books', 'authors',
309
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
310
+ )],
311
+ Immigrant.infer_keys([], [Author, Book]).first
312
+ )
313
+ end
314
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: immigrant
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jon Jensen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-01 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ version: "3.0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: foreigner
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 21
45
+ segments:
46
+ - 1
47
+ - 1
48
+ - 3
49
+ version: 1.1.3
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Adds a generator for creating a foreign key migration based on your current model associations
53
+ email: jenseng@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.rdoc
60
+ files:
61
+ - LICENSE.txt
62
+ - Rakefile
63
+ - README.rdoc
64
+ - lib/generators/USAGE
65
+ - lib/generators/immigration_generator.rb
66
+ - lib/generators/templates/immigration-pre-3.1.rb
67
+ - lib/generators/templates/immigration.rb
68
+ - lib/immigrant/foreign_key_definition.rb
69
+ - lib/immigrant/loader.rb
70
+ - lib/immigrant/railtie.rb
71
+ - lib/immigrant.rb
72
+ - test/helper.rb
73
+ - test/immigrant_test.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/jenseng/immigrant
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 57
89
+ segments:
90
+ - 1
91
+ - 8
92
+ - 7
93
+ version: 1.8.7
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 17
100
+ segments:
101
+ - 1
102
+ - 3
103
+ - 5
104
+ version: 1.3.5
105
+ requirements: []
106
+
107
+ rubyforge_project:
108
+ rubygems_version: 1.6.2
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Migration generator for Foreigner
112
+ test_files: []
113
+