composite_primary_keys 2.3.2 → 2.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ == 2.3.5 2009-12-16
2
+
3
+ * Fixed several bugs in has_one and has_many associations when :primary_key specified [kpumuk]
4
+
1
5
  == 2.3.2 2009-07-16
2
6
 
3
7
  * explicitly load associations.rb due to some getting an unitialized constant error
@@ -46,7 +50,7 @@
46
50
 
47
51
  == 1.0.4 2008-07-15
48
52
 
49
- * support for oracle_enhanced adapter [thx Raimonds Simanovskis]
53
+ * support for oracle_enhanced adapter [thx Raimonds Simanovskis]
50
54
 
51
55
  == 1.0.3 2008-07-13
52
56
 
@@ -116,7 +120,7 @@
116
120
  == 0.8.4 / 2007-5-3
117
121
 
118
122
  * 1 bugfix
119
- * Corrected ids_list => ids in the exception message. That'll teach me for not adding unit tests before fixing bugs.
123
+ * Corrected ids_list => ids in the exception message. That'll teach me for not adding unit tests before fixing bugs.
120
124
 
121
125
  == 0.8.3 / 2007-5-3
122
126
 
@@ -154,11 +158,11 @@
154
158
  == 0.1.4
155
159
  * it was important that #{primary_key} for composites --> 'key1,key2' and not 'key1key2' so created PrimaryKeys class
156
160
 
157
- == 0.0.1
161
+ == 0.0.1
158
162
  * Initial version
159
163
  * set_primary_keys(*keys) is the activation class method to transform an ActiveRecord into a composite primary key AR
160
- * find(*ids) supports the passing of
161
- * id sets: Foo.find(2,1),
162
- * lists of id sets: Foo.find([2,1], [7,3], [8,12]),
164
+ * find(*ids) supports the passing of
165
+ * id sets: Foo.find(2,1),
166
+ * lists of id sets: Foo.find([2,1], [7,3], [8,12]),
163
167
  * and even stringified versions of the above:
164
168
  * Foo.find '2,1' or Foo.find '2,1;7,3'
@@ -24,6 +24,7 @@ lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb
24
24
  lib/composite_primary_keys/fixtures.rb
25
25
  lib/composite_primary_keys/migration.rb
26
26
  lib/composite_primary_keys/reflection.rb
27
+ lib/composite_primary_keys/validations/uniqueness.rb
27
28
  lib/composite_primary_keys/version.rb
28
29
  loader.rb
29
30
  local/database_connections.rb.sample
data/Rakefile CHANGED
@@ -1,65 +1,65 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'rake/clean'
4
- require 'rake/testtask'
5
- require 'rake/rdoctask'
6
- require 'rake/packagetask'
7
- require 'rake/gempackagetask'
8
- require 'rake/contrib/rubyforgepublisher'
9
- require 'fileutils'
10
- require 'hoe'
11
- include FileUtils
12
- require File.join(File.dirname(__FILE__), 'lib', 'composite_primary_keys', 'version')
13
-
14
- AUTHOR = "Dr Nic Williams"
15
- EMAIL = "drnicwilliams@gmail.com"
16
- DESCRIPTION = "Composite key support for ActiveRecords"
17
- GEM_NAME = "composite_primary_keys" # what ppl will type to install your gem
18
- if File.exists?("~/.rubyforge/user-config.yml")
19
- # TODO this should prob go in a local/ file
20
- config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
21
- RUBYFORGE_USERNAME = config["username"]
22
- end
23
- RUBYFORGE_PROJECT = "compositekeys"
24
- HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
25
-
26
- REV = nil #File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil
27
- VERS = ENV['VERSION'] || (CompositePrimaryKeys::VERSION::STRING + (REV ? ".#{REV}" : ""))
28
- CLEAN.include ['**/.*.sw?', '*.gem', '.config','debug.log','*.db','logfile','log/**/*','**/.DS_Store', '.project']
29
- RDOC_OPTS = ['--quiet', '--title', "newgem documentation",
30
- "--opname", "index.html",
31
- "--line-numbers",
32
- "--main", "README",
33
- "--inline-source"]
34
-
35
- class Hoe
36
- def extra_deps
37
- @extra_deps.reject { |x| Array(x).first == 'hoe' }
38
- end
39
- end
40
-
41
- # Generate all the Rake tasks
42
- # Run 'rake -T' to see list of generated tasks (from gem root directory)
43
- hoe = Hoe.new(GEM_NAME, VERS) do |p|
44
- p.author = AUTHOR
45
- p.description = DESCRIPTION
46
- p.email = EMAIL
47
- p.summary = DESCRIPTION
48
- p.url = HOMEPATH
49
- p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
50
- p.test_globs = ["test/**/test*.rb"]
51
- p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
52
-
53
- # == Optional
54
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
55
- p.extra_deps = [['activerecord', '>= 2.3.2']] #An array of rubygem dependencies.
56
- #p.spec_extras - A hash of extra values to set in the gemspec.
57
- end
58
-
59
- CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
60
- PATH = RUBYFORGE_PROJECT
61
- hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
62
-
63
- PROJECT_ROOT = File.expand_path(".")
64
-
65
- require 'loader'
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/packagetask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+ include FileUtils
12
+ require File.join(File.dirname(__FILE__), 'lib', 'composite_primary_keys', 'version')
13
+
14
+ AUTHOR = "Dr Nic Williams"
15
+ EMAIL = "drnicwilliams@gmail.com"
16
+ DESCRIPTION = "Composite key support for ActiveRecords"
17
+ GEM_NAME = "composite_primary_keys" # what ppl will type to install your gem
18
+ if File.exists?("~/.rubyforge/user-config.yml")
19
+ # TODO this should prob go in a local/ file
20
+ config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
21
+ RUBYFORGE_USERNAME = config["username"]
22
+ end
23
+ RUBYFORGE_PROJECT = "compositekeys"
24
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
25
+
26
+ REV = nil #File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil
27
+ VERS = ENV['VERSION'] || (CompositePrimaryKeys::VERSION::STRING + (REV ? ".#{REV}" : ""))
28
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config','debug.log','*.db','logfile','log/**/*','**/.DS_Store', '.project']
29
+ RDOC_OPTS = ['--quiet', '--title', "newgem documentation",
30
+ "--opname", "index.html",
31
+ "--line-numbers",
32
+ "--main", "README",
33
+ "--inline-source"]
34
+
35
+ class Hoe
36
+ def extra_deps
37
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
38
+ end
39
+ end
40
+
41
+ # Generate all the Rake tasks
42
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
43
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
44
+ p.author = AUTHOR
45
+ p.description = DESCRIPTION
46
+ p.email = EMAIL
47
+ p.summary = DESCRIPTION
48
+ p.url = HOMEPATH
49
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
50
+ p.test_globs = ["test/**/test*.rb"]
51
+ p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
52
+
53
+ # == Optional
54
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
55
+ p.extra_deps = [['activerecord', '>= 2.3.5']] #An array of rubygem dependencies.
56
+ #p.spec_extras - A hash of extra values to set in the gemspec.
57
+ end
58
+
59
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
60
+ PATH = RUBYFORGE_PROJECT
61
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
62
+
63
+ PROJECT_ROOT = File.expand_path(".")
64
+
65
+ require 'loader'
@@ -44,6 +44,7 @@ require 'composite_primary_keys/base'
44
44
  require 'composite_primary_keys/calculations'
45
45
  require 'composite_primary_keys/migration'
46
46
  require 'composite_primary_keys/attribute_methods'
47
+ require 'composite_primary_keys/validations/uniqueness'
47
48
 
48
49
  ActiveRecord::Base.class_eval do
49
50
  include CompositePrimaryKeys::ActiveRecord::Base
@@ -180,11 +180,12 @@ module ActiveRecord::Associations::ClassMethods
180
180
  raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
181
181
  else
182
182
  foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
183
+ primary_key = options[:primary_key] || parent.primary_key
183
184
  " LEFT OUTER JOIN %s ON %s " % [
184
185
  table_name_and_alias,
185
186
  composite_join_clause(
186
187
  full_keys(aliased_table_name, foreign_key),
187
- full_keys(parent.aliased_table_name, parent.primary_key)),
188
+ full_keys(parent.aliased_table_name, primary_key)),
188
189
  ]
189
190
  end
190
191
  when :belongs_to
@@ -338,7 +339,7 @@ module ActiveRecord::Associations
338
339
  @finder_sql << " AND (#{conditions})" if conditions
339
340
 
340
341
  else
341
- @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
342
+ @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, owner_quoted_id)
342
343
  @finder_sql << " AND (#{conditions})" if conditions
343
344
  end
344
345
 
@@ -386,7 +387,7 @@ module ActiveRecord::Associations
386
387
  "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
387
388
  "#{@reflection.klass.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
388
389
  else
389
- @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, @owner.quoted_id)
390
+ @finder_sql = full_columns_equals(@reflection.klass.table_name, @reflection.primary_key_name, owner_quoted_id)
390
391
  end
391
392
 
392
393
  @finder_sql << " AND (#{conditions})" if conditions
@@ -29,6 +29,8 @@ module CompositePrimaryKeys
29
29
  include CompositePrimaryKeys::ActiveRecord::AssociationPreload
30
30
  include CompositePrimaryKeys::ActiveRecord::Calculations
31
31
  include CompositePrimaryKeys::ActiveRecord::AttributeMethods
32
+
33
+ extend CompositePrimaryKeys::ActiveRecord::Validations::Uniqueness::ClassMethods
32
34
  EOV
33
35
  end
34
36
 
@@ -0,0 +1,77 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module Validations
4
+ module Uniqueness
5
+ module ClassMethods
6
+ def validates_uniqueness_of(*attr_names)
7
+ configuration = { :case_sensitive => true }
8
+ configuration.update(attr_names.extract_options!)
9
+
10
+ validates_each(attr_names,configuration) do |record, attr_name, value|
11
+ # The check for an existing value should be run from a class that
12
+ # isn't abstract. This means working down from the current class
13
+ # (self), to the first non-abstract class. Since classes don't know
14
+ # their subclasses, we have to build the hierarchy between self and
15
+ # the record's class.
16
+ class_hierarchy = [record.class]
17
+ while class_hierarchy.first != self
18
+ class_hierarchy.insert(0, class_hierarchy.first.superclass)
19
+ end
20
+
21
+ # Now we can work our way down the tree to the first non-abstract
22
+ # class (which has a database table to query from).
23
+ finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
24
+
25
+ column = finder_class.columns_hash[attr_name.to_s]
26
+
27
+ if value.nil?
28
+ comparison_operator = "IS ?"
29
+ elsif column.text?
30
+ comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
31
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
32
+ else
33
+ comparison_operator = "= ?"
34
+ end
35
+
36
+ sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
37
+
38
+ if value.nil? || (configuration[:case_sensitive] || !column.text?)
39
+ condition_sql = "#{sql_attribute} #{comparison_operator}"
40
+ condition_params = [value]
41
+ else
42
+ condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
43
+ condition_params = [value.mb_chars.downcase]
44
+ end
45
+
46
+ if scope = configuration[:scope]
47
+ Array(scope).map do |scope_item|
48
+ scope_value = record.send(scope_item)
49
+ condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
50
+ condition_params << scope_value
51
+ end
52
+ end
53
+
54
+ unless record.new_record?
55
+ if record.class.composite?
56
+ record.class.primary_keys.each do |key|
57
+ condition_sql << " AND #{record.class.quoted_table_name}.#{key} <> ?"
58
+ condition_params << record.send(key)
59
+ end
60
+ else
61
+ condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
62
+ condition_params << record.send(:id)
63
+ end
64
+ end
65
+
66
+ finder_class.with_exclusive_scope do
67
+ if finder_class.exists?([condition_sql, *condition_params])
68
+ record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,8 +1,8 @@
1
- module CompositePrimaryKeys
2
- module VERSION #:nodoc:
3
- MAJOR = 2
4
- MINOR = 3
5
- TINY = 2
6
- STRING = [MAJOR, MINOR, TINY].join('.')
7
- end
8
- end
1
+ module CompositePrimaryKeys
2
+ module VERSION #:nodoc:
3
+ MAJOR = 2
4
+ MINOR = 3
5
+ TINY = 5
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+ end
@@ -172,3 +172,10 @@ create table room_assignments (
172
172
  room_id int(11) not null
173
173
  ) type=InnoDB;
174
174
 
175
+ create table seats (
176
+ flight_number int(11) not null,
177
+ seat int(11) not null,
178
+ customer int,
179
+ primary key (flight_number, seat)
180
+ ) type=InnoDB;
181
+
@@ -197,3 +197,10 @@ create table room_assignments (
197
197
  room_id int not null
198
198
  );
199
199
 
200
+ create table seats (
201
+ flight_number int not null,
202
+ seat int not null,
203
+ customer int,
204
+ primary key (flight_number, seat)
205
+ );
206
+
@@ -158,3 +158,9 @@ create table room_assignments (
158
158
  room_id integer not null
159
159
  );
160
160
 
161
+ create table seats (
162
+ flight_number integer not_null,
163
+ seat integer not_null,
164
+ customer integer,
165
+ primary key (flight_number, seat)
166
+ );
@@ -4,4 +4,7 @@ class Membership < ActiveRecord::Base
4
4
  belongs_to :user
5
5
  belongs_to :group
6
6
  has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id]
7
+
8
+ has_many :readings, :primary_key => :user_id, :foreign_key => :user_id
9
+ has_one :reading, :primary_key => :user_id, :foreign_key => :user_id, :order => 'id DESC'
7
10
  end
@@ -17,7 +17,8 @@ require 'fixtures/reading'
17
17
 
18
18
  class TestAssociations < ActiveSupport::TestCase
19
19
  fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants, :restaurants_suburbs,
20
- :dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings
20
+ :dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings,
21
+ :memberships
21
22
 
22
23
  def test_has_many_through_with_conditions_when_through_association_is_not_composite
23
24
  user = User.find(:first)
@@ -157,4 +158,28 @@ class TestAssociations < ActiveSupport::TestCase
157
158
  @restaurant = Restaurant.find([1,1], :include => :suburbs)
158
159
  assert_equal 2, @restaurant.suburbs.size
159
160
  end
161
+
162
+ def test_has_many_with_primary_key
163
+ @membership = Membership.find([1, 1])
164
+
165
+ assert_equal 2, @membership.readings.size
166
+ end
167
+
168
+ def test_has_one_with_primary_key
169
+ @membership = Membership.find([1, 1])
170
+
171
+ assert_equal 2, @membership.reading.id
172
+ end
173
+
174
+ def test_joins_has_many_with_primary_key
175
+ @membership = Membership.find(:first, :joins => :readings, :conditions => { :readings => { :id => 1 } })
176
+
177
+ assert_equal [1, 1], @membership.id
178
+ end
179
+
180
+ def test_joins_has_one_with_primary_key
181
+ @membership = Membership.find(:first, :joins => :reading, :conditions => { :readings => { :id => 2 } })
182
+
183
+ assert_equal [1, 1], @membership.id
184
+ end
160
185
  end
@@ -0,0 +1,11 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/seat'
3
+
4
+ class TestValidations < ActiveSupport::TestCase
5
+ fixtures :seats
6
+
7
+ def test_uniqueness_validation_on_saved_record
8
+ s = Seat.find([1,1])
9
+ assert s.valid?
10
+ end
11
+ end
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composite_primary_keys
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.2
4
+ version: 2.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dr Nic Williams
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-16 00:00:00 -05:00
12
+ date: 2009-12-16 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 2.3.2
23
+ version: 2.3.5
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: hoe
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 2.3.2
33
+ version: 2.4.0
34
34
  version:
35
35
  description: Composite key support for ActiveRecords
36
36
  email: drnicwilliams@gmail.com
@@ -73,6 +73,7 @@ files:
73
73
  - lib/composite_primary_keys/fixtures.rb
74
74
  - lib/composite_primary_keys/migration.rb
75
75
  - lib/composite_primary_keys/reflection.rb
76
+ - lib/composite_primary_keys/validations/uniqueness.rb
76
77
  - lib/composite_primary_keys/version.rb
77
78
  - loader.rb
78
79
  - local/database_connections.rb.sample
@@ -194,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
195
  requirements: []
195
196
 
196
197
  rubyforge_project: compositekeys
197
- rubygems_version: 1.3.4
198
+ rubygems_version: 1.3.5
198
199
  signing_key:
199
200
  specification_version: 3
200
201
  summary: Composite key support for ActiveRecords
@@ -216,3 +217,4 @@ test_files:
216
217
  - test/test_santiago.rb
217
218
  - test/test_tutorial_examle.rb
218
219
  - test/test_update.rb
220
+ - test/test_validations.rb