composite_primary_keys 0.3.3 → 0.6.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.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ * 0.3.3 * - bug fixes
2
+ - id=
3
+ - create now work
4
+
1
5
  * 0.1.4 *
2
6
  - it was important that #{primary_key} for composites --> 'key1,key2' and not 'key1key2'
3
7
  so created PrimaryKeys class
data/README CHANGED
@@ -0,0 +1,16 @@
1
+
2
+ Composite Primary Keys for ActiveRecords
3
+ Summary:
4
+ ActiveRecords/Rails famously doesn't support composite primary keys.
5
+ For 2 years there has been no support for this aspect of legacy databases.
6
+ This project will be a fully-featured solution and I welcome use cases and
7
+ legacy DB schema samples to help make the project work with as many common
8
+ legacy environments as possible.
9
+
10
+ Url:
11
+ http://compositekeys.rubyforge.org
12
+
13
+ Questions and Discussion:
14
+ http://groups.google.com/compositekeys
15
+
16
+ Written by Dr Nic Williams, drnicwilliams@gmail
@@ -1,4 +1,329 @@
1
- module ActiveRecord
2
- class Base
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module Associations
4
+ def self.append_features(base)
5
+ super
6
+ base.send(:extend, ClassMethods)
7
+ end
8
+
9
+ # Composite key versions of Association functions
10
+ module ClassMethods
11
+
12
+ def construct_counter_sql_with_included_associations(options, join_dependency)
13
+ scope = scope(:find)
14
+ sql = "SELECT COUNT(DISTINCT #{quoted_table_columns(primary_key)})"
15
+
16
+ # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
17
+ if !self.connection.supports_count_distinct?
18
+ sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{quoted_table_columns(primary_key)}"
19
+ end
20
+
21
+ sql << " FROM #{table_name} "
22
+ sql << join_dependency.join_associations.collect{|join| join.association_join }.join
23
+
24
+ add_joins!(sql, options, scope)
25
+ add_conditions!(sql, options[:conditions], scope)
26
+ add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
27
+
28
+ add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
29
+
30
+ if !self.connection.supports_count_distinct?
31
+ sql << ")"
32
+ end
33
+
34
+ return sanitize_sql(sql)
35
+ end
36
+
37
+ def construct_finder_sql_with_included_associations(options, join_dependency)
38
+ scope = scope(:find)
39
+ sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
40
+ sql << join_dependency.join_associations.collect{|join| join.association_join }.join
41
+
42
+ add_joins!(sql, options, scope)
43
+ add_conditions!(sql, options[:conditions], scope)
44
+ add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
45
+
46
+ sql << "ORDER BY #{options[:order]} " if options[:order]
47
+
48
+ add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
49
+
50
+ return sanitize_sql(sql)
51
+ end
52
+
53
+ def table_columns(columns)
54
+ columns.collect {|column| "#{self.table_name}.#{column}"}
55
+ end
56
+
57
+ def quoted_table_columns(columns)
58
+ table_columns(columns).join(ID_SEP)
59
+ end
60
+
61
+ end
62
+
63
+ end
3
64
  end
4
- end
65
+ end
66
+
67
+ module ActiveRecord::Associations::ClassMethods
68
+ class JoinDependency
69
+ def construct_association(record, join, row)
70
+ case join.reflection.macro
71
+ when :has_many, :has_and_belongs_to_many
72
+ collection = record.send(join.reflection.name)
73
+ collection.loaded
74
+
75
+ join_aliased_primary_keys = join.active_record.composite? ?
76
+ join.aliased_primary_key : [join.aliased_primary_key]
77
+ return nil if
78
+ record.id.to_s != join.parent.record_id(row).to_s or not
79
+ join_aliased_primary_keys.select {|key| row[key].nil?}.blank?
80
+ association = join.instantiate(row)
81
+ collection.target.push(association) unless collection.target.include?(association)
82
+ when :has_one, :belongs_to
83
+ return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
84
+ association = join.instantiate(row)
85
+ record.send("set_#{join.reflection.name}_target", association)
86
+ else
87
+ raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
88
+ end
89
+ return association
90
+ end
91
+
92
+ class JoinBase
93
+ def aliased_primary_key
94
+ active_record.composite? ?
95
+ primary_key.inject([]) {|aliased_keys, key| aliased_keys << "#{ aliased_prefix }_r#{aliased_keys.length}"} :
96
+ "#{ aliased_prefix }_r0"
97
+ end
98
+
99
+ def record_id(row)
100
+ active_record.composite? ?
101
+ aliased_primary_key.map {|key| row[key]}.to_composite_ids :
102
+ row[aliased_primary_key]
103
+ end
104
+
105
+ def column_names_with_alias
106
+ unless @column_names_with_alias
107
+ @column_names_with_alias = []
108
+ keys = active_record.composite? ? primary_key.map(&:to_s) : [primary_key]
109
+ (keys + (column_names - keys)).each_with_index do |column_name, i|
110
+ @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
111
+ end
112
+ end
113
+ return @column_names_with_alias
114
+ end
115
+ end
116
+
117
+ class JoinAssociation < JoinBase
118
+ alias single_association_join association_join
119
+ def association_join
120
+ reflection.active_record.composite? ?
121
+ composite_association_join :
122
+ single_association_join
123
+ end
124
+
125
+ def composite_association_join
126
+ join = case reflection.macro
127
+ when :has_and_belongs_to_many
128
+ " LEFT OUTER JOIN %s ON (%s) = (%s) " % [
129
+ table_alias_for(options[:join_table], aliased_join_table_name),
130
+ full_keys(aliased_join_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key),
131
+ full_keys(reflection.active_record.table_name, reflection.active_record.primary_key)] +
132
+ " LEFT OUTER JOIN %s ON (%s) = (%s) " % [
133
+ table_name_and_alias,
134
+ full_keys(aliased_table_name, klass.primary_key),
135
+ full_keys(aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key)
136
+ ]
137
+ when :has_many, :has_one
138
+ case
139
+ when reflection.macro == :has_many && reflection.options[:through]
140
+ through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
141
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
142
+ raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
143
+ else
144
+ if source_reflection.macro == :has_many && source_reflection.options[:as]
145
+ raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
146
+ else
147
+ case source_reflection.macro
148
+ when :belongs_to
149
+ first_key = primary_key
150
+ second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
151
+ when :has_many
152
+ first_key = through_reflection.klass.to_s.classify.foreign_key
153
+ second_key = options[:foreign_key] || primary_key
154
+ end
155
+
156
+ " LEFT OUTER JOIN %s ON (%s) = (%s) " % [
157
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
158
+ full_keys(aliased_join_table_name, through_reflection.primary_key_name),
159
+ full_keys(parent.aliased_table_name, parent.primary_key)] +
160
+ " LEFT OUTER JOIN %s ON (%s) = (%s) " % [
161
+ table_name_and_alias,
162
+ full_keys(aliased_table_name, first_key),
163
+ full_keys(aliased_join_table_name, second_key)
164
+ ]
165
+ end
166
+ end
167
+
168
+ when reflection.macro == :has_many && reflection.options[:as]
169
+ raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
170
+ when reflection.macro == :has_one && reflection.options[:as]
171
+ raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
172
+ else
173
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
174
+ " LEFT OUTER JOIN %s ON (%s) = (%s) " % [
175
+ table_name_and_alias,
176
+ full_keys(aliased_table_name, foreign_key),
177
+ full_keys(parent.aliased_table_name, parent.primary_key)
178
+ ]
179
+ end
180
+ when :belongs_to
181
+ " LEFT OUTER JOIN %s ON (%s) = (%s) " % [
182
+ table_name_and_alias,
183
+ full_keys(aliased_table_name, reflection.klass.primary_key),
184
+ full_keys(parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key)
185
+ ]
186
+ else
187
+ ""
188
+ end || ''
189
+ join << %(AND %s.%s = %s ) % [
190
+ aliased_table_name,
191
+ reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
192
+ klass.quote(klass.name)] unless klass.descends_from_active_record?
193
+ join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
194
+ join
195
+ end
196
+
197
+ def full_keys(table_name, keys)
198
+ keys.collect {|key| "#{table_name}.#{key}"}.join(CompositePrimaryKeys::ID_SEP)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ module ActiveRecord::Associations
205
+ class AssociationProxy #:nodoc:
206
+ def full_keys(table_name, keys)
207
+ keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String)
208
+ keys.is_a?(Array) ?
209
+ keys.collect {|key| "#{table_name}.#{key}"}.join(CompositePrimaryKeys::ID_SEP) :
210
+ "#{table_name}.#{keys}"
211
+ end
212
+
213
+ def full_columns_equals(table_name, keys, quoted_ids)
214
+ keys = keys.split(CompositePrimaryKeys::ID_SEP) if keys.is_a?(String)
215
+ keys_ids = [keys, quoted_ids]
216
+ keys_ids = keys.is_a?(Symbol) ? [keys_ids] : keys_ids.transpose
217
+ keys_ids.collect {|key, id| "(#{table_name}.#{key} = #{id})"}.join(' AND ')
218
+ end
219
+ end
220
+
221
+ class HasManyAssociation < AssociationCollection #:nodoc:
222
+ def construct_sql
223
+ case
224
+ when @reflection.options[:finder_sql]
225
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
226
+
227
+ when @reflection.options[:as]
228
+ @finder_sql =
229
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
230
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
231
+ @finder_sql << " AND (#{conditions})" if conditions
232
+
233
+ else
234
+ @finder_sql = full_columns_equals(@reflection.klass.table_name,
235
+ @reflection.primary_key_name, @owner.quoted_id)
236
+ @finder_sql << " AND (#{conditions})" if conditions
237
+ end
238
+
239
+ if @reflection.options[:counter_sql]
240
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
241
+ elsif @reflection.options[:finder_sql]
242
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
243
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
244
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
245
+ else
246
+ @counter_sql = @finder_sql
247
+ end
248
+ end
249
+ end
250
+
251
+ class HasOneAssociation < BelongsToAssociation #:nodoc:
252
+ def construct_sql
253
+ case
254
+ when @reflection.options[:as]
255
+ @finder_sql =
256
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
257
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
258
+ else
259
+ @finder_sql = "(%s) = (%s)" % [
260
+ full_keys(@reflection.table_name, @reflection.primary_key_name),
261
+ @owner.quoted_id
262
+ ]
263
+ end
264
+ @finder_sql << " AND (#{conditions})" if conditions
265
+ end
266
+ end
267
+
268
+ class HasManyThroughAssociation < AssociationProxy #:nodoc:
269
+ def construct_conditions
270
+ conditions = if @reflection.through_reflection.options[:as]
271
+ "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_id = #{@owner.quoted_id} " +
272
+ "AND #{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
273
+ else
274
+ @finder_sql = full_columns_equals(@reflection.through_reflection.table_name,
275
+ @reflection.through_reflection.primary_key_name, @owner.quoted_id)
276
+ end
277
+ conditions << " AND (#{sql_conditions})" if sql_conditions
278
+
279
+ return conditions
280
+ end
281
+
282
+ def construct_joins(custom_joins = nil)
283
+ polymorphic_join = nil
284
+ if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
285
+ reflection_primary_key = @reflection.klass.primary_key
286
+ source_primary_key = @reflection.source_reflection.primary_key_name
287
+ else
288
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
289
+ source_primary_key = @reflection.klass.primary_key
290
+ if @reflection.source_reflection.options[:as]
291
+ polymorphic_join = "AND %s.%s = %s" % [
292
+ @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
293
+ @owner.class.quote(@reflection.through_reflection.klass.name)
294
+ ]
295
+ end
296
+ end
297
+
298
+ "INNER JOIN %s ON (%s) = (%s) %s #{@reflection.options[:joins]} #{custom_joins}" % [
299
+ @reflection.through_reflection.table_name,
300
+ full_keys(@reflection.table_name, reflection_primary_key),
301
+ full_keys(@reflection.through_reflection.table_name, source_primary_key),
302
+ polymorphic_join
303
+ ]
304
+ end
305
+
306
+ def construct_sql
307
+ case
308
+ when @reflection.options[:finder_sql]
309
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
310
+
311
+ @finder_sql = "(%s) = (%s)" % [
312
+ full_keys(@reflection.klass.table_name, @reflection.primary_key_name),
313
+ @owner.quoted_id
314
+ ]
315
+ @finder_sql << " AND (#{conditions})" if conditions
316
+ end
317
+
318
+ if @reflection.options[:counter_sql]
319
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
320
+ elsif @reflection.options[:finder_sql]
321
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
322
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
323
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
324
+ else
325
+ @counter_sql = @finder_sql
326
+ end
327
+ end
328
+ end
329
+ end
@@ -16,16 +16,15 @@ module CompositePrimaryKeys
16
16
 
17
17
  module ClassMethods
18
18
  def set_primary_keys(*keys)
19
- keys = keys.first if keys.first.is_a?(Array) or keys.first.is_a?(CompositeKeys)
19
+ keys = keys.first if keys.first.is_a?(Array)
20
20
  cattr_accessor :primary_keys
21
21
  self.primary_keys = keys.to_composite_keys
22
22
 
23
23
  class_eval <<-EOV
24
- include CompositePrimaryKeys::ActiveRecord::Base::CompositeInstanceMethods
25
24
  extend CompositePrimaryKeys::ActiveRecord::Base::CompositeClassMethods
25
+ include CompositePrimaryKeys::ActiveRecord::Base::CompositeInstanceMethods
26
+ include CompositePrimaryKeys::ActiveRecord::Associations
26
27
  EOV
27
-
28
- #puts "#{self.class}-#{self}.composite = #{self.composite?}"
29
28
  end
30
29
 
31
30
  def composite?
@@ -75,19 +74,6 @@ module CompositePrimaryKeys
75
74
  id
76
75
  end
77
76
 
78
- # Deletes the record in the database and freezes this instance to reflect that no changes should
79
- # be made (since they can't be persisted).
80
- def destroy
81
- unless new_record?
82
- connection.delete <<-end_sql, "#{self.class.name} Destroy"
83
- DELETE FROM #{self.class.table_name}
84
- WHERE (#{self.class.primary_key}) = (#{quoted_id})
85
- end_sql
86
- end
87
-
88
- freeze
89
- end
90
-
91
77
  # Returns a clone of the record that hasn't been assigned an id yet and
92
78
  # is treated as a new record. Note that this is a "shallow" clone:
93
79
  # it copies the object's attributes only, not its associations.
@@ -171,20 +157,11 @@ module CompositePrimaryKeys
171
157
  end
172
158
 
173
159
  private
174
- # Updates the associated record with values matching those of the instance attributes.
175
- def update
176
- connection.update(
177
- "UPDATE #{self.class.table_name} " +
178
- "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
179
- "WHERE (#{self.class.primary_key}) = (#{id})",
180
- "#{self.class.name} Update"
181
- )
182
- return true
183
- end
184
-
160
+ # The xx_without_callbacks methods are overwritten as that is the end of the alias chain
161
+
185
162
  # Creates a new record with values matching those of the instance attributes.
186
- def create
187
- if self.id.nil?
163
+ def create_without_callbacks
164
+ unless self.id
188
165
  raise CompositeKeyError, "Composite keys do not generated ids from sequences, you must provide id values"
189
166
  end
190
167
 
@@ -195,11 +172,34 @@ module CompositePrimaryKeys
195
172
  "#{self.class.name} Create",
196
173
  self.class.primary_key, self.id
197
174
  )
198
-
199
175
  @new_record = false
200
-
201
176
  return true
202
177
  end
178
+
179
+ # Updates the associated record with values matching those of the instance attributes.
180
+ def update_without_callbacks
181
+ connection.update(
182
+ "UPDATE #{self.class.table_name} " +
183
+ "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
184
+ "WHERE (#{self.class.primary_key}) = (#{id})",
185
+ "#{self.class.name} Update"
186
+ )
187
+ return true
188
+ end
189
+
190
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
191
+ # be made (since they can't be persisted).
192
+ def destroy_without_callbacks
193
+ unless new_record?
194
+ connection.delete <<-end_sql, "#{self.class.name} Destroy"
195
+ DELETE FROM #{self.class.table_name}
196
+ WHERE (#{self.class.primary_key}) = (#{quoted_id})
197
+ end_sql
198
+ end
199
+
200
+ freeze
201
+ end
202
+
203
203
  end
204
204
 
205
205
  module CompositeClassMethods
@@ -1,8 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 3
5
- TINY = 3
4
+ MINOR = 6
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -21,11 +21,22 @@ class AssociationTest < Test::Unit::TestCase
21
21
  assert_not_nil @first_flat
22
22
  end
23
23
 
24
+ def test_quoted_table_columns
25
+ assert_equal "product_tariffs.product_id,product_tariffs.tariff_id,product_tariffs.tariff_start_date",
26
+ ProductTariff.send(:quoted_table_columns, ProductTariff.primary_key)
27
+ end
28
+
29
+ def test_count
30
+ assert_equal 2, Product.count(:include => :product_tariffs)
31
+ assert_equal 3, Tariff.count(:include => :product_tariffs)
32
+ end
33
+
24
34
  def test_products
25
35
  assert_not_nil @first_product.product_tariffs
26
36
  assert_equal 2, @first_product.product_tariffs.length
27
37
  assert_not_nil @first_product.tariffs
28
38
  assert_equal 2, @first_product.tariffs.length
39
+ assert_not_nil @first_product.product_tariff
29
40
  end
30
41
 
31
42
  def test_product_tariffs
@@ -37,8 +48,39 @@ class AssociationTest < Test::Unit::TestCase
37
48
 
38
49
  def test_tariffs
39
50
  assert_not_nil @flat.product_tariffs
40
- assert_equal 2, @flat.product_tariffs.length
51
+ assert_equal 1, @flat.product_tariffs.length
41
52
  assert_not_nil @flat.products
42
- assert_equal 2, @flat.products.length
53
+ assert_equal 1, @flat.products.length
54
+ assert_not_nil @flat.product_tariff
55
+ end
56
+
57
+ # Its not generating the instances of associated classes from the rows
58
+ def test_find_includes_products
59
+ assert @products = Product.find(:all, :include => :product_tariffs)
60
+ assert_equal 2, @products.length
61
+ assert_not_nil @products.first.instance_variable_get('@product_tariffs'), '@product_tariffs not set; should be array'
62
+ assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, "Incorrect number of product_tariffs returned"
43
63
  end
64
+
65
+ def test_find_includes_tariffs
66
+ assert @tariffs = Tariff.find(:all, :include => :product_tariffs)
67
+ assert_equal 3, @tariffs.length
68
+ assert_not_nil @tariffs.first.instance_variable_get('@product_tariffs'), '@product_tariffs not set; should be array'
69
+ assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, "Incorrect number of product_tariffs returned"
70
+ end
71
+
72
+ def XXX_test_find_includes_extended
73
+ # TODO - what's the correct syntax?
74
+ assert @products = Product.find(:all, :include => {:product_tariffs => :tariffs})
75
+ assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, "Incorrect number of product_tariffs returned"
76
+
77
+ assert @tariffs = Tariff.find(:all, :include => {:product_tariffs => :products})
78
+ assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length}, "Incorrect number of product_tariffs returned"
79
+
80
+ end
81
+
82
+ #I'm also having problems when I use composite primary keys together with eager loading of associations. Here I'm doing
83
+ #ArtistName.find(:all, :include => :artist, ...)
84
+ # => ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'artists_names.artist_id,name' in 'field list': SELECT artists_names.`artist_id,name` AS t0_r0, ....
85
+ # Had a brief look into it but couldn't spot the code causing this...
44
86
  end
@@ -1,5 +1,6 @@
1
1
  class Product < ActiveRecord::Base
2
2
  set_primary_key :id # redundant
3
3
  has_many :product_tariffs, :foreign_key => :product_id
4
+ has_one :product_tariff, :foreign_key => :product_id
4
5
  has_many :tariffs, :through => :product_tariffs, :foreign_key => :product_id
5
6
  end
@@ -1,4 +1,6 @@
1
1
  class Tariff < ActiveRecord::Base
2
2
  set_primary_keys [:tariff_id, :start_date]
3
3
  has_many :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date]
4
+ has_one :product_tariff, :foreign_key => [:tariff_id, :tariff_start_date]
5
+ has_many :products, :through => :product_tariffs, :foreign_key => [:tariff_id, :tariff_start_date]
4
6
  end
@@ -32,4 +32,9 @@ class MiscellaneousTest < Test::Unit::TestCase
32
32
  assert_equal composite?, @first.composite?
33
33
  end
34
34
  end
35
+
36
+ def test_count
37
+ assert_equal 2, Product.count
38
+ end
39
+
35
40
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: composite_primary_keys
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.3
7
- date: 2006-07-30 00:00:00 +02:00
6
+ version: 0.6.0
7
+ date: 2006-08-02 00:00:00 +02:00
8
8
  summary: Support for composite primary keys in ActiveRecords
9
9
  require_paths:
10
10
  - lib