composite_primary_keys 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/composite_primary_keys.rb +3 -0
- data/lib/composite_primary_keys/associations.rb +4 -0
- data/lib/composite_primary_keys/base.rb +71 -13
- data/lib/composite_primary_keys/composite_arrays.rb +1 -0
- data/lib/composite_primary_keys/fixtures.rb +5 -596
- data/lib/composite_primary_keys/reflection.rb +19 -0
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/abstract_unit.rb +3 -3
- data/test/associations_test.rb +44 -0
- data/test/attributes_test.rb +85 -0
- data/test/create_test.rb +52 -0
- data/test/dummy_test.rb +1 -0
- data/test/find_test.rb +10 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -30
- data/test/fixtures/db_definitions/mysql.sql +21 -0
- data/test/fixtures/product.rb +5 -0
- data/test/fixtures/product_tariff.rb +5 -0
- data/test/fixtures/product_tariffs.yml +12 -0
- data/test/fixtures/products.yml +6 -0
- data/test/fixtures/tariff.rb +4 -0
- data/test/fixtures/tariffs.yml +13 -0
- data/test/ids_test.rb +27 -0
- metadata +14 -5
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
@@ -34,7 +34,10 @@ unless defined?(ActiveRecord)
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
require 'composite_primary_keys/fixtures'
|
37
38
|
require 'composite_primary_keys/composite_arrays'
|
39
|
+
require 'composite_primary_keys/associations'
|
40
|
+
require 'composite_primary_keys/reflection'
|
38
41
|
require 'composite_primary_keys/base'
|
39
42
|
|
40
43
|
ActiveRecord::Base.class_eval do
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module CompositePrimaryKeys
|
2
2
|
module ActiveRecord #:nodoc:
|
3
|
+
class CompositeKeyError < StandardError #:nodoc:
|
4
|
+
end
|
5
|
+
|
3
6
|
module Base #:nodoc:
|
4
7
|
|
5
8
|
INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
|
@@ -51,7 +54,7 @@ module CompositePrimaryKeys
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def id_before_type_cast #:nodoc:
|
54
|
-
raise CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET
|
57
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET
|
55
58
|
end
|
56
59
|
|
57
60
|
def quoted_id #:nodoc:
|
@@ -62,12 +65,14 @@ module CompositePrimaryKeys
|
|
62
65
|
end
|
63
66
|
|
64
67
|
# Sets the primary ID.
|
65
|
-
def id=(
|
66
|
-
ids =
|
68
|
+
def id=(ids)
|
69
|
+
ids = ids.split(ID_SEP) if ids.is_a?(String)
|
70
|
+
ids.flatten!
|
67
71
|
unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length
|
68
72
|
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
|
69
73
|
end
|
70
|
-
ids.each {|
|
74
|
+
[primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
|
75
|
+
id
|
71
76
|
end
|
72
77
|
|
73
78
|
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
@@ -96,6 +101,36 @@ module CompositePrimaryKeys
|
|
96
101
|
end
|
97
102
|
end
|
98
103
|
|
104
|
+
# Allows +attr_name+ to be the list of primary_keys, and returns the id
|
105
|
+
# of the object
|
106
|
+
# e.g. @object[@object.class.primary_key] => [1,1]
|
107
|
+
def [](attr_name)
|
108
|
+
if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
|
109
|
+
attr_name = attr_name.split(ID_SEP)
|
110
|
+
end
|
111
|
+
attr_name.is_a?(Array) ?
|
112
|
+
attr_name.map {|name| read_attribute(name)} :
|
113
|
+
read_attribute(attr_name)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
117
|
+
# (Alias for the protected write_attribute method).
|
118
|
+
def []=(attr_name, value)
|
119
|
+
if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
|
120
|
+
attr_name = attr_name.split(ID_SEP)
|
121
|
+
end
|
122
|
+
if attr_name.is_a? Array
|
123
|
+
value = value.split(ID_SEP) if value.is_a? String
|
124
|
+
unless value.length == attr_name.length
|
125
|
+
raise "Number of attr_names and values do not match"
|
126
|
+
end
|
127
|
+
#breakpoint
|
128
|
+
[attr_name, value].transpose.map {|name,val| write_attribute(name.to_s, val)}
|
129
|
+
else
|
130
|
+
write_attribute(attr_name, value)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
99
134
|
# Define an attribute reader method. Cope with nil column.
|
100
135
|
def define_read_method(symbol, attr_name, column)
|
101
136
|
cast_code = column.type_cast_code('v') if column
|
@@ -146,6 +181,25 @@ module CompositePrimaryKeys
|
|
146
181
|
)
|
147
182
|
return true
|
148
183
|
end
|
184
|
+
|
185
|
+
# Creates a new record with values matching those of the instance attributes.
|
186
|
+
def create
|
187
|
+
if self.id.nil?
|
188
|
+
raise CompositeKeyError, "Composite keys do not generated ids from sequences, you must provide id values"
|
189
|
+
end
|
190
|
+
|
191
|
+
self.id = connection.insert(
|
192
|
+
"INSERT INTO #{self.class.table_name} " +
|
193
|
+
"(#{quoted_column_names.join(', ')}) " +
|
194
|
+
"VALUES(#{attributes_with_quotes.values.join(', ')})",
|
195
|
+
"#{self.class.name} Create",
|
196
|
+
self.class.primary_key, self.id
|
197
|
+
)
|
198
|
+
|
199
|
+
@new_record = false
|
200
|
+
|
201
|
+
return true
|
202
|
+
end
|
149
203
|
end
|
150
204
|
|
151
205
|
module CompositeClassMethods
|
@@ -207,40 +261,44 @@ module CompositePrimaryKeys
|
|
207
261
|
# Lazy-set the sequence name to the connection's default. This method
|
208
262
|
# is only ever called once since set_sequence_name overrides it.
|
209
263
|
def sequence_name #:nodoc:
|
210
|
-
raise CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
264
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
211
265
|
end
|
212
266
|
|
213
267
|
def reset_sequence_name #:nodoc:
|
214
|
-
raise CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
268
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
215
269
|
end
|
216
270
|
|
217
271
|
def set_primary_key(value = nil, &block)
|
218
|
-
raise CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
272
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
219
273
|
end
|
220
274
|
|
221
275
|
private
|
222
276
|
def find_one(id, options)
|
223
|
-
raise CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
277
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
224
278
|
end
|
225
279
|
|
226
280
|
def find_some(ids, options)
|
227
|
-
raise CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
281
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
228
282
|
end
|
229
283
|
|
230
284
|
def find_from_ids(ids, options)
|
231
285
|
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
232
286
|
# if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order)
|
233
287
|
# if ids is list of lists, then each inner list must follow rule above
|
234
|
-
#if ids.first.is_a?(String) - find '2,1' -> find_from_ids ['2,1']
|
235
|
-
ids = ids[0].split(';').map {|id_set| id_set.split ','} if ids.first.is_a? String
|
236
|
-
ids = [ids] if not ids.first.kind_of?(Array)
|
237
288
|
|
289
|
+
if ids.first.is_a? String
|
290
|
+
# find '2,1' -> ids = ['2,1']
|
291
|
+
# find '2,1;7,3' -> ids = ['2,1;7,3']
|
292
|
+
ids = ids.first.split(ID_SET_SEP).map {|id_set| id_set.split(ID_SEP).to_composite_ids}
|
293
|
+
# find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds
|
294
|
+
end
|
295
|
+
ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)
|
238
296
|
ids.each do |id_set|
|
239
297
|
unless id_set.is_a?(Array)
|
240
298
|
raise "Ids must be in an Array, instead received: #{id_set.inspect}"
|
241
299
|
end
|
242
300
|
unless id_set.length == primary_keys.length
|
243
|
-
raise "Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"
|
301
|
+
raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"
|
244
302
|
end
|
245
303
|
end
|
246
304
|
|
@@ -1,600 +1,9 @@
|
|
1
|
-
require 'erb'
|
2
|
-
require 'yaml'
|
3
|
-
require 'csv'
|
4
|
-
|
5
|
-
module YAML #:nodoc:
|
6
|
-
class Omap #:nodoc:
|
7
|
-
def keys; map { |k, v| k } end
|
8
|
-
def values; map { |k, v| v } end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
|
13
|
-
end
|
14
|
-
|
15
|
-
# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
|
16
|
-
#
|
17
|
-
# 1. YAML fixtures
|
18
|
-
# 2. CSV fixtures
|
19
|
-
# 3. Single-file fixtures
|
20
|
-
#
|
21
|
-
# = YAML fixtures
|
22
|
-
#
|
23
|
-
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
|
24
|
-
# in a non-verbose, humanly-readable format. It ships with Ruby 1.8.1+.
|
25
|
-
#
|
26
|
-
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
|
27
|
-
# by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
|
28
|
-
# put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
|
29
|
-
# "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
|
30
|
-
#
|
31
|
-
# rubyonrails:
|
32
|
-
# id: 1
|
33
|
-
# name: Ruby on Rails
|
34
|
-
# url: http://www.rubyonrails.org
|
35
|
-
#
|
36
|
-
# google:
|
37
|
-
# id: 2
|
38
|
-
# name: Google
|
39
|
-
# url: http://www.google.com
|
40
|
-
#
|
41
|
-
# This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
|
42
|
-
# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
|
43
|
-
# pleasure.
|
44
|
-
#
|
45
|
-
# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
|
46
|
-
# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
|
47
|
-
# This is commonly needed for tree structures. Example:
|
48
|
-
#
|
49
|
-
# --- !omap
|
50
|
-
# - parent:
|
51
|
-
# id: 1
|
52
|
-
# parent_id: NULL
|
53
|
-
# title: Parent
|
54
|
-
# - child:
|
55
|
-
# id: 2
|
56
|
-
# parent_id: 1
|
57
|
-
# title: Child
|
58
|
-
#
|
59
|
-
# = CSV fixtures
|
60
|
-
#
|
61
|
-
# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
|
62
|
-
# in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
|
63
|
-
#
|
64
|
-
# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
|
65
|
-
# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
|
66
|
-
# of the actual data (1 per line). Here's an example:
|
67
|
-
#
|
68
|
-
# id, name, url
|
69
|
-
# 1, Ruby On Rails, http://www.rubyonrails.org
|
70
|
-
# 2, Google, http://www.google.com
|
71
|
-
#
|
72
|
-
# Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
|
73
|
-
# need to use a double quote character, you must escape it with another double quote.
|
74
|
-
#
|
75
|
-
# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
|
76
|
-
# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
|
77
|
-
# number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
|
78
|
-
# "web_site_2".
|
79
|
-
#
|
80
|
-
# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
|
81
|
-
# have existing data somewhere already.
|
82
|
-
#
|
83
|
-
# = Single-file fixtures
|
84
|
-
#
|
85
|
-
# This type of fixtures was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
|
86
|
-
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
|
87
|
-
# appointed by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
|
88
|
-
# put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ for the WebSite
|
89
|
-
# model).
|
90
|
-
#
|
91
|
-
# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
|
92
|
-
# extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. Here's what the
|
93
|
-
# above example might look like:
|
94
|
-
#
|
95
|
-
# web_sites/google
|
96
|
-
# web_sites/yahoo.txt
|
97
|
-
# web_sites/ruby-on-rails
|
98
|
-
#
|
99
|
-
# The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
|
100
|
-
# of "name => value". Here's an example of the ruby-on-rails fixture above:
|
101
|
-
#
|
102
|
-
# id => 1
|
103
|
-
# name => Ruby on Rails
|
104
|
-
# url => http://www.rubyonrails.org
|
105
|
-
#
|
106
|
-
# = Using Fixtures
|
107
|
-
#
|
108
|
-
# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
|
109
|
-
# fixtures, but first let's take a look at a sample unit test found:
|
110
|
-
#
|
111
|
-
# require 'web_site'
|
112
|
-
#
|
113
|
-
# class WebSiteTest < Test::Unit::TestCase
|
114
|
-
# def test_web_site_count
|
115
|
-
# assert_equal 2, WebSite.count
|
116
|
-
# end
|
117
|
-
# end
|
118
|
-
#
|
119
|
-
# As it stands, unless we pre-load the web_site table in our database with two records, this test will fail. Here's the
|
120
|
-
# easiest way to add fixtures to the database:
|
121
|
-
#
|
122
|
-
# ...
|
123
|
-
# class WebSiteTest < Test::Unit::TestCase
|
124
|
-
# fixtures :web_sites # add more by separating the symbols with commas
|
125
|
-
# ...
|
126
|
-
#
|
127
|
-
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger
|
128
|
-
# the testing environment to automatically load the appropriate fixtures into the database before each test.
|
129
|
-
# To ensure consistent data, the environment deletes the fixtures before running the load.
|
130
|
-
#
|
131
|
-
# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
|
132
|
-
# of the test case. It is named after the symbol... so, in our example, there would be a hash available called
|
133
|
-
# @web_sites. This is where the "fixture name" comes into play.
|
134
|
-
#
|
135
|
-
# On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
|
136
|
-
# So for the YAML fixtures, we'd get @rubyonrails and @google, which could be interrogated using regular Active Record semantics:
|
137
|
-
#
|
138
|
-
# # test if the object created from the fixture data has the same attributes as the data itself
|
139
|
-
# def test_find
|
140
|
-
# assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
|
141
|
-
# end
|
142
|
-
#
|
143
|
-
# As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
|
144
|
-
# "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
|
145
|
-
# from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
|
146
|
-
# fixtures available as instance variables @web_site_1 and @web_site_2.
|
147
|
-
#
|
148
|
-
# If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
|
149
|
-
#
|
150
|
-
# - to completely disable instantiated fixtures:
|
151
|
-
# self.use_instantiated_fixtures = false
|
152
|
-
#
|
153
|
-
# - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
|
154
|
-
# self.use_instantiated_fixtures = :no_instances
|
155
|
-
#
|
156
|
-
# Even if auto-instantiated fixtures are disabled, you can still access them
|
157
|
-
# by name via special dynamic methods. Each method has the same name as the
|
158
|
-
# model, and accepts the name of the fixture to instantiate:
|
159
|
-
#
|
160
|
-
# fixtures :web_sites
|
161
|
-
#
|
162
|
-
# def test_find
|
163
|
-
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
|
164
|
-
# end
|
165
|
-
#
|
166
|
-
# = Dynamic fixtures with ERb
|
167
|
-
#
|
168
|
-
# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
|
169
|
-
# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
|
170
|
-
#
|
171
|
-
# <% for i in 1..1000 %>
|
172
|
-
# fix_<%= i %>:
|
173
|
-
# id: <%= i %>
|
174
|
-
# name: guy_<%= 1 %>
|
175
|
-
# <% end %>
|
176
|
-
#
|
177
|
-
# This will create 1000 very simple YAML fixtures.
|
178
|
-
#
|
179
|
-
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
|
180
|
-
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
|
181
|
-
# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
|
182
|
-
# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
|
183
|
-
#
|
184
|
-
# = Transactional fixtures
|
185
|
-
#
|
186
|
-
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
187
|
-
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
|
188
|
-
#
|
189
|
-
# class FooTest < Test::Unit::TestCase
|
190
|
-
# self.use_transactional_fixtures = true
|
191
|
-
# self.use_instantiated_fixtures = false
|
192
|
-
#
|
193
|
-
# fixtures :foos
|
194
|
-
#
|
195
|
-
# def test_godzilla
|
196
|
-
# assert !Foo.find(:all).empty?
|
197
|
-
# Foo.destroy_all
|
198
|
-
# assert Foo.find(:all).empty?
|
199
|
-
# end
|
200
|
-
#
|
201
|
-
# def test_godzilla_aftermath
|
202
|
-
# assert !Foo.find(:all).empty?
|
203
|
-
# end
|
204
|
-
# end
|
205
|
-
#
|
206
|
-
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
207
|
-
# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
|
208
|
-
#
|
209
|
-
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
|
210
|
-
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
|
211
|
-
#
|
212
|
-
# When *not* to use transactional fixtures:
|
213
|
-
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
214
|
-
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
215
|
-
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
|
216
|
-
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
217
|
-
# Use InnoDB, MaxDB, or NDB instead.
|
218
|
-
class Fixtures < YAML::Omap
|
219
|
-
DEFAULT_FILTER_RE = /\.ya?ml$/
|
220
|
-
|
221
|
-
def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
|
222
|
-
object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
|
223
|
-
if load_instances
|
224
|
-
ActiveRecord::Base.silence do
|
225
|
-
fixtures.each do |name, fixture|
|
226
|
-
begin
|
227
|
-
object.instance_variable_set "@#{name}", fixture.find
|
228
|
-
rescue FixtureClassNotFound
|
229
|
-
nil
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def self.instantiate_all_loaded_fixtures(object, load_instances=true)
|
237
|
-
all_loaded_fixtures.each do |table_name, fixtures|
|
238
|
-
Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
cattr_accessor :all_loaded_fixtures
|
243
|
-
self.all_loaded_fixtures = {}
|
244
|
-
|
245
|
-
def self.create_fixtures(fixtures_directory, table_names, class_names = {})
|
246
|
-
table_names = [table_names].flatten.map { |n| n.to_s }
|
247
|
-
connection = block_given? ? yield : ActiveRecord::Base.connection
|
248
|
-
ActiveRecord::Base.silence do
|
249
|
-
fixtures_map = {}
|
250
|
-
fixtures = table_names.map do |table_name|
|
251
|
-
fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
|
252
|
-
end
|
253
|
-
all_loaded_fixtures.merge! fixtures_map
|
254
|
-
|
255
|
-
connection.transaction do
|
256
|
-
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
257
|
-
fixtures.each { |fixture| fixture.insert_fixtures }
|
258
|
-
|
259
|
-
# Cap primary key sequences to max(pk).
|
260
|
-
if connection.respond_to?(:reset_pk_sequence!)
|
261
|
-
table_names.each do |table_name|
|
262
|
-
connection.reset_pk_sequence!(table_name)
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
return fixtures.size > 1 ? fixtures : fixtures.first
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
|
272
|
-
attr_reader :table_name
|
273
|
-
|
274
|
-
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
|
275
|
-
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
|
276
|
-
@class_name = class_name ||
|
277
|
-
(ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
|
278
|
-
@table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
|
279
|
-
read_fixture_files
|
280
|
-
end
|
281
|
-
|
282
|
-
def delete_existing_fixtures
|
283
|
-
@connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
|
284
|
-
end
|
285
|
-
|
286
|
-
def insert_fixtures
|
287
|
-
values.each do |fixture|
|
288
|
-
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
private
|
293
|
-
|
294
|
-
def read_fixture_files
|
295
|
-
if File.file?(yaml_file_path)
|
296
|
-
# YAML fixtures
|
297
|
-
begin
|
298
|
-
yaml_string = ""
|
299
|
-
Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
|
300
|
-
yaml_string << IO.read(subfixture_path)
|
301
|
-
end
|
302
|
-
yaml_string << IO.read(yaml_file_path)
|
303
|
-
|
304
|
-
if yaml = YAML::load(erb_render(yaml_string))
|
305
|
-
yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
|
306
|
-
yaml.each do |name, data|
|
307
|
-
self[name] = Fixture.new(data, @class_name)
|
308
|
-
end
|
309
|
-
end
|
310
|
-
rescue Exception=>boom
|
311
|
-
raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
|
312
|
-
end
|
313
|
-
elsif File.file?(csv_file_path)
|
314
|
-
# CSV fixtures
|
315
|
-
reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
|
316
|
-
header = reader.shift
|
317
|
-
i = 0
|
318
|
-
reader.each do |row|
|
319
|
-
data = {}
|
320
|
-
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
321
|
-
self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name)
|
322
|
-
end
|
323
|
-
elsif File.file?(deprecated_yaml_file_path)
|
324
|
-
raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
|
325
|
-
else
|
326
|
-
# Standard fixtures
|
327
|
-
Dir.entries(@fixture_path).each do |file|
|
328
|
-
path = File.join(@fixture_path, file)
|
329
|
-
if File.file?(path) and file !~ @file_filter
|
330
|
-
self[file] = Fixture.new(path, @class_name)
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
def yaml_file_path
|
337
|
-
"#{@fixture_path}.yml"
|
338
|
-
end
|
339
|
-
|
340
|
-
def deprecated_yaml_file_path
|
341
|
-
"#{@fixture_path}.yaml"
|
342
|
-
end
|
343
|
-
|
344
|
-
def csv_file_path
|
345
|
-
@fixture_path + ".csv"
|
346
|
-
end
|
347
|
-
|
348
|
-
def yaml_fixtures_key(path)
|
349
|
-
File.basename(@fixture_path).split(".").first
|
350
|
-
end
|
351
|
-
|
352
|
-
def erb_render(fixture_content)
|
353
|
-
ERB.new(fixture_content).result
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
1
|
class Fixture #:nodoc:
|
358
|
-
include Enumerable
|
359
|
-
class FixtureError < StandardError#:nodoc:
|
360
|
-
end
|
361
|
-
class FormatError < FixtureError#:nodoc:
|
362
|
-
end
|
363
|
-
|
364
|
-
def initialize(fixture, class_name)
|
365
|
-
case fixture
|
366
|
-
when Hash, YAML::Omap
|
367
|
-
@fixture = fixture
|
368
|
-
when String
|
369
|
-
@fixture = read_fixture_file(fixture)
|
370
|
-
else
|
371
|
-
raise ArgumentError, "Bad fixture argument #{fixture.inspect}"
|
372
|
-
end
|
373
|
-
|
374
|
-
@class_name = class_name
|
375
|
-
end
|
376
|
-
|
377
|
-
def each
|
378
|
-
@fixture.each { |item| yield item }
|
379
|
-
end
|
380
|
-
|
381
2
|
def [](key)
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
def to_hash
|
386
|
-
@fixture
|
387
|
-
end
|
388
|
-
|
389
|
-
def key_list
|
390
|
-
columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
|
391
|
-
columns.join(", ")
|
392
|
-
end
|
393
|
-
|
394
|
-
def value_list
|
395
|
-
@fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
|
396
|
-
end
|
397
|
-
|
398
|
-
def find
|
399
|
-
klass = @class_name.is_a?(Class) ? @class_name : Object.const_get(@class_name) rescue nil
|
400
|
-
if klass
|
401
|
-
klass.find(self[klass.primary_key])
|
402
|
-
else
|
403
|
-
raise FixtureClassNotFound, "The class #{@class_name.inspect} was not found."
|
3
|
+
if key.is_a? Array
|
4
|
+
return key.map {|a_key| self[a_key.to_s]}.
|
5
|
+
to_composite_ids.to_s
|
404
6
|
end
|
7
|
+
@fixture[key]
|
405
8
|
end
|
406
|
-
|
407
|
-
private
|
408
|
-
def read_fixture_file(fixture_file_path)
|
409
|
-
IO.readlines(fixture_file_path).inject({}) do |fixture, line|
|
410
|
-
# Mercifully skip empty lines.
|
411
|
-
next if line =~ /^\s*$/
|
412
|
-
|
413
|
-
# Use the same regular expression for attributes as Active Record.
|
414
|
-
unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
|
415
|
-
raise FormatError, "#{fixture_file_path}: fixture format error at '#{line}'. Expecting 'key => value'."
|
416
|
-
end
|
417
|
-
key, value = md.captures
|
418
|
-
|
419
|
-
# Disallow duplicate keys to catch typos.
|
420
|
-
raise FormatError, "#{fixture_file_path}: duplicate '#{key}' in fixture." if fixture[key]
|
421
|
-
fixture[key] = value.strip
|
422
|
-
fixture
|
423
|
-
end
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
module Test #:nodoc:
|
428
|
-
module Unit #:nodoc:
|
429
|
-
class TestCase #:nodoc:
|
430
|
-
cattr_accessor :fixture_path
|
431
|
-
class_inheritable_accessor :fixture_table_names
|
432
|
-
class_inheritable_accessor :fixture_class_names
|
433
|
-
class_inheritable_accessor :use_transactional_fixtures
|
434
|
-
class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
435
|
-
class_inheritable_accessor :pre_loaded_fixtures
|
436
|
-
|
437
|
-
self.fixture_table_names = []
|
438
|
-
self.use_transactional_fixtures = false
|
439
|
-
self.use_instantiated_fixtures = true
|
440
|
-
self.pre_loaded_fixtures = false
|
441
|
-
|
442
|
-
self.fixture_class_names = {}
|
443
|
-
|
444
|
-
@@already_loaded_fixtures = {}
|
445
|
-
self.fixture_class_names = {}
|
446
|
-
|
447
|
-
def self.set_fixture_class(class_names = {})
|
448
|
-
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
449
|
-
end
|
450
|
-
|
451
|
-
def self.fixtures(*table_names)
|
452
|
-
table_names = table_names.flatten.map { |n| n.to_s }
|
453
|
-
self.fixture_table_names |= table_names
|
454
|
-
require_fixture_classes(table_names)
|
455
|
-
setup_fixture_accessors(table_names)
|
456
|
-
end
|
457
|
-
|
458
|
-
def self.require_fixture_classes(table_names=nil)
|
459
|
-
(table_names || fixture_table_names).each do |table_name|
|
460
|
-
file_name = table_name.to_s
|
461
|
-
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
|
462
|
-
begin
|
463
|
-
require file_name
|
464
|
-
rescue LoadError
|
465
|
-
# Let's hope the developer has included it himself
|
466
|
-
end
|
467
|
-
end
|
468
|
-
end
|
469
|
-
|
470
|
-
def self.setup_fixture_accessors(table_names=nil)
|
471
|
-
(table_names || fixture_table_names).each do |table_name|
|
472
|
-
table_name = table_name.to_s.tr('.','_')
|
473
|
-
define_method(table_name) do |fixture, *optionals|
|
474
|
-
force_reload = optionals.shift
|
475
|
-
@fixture_cache[table_name] ||= Hash.new
|
476
|
-
@fixture_cache[table_name][fixture] = nil if force_reload
|
477
|
-
if @loaded_fixtures[table_name][fixture.to_s]
|
478
|
-
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
479
|
-
else
|
480
|
-
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
481
|
-
end
|
482
|
-
end
|
483
|
-
end
|
484
|
-
end
|
485
|
-
|
486
|
-
def self.uses_transaction(*methods)
|
487
|
-
@uses_transaction ||= []
|
488
|
-
@uses_transaction.concat methods.map { |m| m.to_s }
|
489
|
-
end
|
490
|
-
|
491
|
-
def self.uses_transaction?(method)
|
492
|
-
@uses_transaction && @uses_transaction.include?(method.to_s)
|
493
|
-
end
|
494
|
-
|
495
|
-
def use_transactional_fixtures?
|
496
|
-
use_transactional_fixtures &&
|
497
|
-
!self.class.uses_transaction?(method_name)
|
498
|
-
end
|
499
|
-
|
500
|
-
def setup_with_fixtures
|
501
|
-
if pre_loaded_fixtures && !use_transactional_fixtures
|
502
|
-
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
503
|
-
end
|
504
|
-
|
505
|
-
@fixture_cache = Hash.new
|
506
|
-
|
507
|
-
# Load fixtures once and begin transaction.
|
508
|
-
if use_transactional_fixtures?
|
509
|
-
if @@already_loaded_fixtures[self.class]
|
510
|
-
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
511
|
-
else
|
512
|
-
load_fixtures
|
513
|
-
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
514
|
-
end
|
515
|
-
ActiveRecord::Base.lock_mutex
|
516
|
-
ActiveRecord::Base.connection.begin_db_transaction
|
517
|
-
|
518
|
-
# Load fixtures for every test.
|
519
|
-
else
|
520
|
-
@@already_loaded_fixtures[self.class] = nil
|
521
|
-
load_fixtures
|
522
|
-
end
|
523
|
-
|
524
|
-
# Instantiate fixtures for every test if requested.
|
525
|
-
instantiate_fixtures if use_instantiated_fixtures
|
526
|
-
end
|
527
|
-
|
528
|
-
alias_method :setup, :setup_with_fixtures
|
529
|
-
|
530
|
-
def teardown_with_fixtures
|
531
|
-
# Rollback changes.
|
532
|
-
if use_transactional_fixtures?
|
533
|
-
ActiveRecord::Base.connection.rollback_db_transaction
|
534
|
-
ActiveRecord::Base.unlock_mutex
|
535
|
-
end
|
536
|
-
ActiveRecord::Base.verify_active_connections!
|
537
|
-
end
|
538
|
-
|
539
|
-
alias_method :teardown, :teardown_with_fixtures
|
540
|
-
|
541
|
-
def self.method_added(method)
|
542
|
-
case method.to_s
|
543
|
-
when 'setup'
|
544
|
-
unless method_defined?(:setup_without_fixtures)
|
545
|
-
alias_method :setup_without_fixtures, :setup
|
546
|
-
define_method(:setup) do
|
547
|
-
setup_with_fixtures
|
548
|
-
setup_without_fixtures
|
549
|
-
end
|
550
|
-
end
|
551
|
-
when 'teardown'
|
552
|
-
unless method_defined?(:teardown_without_fixtures)
|
553
|
-
alias_method :teardown_without_fixtures, :teardown
|
554
|
-
define_method(:teardown) do
|
555
|
-
teardown_without_fixtures
|
556
|
-
teardown_with_fixtures
|
557
|
-
end
|
558
|
-
end
|
559
|
-
end
|
560
|
-
end
|
561
|
-
|
562
|
-
private
|
563
|
-
def load_fixtures
|
564
|
-
@loaded_fixtures = {}
|
565
|
-
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
566
|
-
unless fixtures.nil?
|
567
|
-
if fixtures.instance_of?(Fixtures)
|
568
|
-
@loaded_fixtures[fixtures.table_name] = fixtures
|
569
|
-
else
|
570
|
-
fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
|
571
|
-
end
|
572
|
-
end
|
573
|
-
end
|
574
|
-
|
575
|
-
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
|
576
|
-
@@required_fixture_classes = false
|
577
|
-
|
578
|
-
def instantiate_fixtures
|
579
|
-
if pre_loaded_fixtures
|
580
|
-
raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
|
581
|
-
unless @@required_fixture_classes
|
582
|
-
self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
|
583
|
-
@@required_fixture_classes = true
|
584
|
-
end
|
585
|
-
Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
|
586
|
-
else
|
587
|
-
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
588
|
-
@loaded_fixtures.each do |table_name, fixtures|
|
589
|
-
Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
|
590
|
-
end
|
591
|
-
end
|
592
|
-
end
|
593
|
-
|
594
|
-
def load_instances?
|
595
|
-
use_instantiated_fixtures != :no_instances
|
596
|
-
end
|
597
|
-
end
|
598
|
-
|
599
|
-
end
|
600
|
-
end
|
9
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Reflection
|
3
|
+
class AssociationReflection
|
4
|
+
def primary_key_name
|
5
|
+
return @primary_key_name if @primary_key_name
|
6
|
+
case
|
7
|
+
when macro == :belongs_to
|
8
|
+
@primary_key_name = options[:foreign_key] || class_name.foreign_key
|
9
|
+
when options[:as]
|
10
|
+
@primary_key_name = options[:foreign_key] || "#{options[:as]}_id"
|
11
|
+
else
|
12
|
+
@primary_key_name = options[:foreign_key] || active_record.name.foreign_key
|
13
|
+
end
|
14
|
+
@primary_key_name = @primary_key_name.to_composite_keys.to_s if @primary_key_name.is_a? Array
|
15
|
+
@primary_key_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/test/abstract_unit.rb
CHANGED
@@ -8,7 +8,6 @@ require 'active_support/binding_of_caller'
|
|
8
8
|
require 'active_support/breakpoint'
|
9
9
|
require 'connection'
|
10
10
|
require 'composite_primary_keys'
|
11
|
-
require 'composite_primary_keys/fixtures'
|
12
11
|
|
13
12
|
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
|
14
13
|
|
@@ -63,11 +62,12 @@ protected
|
|
63
62
|
end
|
64
63
|
|
65
64
|
def first_id
|
66
|
-
(1..@primary_keys.length).map {|num| 1}
|
65
|
+
ids = (1..@primary_keys.length).map {|num| 1}
|
66
|
+
composite? ? ids.to_composite_ids : ids.first
|
67
67
|
end
|
68
68
|
|
69
69
|
def first_id_str
|
70
|
-
first_id.join(CompositePrimaryKeys::ID_SEP)
|
70
|
+
composite? ? first_id.join(CompositePrimaryKeys::ID_SEP) : first_id.to_s
|
71
71
|
end
|
72
72
|
|
73
73
|
def composite?
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
require 'fixtures/product'
|
3
|
+
require 'fixtures/tariff'
|
4
|
+
require 'fixtures/product_tariff'
|
5
|
+
|
6
|
+
class AssociationTest < Test::Unit::TestCase
|
7
|
+
fixtures :products, :tariffs, :product_tariffs
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
@first_product = products(:first_product)
|
12
|
+
@flat = tariffs(:flat)
|
13
|
+
@free = tariffs(:free)
|
14
|
+
@first_flat = product_tariffs(:first_flat)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_setup
|
18
|
+
assert_not_nil @first_product
|
19
|
+
assert_not_nil @flat
|
20
|
+
assert_not_nil @free
|
21
|
+
assert_not_nil @first_flat
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_products
|
25
|
+
assert_not_nil @first_product.product_tariffs
|
26
|
+
assert_equal 2, @first_product.product_tariffs.length
|
27
|
+
assert_not_nil @first_product.tariffs
|
28
|
+
assert_equal 2, @first_product.tariffs.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_product_tariffs
|
32
|
+
assert_not_nil @first_flat.product
|
33
|
+
assert_not_nil @first_flat.tariff
|
34
|
+
assert_equal Product, @first_flat.product.class
|
35
|
+
assert_equal Tariff, @first_flat.tariff.class
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_tariffs
|
39
|
+
assert_not_nil @flat.product_tariffs
|
40
|
+
assert_equal 2, @flat.product_tariffs.length
|
41
|
+
assert_not_nil @flat.products
|
42
|
+
assert_equal 2, @flat.products.length
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
require 'fixtures/reference_type'
|
3
|
+
require 'fixtures/reference_code'
|
4
|
+
require 'fixtures/product'
|
5
|
+
require 'fixtures/tariff'
|
6
|
+
require 'fixtures/product_tariff'
|
7
|
+
|
8
|
+
class AttributesTest < Test::Unit::TestCase
|
9
|
+
fixtures :reference_types, :reference_codes, :products, :tariffs, :product_tariffs
|
10
|
+
|
11
|
+
CLASSES = {
|
12
|
+
:single => {
|
13
|
+
:class => ReferenceType,
|
14
|
+
:primary_keys => [:reference_type_id],
|
15
|
+
},
|
16
|
+
:dual => {
|
17
|
+
:class => ReferenceCode,
|
18
|
+
:primary_keys => [:reference_type_id, :reference_code],
|
19
|
+
},
|
20
|
+
}
|
21
|
+
|
22
|
+
def setup
|
23
|
+
super
|
24
|
+
self.class.classes = CLASSES
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_brackets
|
28
|
+
testing_with do
|
29
|
+
@first.attributes.each_pair do |attr_name, value|
|
30
|
+
assert_equal value, @first[attr_name]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_brackets_primary_key
|
36
|
+
testing_with do
|
37
|
+
assert_equal @first.id, @first[@primary_keys]
|
38
|
+
assert_equal @first.id, @first[@first.class.primary_key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_brackets_assignment
|
43
|
+
testing_with do
|
44
|
+
@first.attributes.each_pair do |attr_name, value|
|
45
|
+
@first[attr_name]= !value.nil? ? value * 2 : '1'
|
46
|
+
assert_equal !value.nil? ? value * 2 : '1', @first[attr_name]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_brackets_foreign_key_assignment
|
52
|
+
@flat = tariffs(:flat)
|
53
|
+
@second_free = product_tariffs(:second_free)
|
54
|
+
@second_free_fk = [:tariff_id, :tariff_start_date]
|
55
|
+
@second_free[key = @second_free_fk] = @flat.id
|
56
|
+
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|
57
|
+
assert_equal @flat.id, @second_free[key]
|
58
|
+
@second_free[key = @second_free_fk.to_composite_ids] = @flat.id
|
59
|
+
assert_equal @flat.id, @second_free[key]
|
60
|
+
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|
61
|
+
@second_free[key = @second_free_fk.to_composite_ids] = @flat.id.to_s
|
62
|
+
assert_equal @flat.id, @second_free[key]
|
63
|
+
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|
64
|
+
@second_free[key = @second_free_fk.to_composite_ids] = @flat.id.to_s
|
65
|
+
assert_equal @flat.id, @second_free[key]
|
66
|
+
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|
67
|
+
@second_free[key = @second_free_fk.to_composite_ids.to_s] = @flat.id
|
68
|
+
assert_equal @flat.id, @second_free[key]
|
69
|
+
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|
70
|
+
@second_free[key = @second_free_fk.to_composite_ids.to_s] = @flat.id.to_s
|
71
|
+
assert_equal @flat.id, @second_free[key]
|
72
|
+
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|
73
|
+
end
|
74
|
+
private
|
75
|
+
def compare_indexes(obj_name1, indexes1, obj_name2, indexes2)
|
76
|
+
obj1, obj2 = eval "[#{obj_name1}, #{obj_name2}]"
|
77
|
+
indexes1.length.times do |key_index|
|
78
|
+
assert_equal obj1[indexes1[key_index].to_s],
|
79
|
+
obj2[indexes2[key_index].to_s],
|
80
|
+
"#{obj_name1}[#{indexes1[key_index]}]=#{obj1[indexes1[key_index].to_s].inspect} != " +
|
81
|
+
"#{obj_name2}[#{indexes2[key_index]}]=#{obj2[indexes2[key_index].to_s].inspect}; " +
|
82
|
+
"#{obj_name2} = #{obj2.inspect}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/test/create_test.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
require 'fixtures/reference_type'
|
3
|
+
require 'fixtures/reference_code'
|
4
|
+
|
5
|
+
class DummyTest < Test::Unit::TestCase
|
6
|
+
fixtures :reference_types, :reference_codes
|
7
|
+
|
8
|
+
CLASSES = {
|
9
|
+
:single => {
|
10
|
+
:class => ReferenceType,
|
11
|
+
:primary_keys => [:reference_type_id],
|
12
|
+
:create => {:reference_type_id => 10, :type_label => 'NEW_TYPE', :abbreviation => 'New Type'}
|
13
|
+
},
|
14
|
+
:dual => {
|
15
|
+
:class => ReferenceCode,
|
16
|
+
:primary_keys => [:reference_type_id, :reference_code],
|
17
|
+
:create => {:reference_type_id => 1, :reference_code => 20, :code_label => 'NEW_CODE', :abbreviation => 'New Code'}
|
18
|
+
},
|
19
|
+
}
|
20
|
+
|
21
|
+
def setup
|
22
|
+
super
|
23
|
+
self.class.classes = CLASSES
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_setup
|
27
|
+
testing_with do
|
28
|
+
assert_not_nil @klass_info[:create]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_create
|
33
|
+
testing_with do
|
34
|
+
assert new_obj = @klass.create(@klass_info[:create])
|
35
|
+
assert !new_obj.new_record?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_create_no_id
|
40
|
+
testing_with do
|
41
|
+
begin
|
42
|
+
@obj = @klass.create(@klass_info[:create].block(@klass.primary_key))
|
43
|
+
@successful = !composite?
|
44
|
+
rescue CompositePrimaryKeys::ActiveRecord::CompositeKeyError
|
45
|
+
@successful = false
|
46
|
+
rescue
|
47
|
+
flunk "Incorrect exception raised: #{$!}, #{$!.class}"
|
48
|
+
end
|
49
|
+
assert_equal composite?, !@successful, "Create should have failed for composites; #{@obj.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/test/dummy_test.rb
CHANGED
data/test/find_test.rb
CHANGED
@@ -40,6 +40,16 @@ class FindTest < Test::Unit::TestCase
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
def test_find_composite_ids
|
44
|
+
testing_with do
|
45
|
+
found = @klass.find(first_id) # e.g. find([1,1].to_composite_ids)
|
46
|
+
assert found
|
47
|
+
assert_equal @klass, found.class
|
48
|
+
assert_equal found, @klass.find(found.id)
|
49
|
+
assert_equal found, @klass.find(found.to_param)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
43
53
|
def test_to_param
|
44
54
|
testing_with do
|
45
55
|
assert_equal first_id_str, @first.to_param.to_s
|
@@ -1,30 +1,2 @@
|
|
1
|
-
DROP TABLE
|
2
|
-
DROP TABLE
|
3
|
-
DROP TABLE companies;
|
4
|
-
DROP TABLE topics;
|
5
|
-
DROP TABLE developers;
|
6
|
-
DROP TABLE projects;
|
7
|
-
DROP TABLE developers_projects;
|
8
|
-
DROP TABLE customers;
|
9
|
-
DROP TABLE orders;
|
10
|
-
DROP TABLE movies;
|
11
|
-
DROP TABLE subscribers;
|
12
|
-
DROP TABLE booleantests;
|
13
|
-
DROP TABLE auto_id_tests;
|
14
|
-
DROP TABLE entrants;
|
15
|
-
DROP TABLE colnametests;
|
16
|
-
DROP TABLE mixins;
|
17
|
-
DROP TABLE people;
|
18
|
-
DROP TABLE readers;
|
19
|
-
DROP TABLE binaries;
|
20
|
-
DROP TABLE computers;
|
21
|
-
DROP TABLE tasks;
|
22
|
-
DROP TABLE posts;
|
23
|
-
DROP TABLE comments;
|
24
|
-
DROP TABLE authors;
|
25
|
-
DROP TABLE categories;
|
26
|
-
DROP TABLE categories_posts;
|
27
|
-
DROP TABLE fk_test_has_fk;
|
28
|
-
DROP TABLE fk_test_has_pk;
|
29
|
-
DROP TABLE keyboards;
|
30
|
-
DROP TABLE legacy_things;
|
1
|
+
DROP TABLE reference_codes;
|
2
|
+
DROP TABLE reference_types;
|
@@ -14,3 +14,24 @@ CREATE TABLE `reference_codes` (
|
|
14
14
|
`description` varchar(50) default NULL,
|
15
15
|
PRIMARY KEY (`reference_type_id`,`reference_code`)
|
16
16
|
) TYPE=InnoDB;
|
17
|
+
|
18
|
+
CREATE TABLE `products` (
|
19
|
+
`id` int(11) NOT NULL auto_increment,
|
20
|
+
`name` varchar(50) default NULL,
|
21
|
+
PRIMARY KEY (`id`)
|
22
|
+
) TYPE=InnoDB;
|
23
|
+
|
24
|
+
CREATE TABLE `tariffs` (
|
25
|
+
`tariff_id` int(11) NOT NULL,
|
26
|
+
`start_date` date NOT NULL,
|
27
|
+
`amount` integer(11) default NULL,
|
28
|
+
PRIMARY KEY (`tariff_id`,`start_date`)
|
29
|
+
) TYPE=InnoDB;
|
30
|
+
|
31
|
+
CREATE TABLE `product_tariffs` (
|
32
|
+
`product_id` int(11) NOT NULL,
|
33
|
+
`tariff_id` int(11) NOT NULL,
|
34
|
+
`tariff_start_date` date NOT NULL,
|
35
|
+
PRIMARY KEY (`product_id`,`tariff_id`,`tariff_start_date`)
|
36
|
+
) TYPE=InnoDB;
|
37
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
first_flat:
|
2
|
+
product_id: 1
|
3
|
+
tariff_id: 1
|
4
|
+
tariff_start_date: <%= Date.today.to_s(:db) %>
|
5
|
+
first_free:
|
6
|
+
product_id: 1
|
7
|
+
tariff_id: 2
|
8
|
+
tariff_start_date: <%= Date.today.to_s(:db) %>
|
9
|
+
second_free:
|
10
|
+
product_id: 2
|
11
|
+
tariff_id: 2
|
12
|
+
tariff_start_date: <%= Date.today.to_s(:db) %>
|
data/test/ids_test.rb
CHANGED
@@ -42,6 +42,33 @@ class IdsTest < Test::Unit::TestCase
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def test_set_ids_string
|
46
|
+
testing_with do
|
47
|
+
array = @primary_keys.collect {|key| 5}
|
48
|
+
expected = composite? ? array.to_composite_keys : array.first
|
49
|
+
@first.id = expected.to_s
|
50
|
+
assert_equal expected, @first.id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_set_ids_array
|
55
|
+
testing_with do
|
56
|
+
array = @primary_keys.collect {|key| 5}
|
57
|
+
expected = composite? ? array.to_composite_keys : array.first
|
58
|
+
@first.id = expected
|
59
|
+
assert_equal expected, @first.id
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_set_ids_comp
|
64
|
+
testing_with do
|
65
|
+
array = @primary_keys.collect {|key| 5}
|
66
|
+
expected = composite? ? array.to_composite_keys : array.first
|
67
|
+
@first.id = expected
|
68
|
+
assert_equal expected, @first.id
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
45
72
|
def test_primary_keys
|
46
73
|
testing_with do
|
47
74
|
if composite?
|
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.
|
7
|
-
date: 2006-07-
|
6
|
+
version: 0.3.3
|
7
|
+
date: 2006-07-30 00:00:00 +02:00
|
8
8
|
summary: Support for composite primary keys in ActiveRecords
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -34,10 +34,12 @@ files:
|
|
34
34
|
- CHANGELOG
|
35
35
|
- lib/composite_primary_keys
|
36
36
|
- lib/composite_primary_keys.rb
|
37
|
-
- lib/composite_primary_keys/fixtures.rb
|
38
37
|
- lib/composite_primary_keys/composite_arrays.rb
|
39
38
|
- lib/composite_primary_keys/version.rb
|
40
39
|
- lib/composite_primary_keys/base.rb
|
40
|
+
- lib/composite_primary_keys/fixtures.rb
|
41
|
+
- lib/composite_primary_keys/reflection.rb
|
42
|
+
- lib/composite_primary_keys/associations.rb
|
41
43
|
- test/connections
|
42
44
|
- test/fixtures
|
43
45
|
- test/composite_arrays_test.rb
|
@@ -51,6 +53,9 @@ files:
|
|
51
53
|
- test/pagination_test.rb
|
52
54
|
- test/dummy_test.rb
|
53
55
|
- test/clone_test.rb
|
56
|
+
- test/associations_test.rb
|
57
|
+
- test/attributes_test.rb
|
58
|
+
- test/create_test.rb
|
54
59
|
- test/connections/native_mysql
|
55
60
|
- test/connections/native_mysql/connection.rb
|
56
61
|
- test/fixtures/reference_type.rb
|
@@ -58,10 +63,14 @@ files:
|
|
58
63
|
- test/fixtures/reference_types.yml
|
59
64
|
- test/fixtures/reference_codes.yml
|
60
65
|
- test/fixtures/db_definitions
|
66
|
+
- test/fixtures/product.rb
|
67
|
+
- test/fixtures/product_tariff.rb
|
68
|
+
- test/fixtures/tariff.rb
|
69
|
+
- test/fixtures/products.yml
|
70
|
+
- test/fixtures/tariffs.yml
|
71
|
+
- test/fixtures/product_tariffs.yml
|
61
72
|
- test/fixtures/db_definitions/mysql.drop.sql
|
62
73
|
- test/fixtures/db_definitions/mysql.sql
|
63
|
-
- test/fixtures/db_definitions/mysql2.drop.sql
|
64
|
-
- test/fixtures/db_definitions/mysql2.sql
|
65
74
|
test_files: []
|
66
75
|
|
67
76
|
rdoc_options:
|