composite_primary_keys 5.0.12 → 5.0.13
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.
- checksums.yaml +15 -0
- data/History.rdoc +5 -0
- data/lib/composite_primary_keys.rb +2 -0
- data/lib/composite_primary_keys/base.rb +2 -3
- data/lib/composite_primary_keys/composite_arrays.rb +11 -0
- data/lib/composite_primary_keys/nested_attributes.rb +78 -0
- data/lib/composite_primary_keys/relation/batches.rb +29 -12
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/connections/databases.yml +0 -1
- data/test/fixtures/departments.yml +8 -0
- data/test/fixtures/room_assignments.yml +16 -1
- data/test/fixtures/rooms.yml +6 -1
- data/test/fixtures/students.yml +11 -1
- data/test/test_associations.rb +4 -3
- data/test/test_find.rb +20 -4
- data/test/test_nested_attributes.rb +36 -9
- metadata +6 -9
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTVjMmVhMjcyMDljMDU3NWRjZTNhMzEyOTlhMDZhYTg1MTk0NDUxYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDIxYzNmNjc2ZWZiMDdjZThjYTM2YzQ3MjVjODJiYjBmYzcwYzYxOQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YjI2NjIwOWUzZGQyNDY3NTZhZTBiNjNjOWZlOTA5NDUyZTE2MWY4Y2UwNDAz
|
10
|
+
ZWZlMWEwMmZjNDQ4ODc2OTI2YTk1YmIxMDU2YjY2MTlkOThhMTFlODdmNTE1
|
11
|
+
MjRkNzhjMDA1YjRmZDZhODFkOTQ5MzhmM2U5ZTU2MzlhMWZlYWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NWZhZDQ3ZjhmNmNiZmFhZTI2YmZiYWJjNTE0OTA0ZWQxZjA3MzAzZTY2NzIz
|
14
|
+
OTU0YjA2NDAxZmY3MWQ4ZTAwMjgwZDdhMWNjZjk0OGViYzdmNjRjZjk4MGZj
|
15
|
+
NDEyMGFhMGNiNmZlZjY2YTJjMmVjMmQ1ZjI0MzgxOTcxYjE5ZDI=
|
data/History.rdoc
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 5.0.13 2013-04-27
|
2
|
+
* Batch query improvements (Jordan Byron)
|
3
|
+
* Support nested attributes (aiasfina and Charlie Savage)
|
4
|
+
* ActiveRecord 4.0 support in ar_4.0.x branch
|
5
|
+
|
1
6
|
== 5.0.12 2013-01-21
|
2
7
|
* Fix HasManyAssociation foreign_key_present? method to work with non CPK assocations (Charlie Savage)
|
3
8
|
|
@@ -53,6 +53,7 @@ require 'active_model/dirty'
|
|
53
53
|
require 'active_record/attribute_methods/dirty'
|
54
54
|
require 'active_record/attribute_methods/read'
|
55
55
|
require 'active_record/attribute_methods/write'
|
56
|
+
require 'active_record/nested_attributes'
|
56
57
|
|
57
58
|
require 'active_record/connection_adapters/abstract_adapter'
|
58
59
|
|
@@ -91,6 +92,7 @@ require 'composite_primary_keys/dirty'
|
|
91
92
|
require 'composite_primary_keys/attribute_methods/dirty'
|
92
93
|
require 'composite_primary_keys/attribute_methods/read'
|
93
94
|
require 'composite_primary_keys/attribute_methods/write'
|
95
|
+
require 'composite_primary_keys/nested_attributes'
|
94
96
|
|
95
97
|
require 'composite_primary_keys/connection_adapters/abstract_adapter'
|
96
98
|
require 'composite_primary_keys/connection_adapters/abstract/connection_specification_changes'
|
@@ -127,9 +127,8 @@ module ActiveRecord
|
|
127
127
|
|
128
128
|
# Sets the primary ID.
|
129
129
|
def id=(ids)
|
130
|
-
ids =
|
131
|
-
ids.
|
132
|
-
unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length
|
130
|
+
ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
|
131
|
+
unless ids.length == self.class.primary_keys.length
|
133
132
|
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
|
134
133
|
end
|
135
134
|
[self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
|
@@ -9,6 +9,17 @@ module CompositePrimaryKeys
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class CompositeKeys < Array
|
12
|
+
def self.parse(value)
|
13
|
+
case value
|
14
|
+
when Array
|
15
|
+
value.to_composite_keys
|
16
|
+
when String
|
17
|
+
self.new(value.split(ID_SEP))
|
18
|
+
else
|
19
|
+
raise(ArgumentError, "Unsupported type: #{value}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
12
23
|
def to_s
|
13
24
|
# Doing this makes it easier to parse Base#[](attr_name)
|
14
25
|
join(ID_SEP)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module NestedAttributes
|
3
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
|
4
|
+
options = self.nested_attributes_options[association_name]
|
5
|
+
|
6
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
7
|
+
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
8
|
+
end
|
9
|
+
|
10
|
+
if options[:limit] && attributes_collection.size > options[:limit]
|
11
|
+
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
|
12
|
+
end
|
13
|
+
|
14
|
+
if attributes_collection.is_a? Hash
|
15
|
+
keys = attributes_collection.keys
|
16
|
+
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
17
|
+
Array.wrap(attributes_collection)
|
18
|
+
else
|
19
|
+
attributes_collection.values
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
association = association(association_name)
|
24
|
+
|
25
|
+
existing_records = if association.loaded?
|
26
|
+
association.target
|
27
|
+
# CPK
|
28
|
+
elsif association.reflection.klass.composite?
|
29
|
+
attributes_collection.map do |attribute_collection|
|
30
|
+
attribute_ids = attribute_collection['id'] || attribute_collection[:id]
|
31
|
+
if attribute_ids
|
32
|
+
ids = CompositePrimaryKeys::CompositeKeys.parse(attribute_ids)
|
33
|
+
eq_predicates = association.klass.primary_key.zip(ids).map do |primary_key, value|
|
34
|
+
association.klass.arel_table[primary_key].eq(value)
|
35
|
+
end
|
36
|
+
association.scoped.where(*eq_predicates).to_a
|
37
|
+
else
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
end.flatten.compact
|
41
|
+
else
|
42
|
+
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
43
|
+
attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
|
44
|
+
end
|
45
|
+
|
46
|
+
attributes_collection.each do |attributes|
|
47
|
+
attributes = attributes.with_indifferent_access
|
48
|
+
|
49
|
+
if attributes['id'].blank?
|
50
|
+
unless reject_new_record?(association_name, attributes)
|
51
|
+
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
|
52
|
+
end
|
53
|
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
54
|
+
unless association.loaded? || call_reject_if(association_name, attributes)
|
55
|
+
# Make sure we are operating on the actual object which is in the association's
|
56
|
+
# proxy_target array (either by finding it, or adding it if not found)
|
57
|
+
target_record = association.target.detect { |record| record == existing_record }
|
58
|
+
|
59
|
+
if target_record
|
60
|
+
existing_record = target_record
|
61
|
+
else
|
62
|
+
association.add_to_target(existing_record)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
if !call_reject_if(association_name, attributes)
|
68
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
|
69
|
+
end
|
70
|
+
elsif assignment_opts[:without_protection]
|
71
|
+
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
|
72
|
+
else
|
73
|
+
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -5,7 +5,7 @@ module CompositePrimaryKeys
|
|
5
5
|
relation = self
|
6
6
|
|
7
7
|
unless arel.orders.blank? && arel.taken.blank?
|
8
|
-
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
8
|
+
::ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
9
9
|
end
|
10
10
|
|
11
11
|
if (finder_options = options.except(:start, :batch_size)).present?
|
@@ -21,11 +21,10 @@ module CompositePrimaryKeys
|
|
21
21
|
relation = relation.reorder(batch_order).limit(batch_size)
|
22
22
|
|
23
23
|
# CPK
|
24
|
-
#records = relation.where(table[primary_key].gteq(start)).all
|
25
|
-
self.primary_key.
|
26
|
-
|
27
|
-
end
|
28
|
-
records = relation.all
|
24
|
+
# records = relation.where(table[primary_key].gteq(start)).all
|
25
|
+
records = self.primary_key.reduce(relation) do |rel, key|
|
26
|
+
rel.where(table[key].gteq(start))
|
27
|
+
end.all
|
29
28
|
|
30
29
|
while records.any?
|
31
30
|
records_size = records.size
|
@@ -37,12 +36,23 @@ module CompositePrimaryKeys
|
|
37
36
|
|
38
37
|
if primary_key_offset
|
39
38
|
# CPK
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
# Lexicographically select records
|
40
|
+
#
|
41
|
+
query = prefixes(primary_key.zip(primary_key_offset)).map do |kvs|
|
42
|
+
and_clause = kvs.each_with_index.map do |(k, v), i|
|
43
|
+
# Use > for the last key in the and clause
|
44
|
+
# otherwise use =
|
45
|
+
if i == kvs.length - 1
|
46
|
+
table[k].gt(v)
|
47
|
+
else
|
48
|
+
table[k].eq(v)
|
49
|
+
end
|
50
|
+
end.reduce(:and)
|
51
|
+
|
52
|
+
Arel::Nodes::Grouping.new(and_clause)
|
53
|
+
end.reduce(:or)
|
45
54
|
|
55
|
+
records = relation.where(query)
|
46
56
|
else
|
47
57
|
raise "Primary key not included in the custom select clause"
|
48
58
|
end
|
@@ -51,6 +61,13 @@ module CompositePrimaryKeys
|
|
51
61
|
|
52
62
|
private
|
53
63
|
|
64
|
+
# Helper method to collect prefixes of an array:
|
65
|
+
# prefixes([:a, :b, :c]) => [[:a], [:a, :b], [:a, :b, :c]]
|
66
|
+
#
|
67
|
+
def prefixes(ary)
|
68
|
+
ary.length.times.reduce([]) { |results, i| results << ary[0..i] }
|
69
|
+
end
|
70
|
+
|
54
71
|
def batch_order
|
55
72
|
# CPK
|
56
73
|
#"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
@@ -60,4 +77,4 @@ module CompositePrimaryKeys
|
|
60
77
|
end
|
61
78
|
end
|
62
79
|
end
|
63
|
-
end
|
80
|
+
end
|
@@ -6,4 +6,19 @@ jacksons_room:
|
|
6
6
|
bobs_room:
|
7
7
|
student_id: 2
|
8
8
|
dorm_id: 1
|
9
|
-
room_id: 1
|
9
|
+
room_id: 1
|
10
|
+
|
11
|
+
kellys_room:
|
12
|
+
student_id: 3
|
13
|
+
dorm_id: 1
|
14
|
+
room_id: 2
|
15
|
+
|
16
|
+
jordans_room:
|
17
|
+
student_id: 4
|
18
|
+
dorm_id: 1
|
19
|
+
room_id: 2
|
20
|
+
|
21
|
+
brads_room:
|
22
|
+
student_id: 5
|
23
|
+
dorm_id: 1
|
24
|
+
room_id: 3
|
data/test/fixtures/rooms.yml
CHANGED
data/test/fixtures/students.yml
CHANGED
data/test/test_associations.rb
CHANGED
@@ -4,7 +4,7 @@ class TestAssociations < ActiveSupport::TestCase
|
|
4
4
|
fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants,
|
5
5
|
:dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings,
|
6
6
|
:departments, :employees, :memberships, :membership_statuses
|
7
|
-
|
7
|
+
|
8
8
|
def test_products
|
9
9
|
assert_not_nil products(:first_product).product_tariffs
|
10
10
|
assert_equal 2, products(:first_product).product_tariffs.length
|
@@ -131,7 +131,7 @@ class TestAssociations < ActiveSupport::TestCase
|
|
131
131
|
|
132
132
|
def test_has_many_through_when_through_association_is_composite
|
133
133
|
dorm = Dorm.find(:first)
|
134
|
-
assert_equal(
|
134
|
+
assert_equal(3, dorm.rooms.length)
|
135
135
|
assert_equal(1, dorm.rooms.first.room_attributes.length)
|
136
136
|
assert_equal('type', dorm.rooms.first.room_attributes.first.name)
|
137
137
|
end
|
@@ -213,9 +213,10 @@ class TestAssociations < ActiveSupport::TestCase
|
|
213
213
|
def test_has_many_with_composite_key
|
214
214
|
# In this case a regular model (Dorm) has_many composite models (Rooms)
|
215
215
|
dorm = dorms(:branner)
|
216
|
-
assert_equal(
|
216
|
+
assert_equal(3, dorm.rooms.length)
|
217
217
|
assert_equal(1, dorm.rooms[0].room_id)
|
218
218
|
assert_equal(2, dorm.rooms[1].room_id)
|
219
|
+
assert_equal(3, dorm.rooms[2].room_id)
|
219
220
|
end
|
220
221
|
|
221
222
|
def test_joins_has_many_with_primary_key
|
data/test/test_find.rb
CHANGED
@@ -46,6 +46,22 @@ class TestFind < ActiveSupport::TestCase
|
|
46
46
|
assert_equal(['The Netherlands', 'Amsterdam'], capitol.id)
|
47
47
|
end
|
48
48
|
|
49
|
+
def test_find_each
|
50
|
+
room_assignments = []
|
51
|
+
RoomAssignment.find_each(:batch_size => 2) do |assignment|
|
52
|
+
room_assignments << assignment
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_equal(RoomAssignment.count, room_assignments.uniq.length)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_find_each_with_scope
|
59
|
+
scoped_departments = Department.where("department_id <> 3")
|
60
|
+
scoped_departments.find_each(:batch_size => 2) do |department|
|
61
|
+
assert department.id != 3
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
49
65
|
def test_not_found
|
50
66
|
error = assert_raise(::ActiveRecord::RecordNotFound) do
|
51
67
|
ReferenceCode.find(['999', '999'])
|
@@ -76,9 +92,9 @@ class TestFind < ActiveSupport::TestCase
|
|
76
92
|
assert_equal([1,1], suburb.id)
|
77
93
|
end
|
78
94
|
|
79
|
-
def
|
80
|
-
|
81
|
-
assert_equal(
|
95
|
+
def test_find_in_batches
|
96
|
+
Department.find_in_batches do |batch|
|
97
|
+
assert_equal(Department.count, batch.size)
|
82
98
|
end
|
83
99
|
end
|
84
|
-
end
|
100
|
+
end
|
@@ -2,15 +2,13 @@ require File.expand_path('../abstract_unit', __FILE__)
|
|
2
2
|
|
3
3
|
# Testing the find action on composite ActiveRecords with two primary keys
|
4
4
|
class TestNestedAttributes < ActiveSupport::TestCase
|
5
|
-
fixtures :reference_types
|
6
|
-
|
7
|
-
def setup
|
8
|
-
@reference_type = ReferenceType.first
|
9
|
-
end
|
5
|
+
fixtures :reference_types, :reference_codes
|
10
6
|
|
11
7
|
def test_nested_atttribute_create
|
12
8
|
code_id = 1001
|
13
|
-
|
9
|
+
|
10
|
+
reference_type = reference_types(:name_prefix)
|
11
|
+
reference_type.update_attribute :reference_codes_attributes, [{
|
14
12
|
:reference_code => code_id,
|
15
13
|
:code_label => 'XX',
|
16
14
|
:abbreviation => 'Xx'
|
@@ -20,14 +18,17 @@ class TestNestedAttributes < ActiveSupport::TestCase
|
|
20
18
|
|
21
19
|
def test_nested_atttribute_update
|
22
20
|
code_id = 1002
|
23
|
-
|
21
|
+
|
22
|
+
reference_type = reference_types(:name_prefix)
|
23
|
+
reference_type.update_attribute :reference_codes_attributes, [{
|
24
24
|
:reference_code => code_id,
|
25
25
|
:code_label => 'XX',
|
26
26
|
:abbreviation => 'Xx'
|
27
27
|
}]
|
28
|
+
|
28
29
|
reference_code = ReferenceCode.find_by_reference_code(code_id)
|
29
|
-
cpk = CompositePrimaryKeys::CompositeKeys[
|
30
|
-
|
30
|
+
cpk = CompositePrimaryKeys::CompositeKeys[reference_type.reference_type_id, code_id]
|
31
|
+
reference_type.update_attribute :reference_codes_attributes, [{
|
31
32
|
:id => cpk,
|
32
33
|
:code_label => 'AAA',
|
33
34
|
:abbreviation => 'Aaa'
|
@@ -36,4 +37,30 @@ class TestNestedAttributes < ActiveSupport::TestCase
|
|
36
37
|
assert_kind_of(ReferenceCode, reference_code)
|
37
38
|
assert_equal(reference_code.code_label, 'AAA')
|
38
39
|
end
|
40
|
+
|
41
|
+
def test_nested_atttribute_update_2
|
42
|
+
reference_type = reference_types(:gender)
|
43
|
+
reference_code = reference_codes(:gender_male)
|
44
|
+
|
45
|
+
reference_type.update_attributes(:reference_codes_attributes => [{:id => reference_code.id,
|
46
|
+
:code_label => 'XX',
|
47
|
+
:abbreviation => 'Xx'}])
|
48
|
+
|
49
|
+
reference_code.reload
|
50
|
+
assert_equal(reference_code.code_label, 'XX')
|
51
|
+
assert_equal(reference_code.abbreviation, 'Xx')
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_nested_atttribute_update_3
|
55
|
+
reference_type = reference_types(:gender)
|
56
|
+
reference_code = reference_codes(:gender_male)
|
57
|
+
|
58
|
+
reference_type.update_attributes(:reference_codes_attributes => [{:id => reference_code.id.to_s,
|
59
|
+
:code_label => 'XX',
|
60
|
+
:abbreviation => 'Xx'}])
|
61
|
+
|
62
|
+
reference_code.reload
|
63
|
+
assert_equal(reference_code.code_label, 'XX')
|
64
|
+
assert_equal(reference_code.abbreviation, 'Xx')
|
65
|
+
end
|
39
66
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: composite_primary_keys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.
|
5
|
-
prerelease:
|
4
|
+
version: 5.0.13
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Dr Nic Williams
|
@@ -10,12 +9,11 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2013-
|
12
|
+
date: 2013-04-27 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activerecord
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - ! '>='
|
21
19
|
- !ruby/object:Gem::Version
|
@@ -26,7 +24,6 @@ dependencies:
|
|
26
24
|
type: :runtime
|
27
25
|
prerelease: false
|
28
26
|
version_requirements: !ruby/object:Gem::Requirement
|
29
|
-
none: false
|
30
27
|
requirements:
|
31
28
|
- - ! '>='
|
32
29
|
- !ruby/object:Gem::Version
|
@@ -69,6 +66,7 @@ files:
|
|
69
66
|
- lib/composite_primary_keys/counter_cache.rb
|
70
67
|
- lib/composite_primary_keys/dirty.rb
|
71
68
|
- lib/composite_primary_keys/fixtures.rb
|
69
|
+
- lib/composite_primary_keys/nested_attributes.rb
|
72
70
|
- lib/composite_primary_keys/persistence.rb
|
73
71
|
- lib/composite_primary_keys/relation/batches.rb
|
74
72
|
- lib/composite_primary_keys/relation/calculations.rb
|
@@ -195,27 +193,26 @@ files:
|
|
195
193
|
- test/test_validations.rb
|
196
194
|
homepage: https://github.com/drnic/composite_primary_keys
|
197
195
|
licenses: []
|
196
|
+
metadata: {}
|
198
197
|
post_install_message:
|
199
198
|
rdoc_options: []
|
200
199
|
require_paths:
|
201
200
|
- lib
|
202
201
|
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
-
none: false
|
204
202
|
requirements:
|
205
203
|
- - ! '>='
|
206
204
|
- !ruby/object:Gem::Version
|
207
205
|
version: 1.8.7
|
208
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
209
|
-
none: false
|
210
207
|
requirements:
|
211
208
|
- - ! '>='
|
212
209
|
- !ruby/object:Gem::Version
|
213
210
|
version: '0'
|
214
211
|
requirements: []
|
215
212
|
rubyforge_project: compositekeys
|
216
|
-
rubygems_version:
|
213
|
+
rubygems_version: 2.0.3
|
217
214
|
signing_key:
|
218
|
-
specification_version:
|
215
|
+
specification_version: 4
|
219
216
|
summary: Composite key support for ActiveRecord
|
220
217
|
test_files:
|
221
218
|
- test/abstract_unit.rb
|