ackbar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ ###############################################################################
2
+ # DB Schema Migration for testing ActiveKirby
3
+ require 'active_record/migration'
4
+
5
+ class SchemaMigrationTest < ActiveRecord::Migration
6
+ def self.up
7
+ create_table "pages", :force => true do |t|
8
+ t.column "book_id", :integer
9
+ t.column "page_num", :integer
10
+ t.column "content", :text
11
+ end
12
+
13
+ remove_index(:delete_me_in_migration, :junk)
14
+ rename_column(:delete_me_in_migration, :more_junk, :less_junk)
15
+ change_column(:delete_me_in_migration, :junk, :string)
16
+ remove_column(:delete_me_in_migration, :junk_yard)
17
+
18
+ drop_table(:delete_me_in_migration)
19
+
20
+ add_column(:publishers, :address, :string)
21
+ add_index(:publishers, :name, :unique)
22
+
23
+ # test belong_to (book) and has_one (book)
24
+ create_table 'errata', :force => true do |t|
25
+ t.column 'book_id', :integer
26
+ t.column 'contents', :string
27
+ end
28
+ end
29
+
30
+ def self.down
31
+ end
32
+ end
@@ -0,0 +1,415 @@
1
+ require 'test_helper'
2
+
3
+ @@AR_PATH = Gem.latest_load_paths.grep(/activerecord/)[0]
4
+ @@AR_TESTS_PATH = File.expand_path(File.join(@@AR_PATH, '../test'))
5
+ $LOAD_PATH << @@AR_TESTS_PATH
6
+
7
+ ################################################################################
8
+ ### Start requiring ActiveRecords test files ###################################
9
+ # There are 663 tests, 2188 assertions in the AR test suite (sqlite)
10
+
11
+ ################################################################################
12
+ # ActiveRecord::Base basics tests
13
+ require File.join(@@AR_TESTS_PATH, 'base_test.rb')
14
+ class BasicsTest
15
+ # too much SQL
16
+ remove_method :test_count_with_join
17
+ end
18
+
19
+ ################################################################################
20
+ # ActiveRecord::Associations tests
21
+ require File.join(@@AR_TESTS_PATH, 'associations_test.rb')
22
+
23
+ class AssociationsTest
24
+ # We use code blocks (procs) which can't be dumped with Marshal
25
+ remove_method :test_storing_in_pstore
26
+ end
27
+
28
+ class HasAndBelongsToManyAssociationsTest
29
+ # Joins not supported
30
+ remove_method :test_adding_uses_default_values_on_join_table
31
+ remove_method :test_adding_uses_explicit_values_on_join_table
32
+ remove_method :test_additional_columns_from_join_table
33
+ end
34
+
35
+ class HasManyAssociationsTest
36
+ # :group option (GROUP BY statement) not supported
37
+ remove_method :test_find_grouped
38
+ # transactions not supported
39
+ remove_method :test_dependence_with_transaction_support_on_failure
40
+ end
41
+
42
+ class HasAndBelongsToManyAssociationsTest
43
+ # just changes the last select count to work through KB select blocks
44
+ def test_update_attributes_after_push_without_duplicate_join_table_rows
45
+ developer = Developer.new("name" => "Kano")
46
+ project = SpecialProject.create("name" => "Special Project")
47
+ assert developer.save
48
+ developer.projects << project
49
+ developer.update_attribute("name", "Bruza")
50
+ # assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
51
+ # SELECT count(*) FROM developers_projects
52
+ # WHERE project_id = #{project.id}
53
+ # AND developer_id = #{developer.id}
54
+ # end_sql
55
+ num_rows = Developer.connection.db.get_table(:developers_projects).select do |rec|
56
+ rec.project_id == project.id and rec.developer_id == developer.id
57
+ end.size
58
+ assert_equal 1, num_rows
59
+ end
60
+
61
+ def test_removing_associations_on_destroy
62
+ david = DeveloperWithBeforeDestroyRaise.find(1)
63
+ assert !david.projects.empty?
64
+ assert_nothing_raised { david.destroy }
65
+ assert david.projects.empty?
66
+ assert DeveloperWithBeforeDestroyRaise.connection.db.get_table(:developers_projects).select{|rec| rec.developer_id == 1}.empty?
67
+ end
68
+
69
+ end
70
+
71
+ require File.join(@@AR_TESTS_PATH, 'associations_extensions_test.rb')
72
+
73
+ class AssociationsExtensionsTest
74
+ # Procs can't be marshalled
75
+ remove_method :test_marshalling_extensions
76
+ remove_method :test_marshalling_named_extensions
77
+ end
78
+
79
+ require File.join(@@AR_TESTS_PATH, 'deprecated_associations_test.rb')
80
+
81
+ class DeprecatedAssociationsTest
82
+ remove_method :test_has_many_dependence_with_transaction_support_on_failure
83
+ remove_method :test_storing_in_pstore
84
+ end
85
+
86
+ ################################################################################
87
+ # Finder tests
88
+ require File.join(@@AR_TESTS_PATH, 'finder_test.rb')
89
+
90
+ class FinderTest
91
+ # we don't allow full SQL, but might as well check the block format
92
+ def test_count_by_sql
93
+ assert_raises(ActiveRecord::StatementInvalid) { Entrant.count_by_sql("SELECT COUNT(*) FROM entrant") }
94
+ assert_equal 0, Entrant.count(lambda{|rec| rec.recno > 3})
95
+ # this is just too wierd: assert_equal 1, Entrant.count([lambda{|rec| rec.recno > 2}])
96
+ assert_equal 2, Entrant.count{|rec| rec.recno > 1}
97
+ end
98
+ remove_method :test_find_with_entire_select_statement
99
+ remove_method :test_find_with_prepared_select_statement
100
+ remove_method :test_select_value
101
+ remove_method :test_select_values
102
+ remove_method :test_find_all_with_join
103
+ end
104
+
105
+ require File.join(@@AR_TESTS_PATH, 'deprecated_finder_test.rb')
106
+
107
+ class DeprecatedFinderTest
108
+ remove_method :test_count_by_sql
109
+ end
110
+
111
+ ################################################################################
112
+ # Schema & Migrations tests
113
+
114
+ require File.join(@@AR_TESTS_PATH, 'ar_schema_test.rb')
115
+
116
+ class ActiveRecordSchemaTest
117
+ def test_schema_define
118
+ ActiveRecord::Schema.define(:version => 7) do
119
+ create_table :fruits do |t|
120
+ t.column :color, :string
121
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
122
+ t.column :texture, :string
123
+ t.column :flavor, :string
124
+ end
125
+ end
126
+
127
+ assert_nothing_raised { @connection.get_table(:fruits).select }
128
+ assert_nothing_raised { @connection.get_table(:schema_info).select }
129
+ assert_equal 7, @connection.get_table(:schema_info).select[0].version
130
+ end
131
+ end
132
+
133
+ require File.join(@@AR_TESTS_PATH, 'migration_test.rb')
134
+
135
+ class MigrationTest
136
+ def teardown
137
+ ActiveRecord::Base.connection.initialize_schema_information
138
+ ActiveRecord::Base.connection.get_table(:schema_info).update_all {|rec| rec.version = 0}
139
+
140
+ Reminder.connection.drop_table("reminders") rescue nil
141
+ Reminder.connection.drop_table("people_reminders") rescue nil
142
+ Reminder.connection.drop_table("prefix_reminders_suffix") rescue nil
143
+ Reminder.reset_column_information
144
+
145
+ Person.connection.remove_column("people", "last_name") rescue nil
146
+ Person.connection.remove_column("people", "bio") rescue nil
147
+ Person.connection.remove_column("people", "age") rescue nil
148
+ Person.connection.remove_column("people", "height") rescue nil
149
+ Person.connection.remove_column("people", "birthday") rescue nil
150
+ Person.connection.remove_column("people", "favorite_day") rescue nil
151
+ Person.connection.remove_column("people", "male") rescue nil
152
+ Person.connection.remove_column("people", "administrator") rescue nil
153
+ Person.reset_column_information
154
+ end
155
+
156
+ def test_create_table_with_not_null_column
157
+ Person.connection.create_table :testings do |t|
158
+ t.column :foo, :string, :null => false
159
+ end
160
+
161
+ # ArgumentError and not ActiveRecord::StatementInvalid because we're inserting directly to the db.
162
+ # Still showns that this field is required
163
+ assert_raises(ArgumentError) do
164
+ Person.connection.get_table(:testings).insert :foo => nil
165
+ end
166
+ ensure
167
+ Person.connection.drop_table :testings rescue nil
168
+ end
169
+
170
+ def test_add_column_not_null_with_default
171
+ Person.connection.create_table :testings do |t|
172
+ t.column :foo, :string
173
+ end
174
+ Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default"
175
+
176
+ # changed from ActiveRecord::StatementInvalid as we're operating directly on
177
+ # the database, and that is what KB spits out. Still, it validates that the
178
+ # field is now required (not null)
179
+ assert_raises(ArgumentError) do
180
+ Person.connection.get_table(:testings).insert :foo => 'hello', :bar => nil
181
+ end
182
+ ensure
183
+ Person.connection.drop_table :testings rescue nil
184
+ end
185
+
186
+ def test_add_column_not_null_without_default
187
+ Person.connection.create_table :testings do |t|
188
+ t.column :foo, :string
189
+ end
190
+ Person.connection.add_column :testings, :bar, :string, :null => false
191
+
192
+ assert_raises(ArgumentError) do
193
+ Person.connection.get_table(:testings).insert :foo => 'hello', :bar => nil
194
+ end
195
+ ensure
196
+ Person.connection.drop_table :testings rescue nil
197
+ end
198
+
199
+ # KirbyBase only supports one index per column, so created new column
200
+ # (middle_name) for those tests. Also, KirbyBase does not support named
201
+ # indexes, so those tests were disabled.
202
+ def test_add_index
203
+ Person.connection.add_column "people", "last_name", :string
204
+ Person.connection.add_column "people", "middle_name", :string
205
+ Person.connection.add_column "people", "administrator", :boolean
206
+
207
+ assert_nothing_raised { Person.connection.add_index("people", "last_name") }
208
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
209
+
210
+ assert_nothing_raised { Person.connection.add_index("people", ["middle_name", "first_name"]) }
211
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
212
+
213
+ # assert_nothing_raised { Person.connection.add_index("people", %w(last_name middle_name administrator), :name => "named_admin") }
214
+ # assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
215
+ end
216
+
217
+ def test_rename_table
218
+ begin
219
+ ActiveRecord::Base.connection.create_table :octopuses do |t|
220
+ t.column :url, :string
221
+ end
222
+ ActiveRecord::Base.connection.rename_table :octopuses, :octopi
223
+
224
+ assert_nothing_raised do
225
+ ActiveRecord::Base.connection.get_table(:octopi).insert :url => 'http://www.foreverflying.com/octopus-black7.jpg'
226
+ end
227
+
228
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg',
229
+ ActiveRecord::Base.connection.get_table(:octopi).select{|r|r.recno == 1}.first.url
230
+
231
+ ensure
232
+ ActiveRecord::Base.connection.drop_table :octopuses rescue nil
233
+ ActiveRecord::Base.connection.drop_table :octopi rescue nil
234
+ end
235
+ end
236
+
237
+ # Since this test uses aboolean field with a default, we need to override the
238
+ # change_column statement to use FalseClass rather than 0.
239
+ # In real life migrations, this should be guarded with an if current_adapter ...
240
+ def test_change_column_with_new_default
241
+ Person.connection.add_column "people", "administrator", :boolean, :default => true
242
+ Person.reset_column_information
243
+ assert Person.new.administrator?
244
+
245
+ assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
246
+ Person.reset_column_information
247
+ assert !Person.new.administrator?
248
+ end
249
+ end
250
+
251
+ ################################################################################
252
+ # Misc tests
253
+
254
+ require File.join(@@AR_TESTS_PATH, 'inheritance_test.rb')
255
+
256
+ class InheritanceTest
257
+ # not supporting non-integer primary keys just yet
258
+ remove_method :test_inheritance_without_mapping
259
+
260
+ def test_a_bad_type_column
261
+ recno = Company.table.insert :mame => 'bad_class!', :type => 'Not happening'
262
+ assert_raises(ActiveRecord::SubclassNotFound) { Company.find(recno) }
263
+ end
264
+ end
265
+
266
+ require File.join(@@AR_TESTS_PATH, 'method_scoping_test.rb')
267
+
268
+ class MethodScopingTest
269
+ # changed the LIKE clause to ruby block
270
+ def test_scoped_count
271
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
272
+ assert_equal 1, Developer.count
273
+ end
274
+
275
+ Developer.with_scope(:find => { :conditions => 'salary = 100000' }) do
276
+ assert_equal 8, Developer.count
277
+ # assert_equal 1, Developer.count("name LIKE 'fixture_1%'")
278
+ assert_equal 1, Developer.count(lambda{|rec| rec.name =~ /fixture_1.*/})
279
+ end
280
+ end
281
+ end
282
+
283
+ class HasAndBelongsToManyScopingTest
284
+ # we don't use the nested scopes
285
+ remove_method :test_raise_on_nested_scope
286
+ end
287
+
288
+ require File.join(@@AR_TESTS_PATH, 'pk_test.rb')
289
+
290
+ # Strings not supported for primary keys
291
+ class PrimaryKeysTest
292
+ remove_method :test_string_key
293
+ remove_method :test_find_with_more_than_one_string_key
294
+ end
295
+
296
+ require File.join(@@AR_TESTS_PATH, 'reflection_test.rb')
297
+
298
+ # We don't keep limits on Strings (and others). Using plain Ruby types.
299
+ class ReflectionTest
300
+ remove_method :test_column_string_type_and_limit
301
+ end
302
+
303
+
304
+ ################################################################################
305
+ # Require all other test files not specifically handled above
306
+
307
+ ar_test_files = Dir[@@AR_TESTS_PATH + '/*_test.rb']
308
+ # Remove things we don't support
309
+ %w{
310
+ aaa_create_tables_test.rb
311
+ transactions_test.rb
312
+ associations_go_eager_test.rb
313
+ mixin_test.rb
314
+ mixin_nested_set_test.rb
315
+ }.each {|test_file| ar_test_files.delete File.join(@@AR_TESTS_PATH, test_file)}
316
+ ar_test_files.each {|test_file| require test_file}
317
+
318
+ class BinaryTest
319
+ def setup
320
+ Binary.table.clear
321
+ @data = File.read(BINARY_FIXTURE_PATH).freeze
322
+ end
323
+ end
324
+
325
+ class FixturesTest
326
+ def test_inserts
327
+ topics = create_fixtures("topics")
328
+ # firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
329
+ firstRow = ActiveRecord::Base.connection.db.get_table(:topics).select{|rec| rec.author_name == 'David'}.first
330
+ assert_equal("The First Topic", firstRow["title"])
331
+
332
+ # secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'Mary'")
333
+ secondRow = ActiveRecord::Base.connection.db.get_table(:topics).select{|rec| rec.author_name == 'Mary'}.first
334
+ assert_nil(secondRow["author_email_address"])
335
+ end
336
+
337
+ def test_inserts_with_pre_and_suffix
338
+ ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
339
+ t.column :title, :string
340
+ t.column :author_name, :string
341
+ t.column :author_email_address, :string
342
+ t.column :written_on, :datetime
343
+ t.column :bonus_time, :time
344
+ t.column :last_read, :date
345
+ t.column :content, :text
346
+ t.column :approved, :boolean, :default => 1
347
+ t.column :replies_count, :integer, :default => 0
348
+ t.column :parent_id, :integer
349
+ t.column :type, :string, :limit => 50
350
+ end
351
+
352
+ # Store existing prefix/suffix
353
+ old_prefix = ActiveRecord::Base.table_name_prefix
354
+ old_suffix = ActiveRecord::Base.table_name_suffix
355
+
356
+ # Set a prefix/suffix we can test against
357
+ ActiveRecord::Base.table_name_prefix = 'prefix_'
358
+ ActiveRecord::Base.table_name_suffix = '_suffix'
359
+
360
+ topics = create_fixtures("topics")
361
+
362
+ # Restore prefix/suffix to its previous values
363
+ ActiveRecord::Base.table_name_prefix = old_prefix
364
+ ActiveRecord::Base.table_name_suffix = old_suffix
365
+
366
+ # firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'")
367
+ firstRow = ActiveRecord::Base.connection.db.get_table(:prefix_topics_suffix).select{|rec| rec.author_name == 'David'}.first
368
+ assert_equal("The First Topic", firstRow["title"])
369
+
370
+ # secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'")
371
+ secondRow = ActiveRecord::Base.connection.db.get_table(:prefix_topics_suffix).select{|rec| rec.author_name == 'Mary'}.first
372
+ assert_nil(secondRow["author_email_address"])
373
+ ensure
374
+ ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil
375
+ end
376
+ end
377
+
378
+ # Too SQL specific
379
+ class TestColumnAlias
380
+ # can't remove_method, because it's the only one in the textcase
381
+ def test_column_alias() end
382
+ end
383
+
384
+ ################################################################################
385
+ # Introduce my adaptation of the model classes (override SQL with blocks)
386
+ require 'ar_model_adaptation'
387
+
388
+ # schema_test and schema_dumper_test require from relative URLs, which means they
389
+ # override my changes. So the changes are reintroduced here.
390
+ module ActiveRecord #:nodoc:
391
+ class Schema
392
+ def self.define(info={}, &block)
393
+ instance_eval(&block)
394
+
395
+ unless info.empty?
396
+ initialize_schema_information
397
+ ActiveRecord::Base.connection.get_table(ActiveRecord::Migrator.schema_info_table_name.to_sym).update_all(info)
398
+ end
399
+ end
400
+ end
401
+
402
+ class SchemaDumper
403
+ def initialize(connection)
404
+ @connection = connection
405
+ @types = @connection.native_database_types
406
+ @info = @connection.get_table(:schema_info).select[0] rescue nil
407
+ end
408
+ end
409
+ end
410
+
411
+ # This is required as KirbyBase does not support transactions.
412
+ ObjectSpace.each_object(Class) do |test|
413
+ test.use_transactional_fixtures = false if test < Test::Unit::TestCase
414
+ end
415
+
@@ -0,0 +1,98 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ module ClassMethods
4
+ def remove_association *names
5
+ if names.length == 1
6
+ assocs = @inheritable_attributes[:associations].select{ |a| a.name == names[0] }
7
+ assocs.each {|assoc| @inheritable_attributes[:associations].delete assoc }
8
+ else
9
+ names.each {|assoc| remove_association(assoc)}
10
+ end
11
+ end
12
+ def has_and_belongs_to_many_without_method_redefinition(association_id, options = {}, &extension)
13
+ options.assert_valid_keys(
14
+ :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :include,
15
+ :join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,
16
+ :before_remove, :after_remove, :extend
17
+ )
18
+
19
+ options[:extend] = create_extension_module(association_id, extension) if block_given?
20
+
21
+ association_name, association_class_name, association_class_primary_key_name =
22
+ associate_identification(association_id, options[:class_name], options[:foreign_key])
23
+
24
+ require_association_class(association_class_name)
25
+
26
+ options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name))
27
+
28
+ add_multiple_associated_save_callbacks(association_name)
29
+
30
+ collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation)
31
+
32
+ add_association_callbacks(association_name, options)
33
+
34
+ # deprecated api
35
+ deprecated_collection_count_method(association_name)
36
+ deprecated_add_association_relation(association_name)
37
+ deprecated_remove_association_relation(association_name)
38
+ deprecated_has_collection_method(association_name)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class Company < ActiveRecord::Base
45
+ set_sequence_name nil
46
+ end
47
+
48
+ class Firm
49
+ remove_association :clients, :clients_of_firm, :clients_using_sql, :clients_using_counter_sql,
50
+ :clients_using_zero_counter_sql, :no_clients_using_counter_sql
51
+
52
+ has_many :clients, :order => "id", :dependent => true, :counter_sql =>
53
+ lambda {|rec, firm| rec.firm_id == 1 and ['Client', 'SpecialClient', 'VerySpecialClient'].include?(rec.type) }
54
+ # "SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
55
+ # "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
56
+
57
+ has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
58
+ has_many :clients_using_sql, :class_name => "Client",
59
+ :finder_sql => lambda{|rec, firm| rec.client_of == firm.id} # 'SELECT * FROM companies WHERE client_of = #{id}'
60
+ has_many :clients_using_counter_sql, :class_name => "Client",
61
+ :finder_sql => lambda{|rec, firm| rec.client_of == firm.id}, # 'SELECT * FROM companies WHERE client_of = #{id}',
62
+ :counter_sql => lambda{|rec, firm| rec.client_of == firm.id} # 'SELECT COUNT(*) FROM companies WHERE client_of = #{id}'
63
+ has_many :clients_using_zero_counter_sql, :class_name => "Client",
64
+ :finder_sql => lambda{|rec, firm| rec.client_of == id}, # 'SELECT * FROM companies WHERE client_of = #{id}'
65
+ :counter_sql => lambda{|rec, firm| false } # 'SELECT 0 FROM companies WHERE client_of = #{id}'
66
+ has_many :no_clients_using_counter_sql, :class_name => "Client",
67
+ :finder_sql => lambda{|rec, firm| rec.client_of == 1000}, # 'SELECT * FROM companies WHERE client_of = 1000'
68
+ :counter_sql => lambda{|rec, firm| rec.client_of == 1000} # 'SELECT COUNT(*) FROM companies WHERE client_of = 1000'
69
+ end
70
+
71
+ class Client < Company
72
+ belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => "1 = 1"
73
+ end
74
+
75
+ # module MyApplication
76
+ # module Business
77
+ # class Firm
78
+ # has_many :clients_using_sql, :class_name => "Client",
79
+ # :finder_sql => lambda{|rec, firm| rec.client_of == firm.id} # 'SELECT * FROM companies WHERE client_of = #{id}'
80
+ # end
81
+ # end
82
+ # end
83
+
84
+ class Project
85
+ remove_association :developers_with_finder_sql, :developers_by_sql
86
+
87
+ ## Unfortunately calling habtm with the same name again will cause some method
88
+ # redefinition loop (see associations.rb for the dynamic method redefinition).
89
+ # So we have to go through loops to redefine the exact methods.
90
+ has_and_belongs_to_many_without_method_redefinition :developers_with_finder_sql, :class_name => "Developer",
91
+ # :finder_sql => 'SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id}'
92
+ :finder_sql => lambda{|join, project| join.project_id == project.id } # this is run on the join table
93
+
94
+ has_and_belongs_to_many_without_method_redefinition :developers_by_sql, :class_name => "Developer",
95
+ # :delete_sql => "DELETE FROM developers_projects WHERE project_id = \#{id} AND developer_id = \#{record.id}"
96
+ :delete_sql => lambda{|join, project, developer| join.project_id == project.id and join.developer_id == developer.id}
97
+ end
98
+