composite_primary_keys 0.7.5 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +75 -0
- data/Rakefile +83 -101
- data/lib/composite_primary_keys.rb +8 -0
- data/lib/composite_primary_keys/associations.rb +66 -18
- data/lib/composite_primary_keys/base.rb +169 -159
- data/lib/composite_primary_keys/calculations.rb +63 -0
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +11 -0
- data/lib/composite_primary_keys/version.rb +2 -2
- data/scripts/txt2html +4 -2
- data/scripts/txt2js +3 -2
- data/test/abstract_unit.rb +9 -2
- data/test/connections/native_mysql/connection.rb +5 -2
- data/test/connections/native_postgresql/connection.rb +15 -0
- data/test/connections/native_sqlite/connection.rb +10 -0
- data/test/fixtures/db_definitions/mysql.sql +20 -0
- data/test/fixtures/db_definitions/postgresql.sql +100 -0
- data/test/fixtures/db_definitions/sqlite.sql +84 -0
- data/test/fixtures/group.rb +3 -0
- data/test/fixtures/groups.yml +3 -0
- data/test/fixtures/membership.rb +7 -0
- data/test/fixtures/membership_status.rb +3 -0
- data/test/fixtures/membership_statuses.yml +8 -0
- data/test/fixtures/memberships.yml +6 -0
- data/test/{associations_test.rb → test_associations.rb} +22 -12
- data/test/{attributes_test.rb → test_attributes.rb} +4 -5
- data/test/{clone_test.rb → test_clone.rb} +2 -3
- data/test/{create_test.rb → test_create.rb} +2 -3
- data/test/{delete_test.rb → test_delete.rb} +2 -3
- data/test/{dummy_test.rb → test_dummy.rb} +4 -5
- data/test/{find_test.rb → test_find.rb} +3 -4
- data/test/{ids_test.rb → test_ids.rb} +4 -4
- data/test/{miscellaneous_test.rb → test_miscellaneous.rb} +2 -3
- data/test/{pagination_test.rb → test_pagination.rb} +4 -3
- data/test/{santiago_test.rb → test_santiago.rb} +5 -3
- data/test/test_tutorial_examle.rb +29 -0
- data/test/{update_test.rb → test_update.rb} +2 -3
- data/website/index.html +267 -201
- data/website/index.txt +74 -70
- data/website/stylesheets/screen.css +33 -3
- data/website/version-raw.js +1 -1
- data/website/version.js +1 -1
- metadata +80 -66
- data/scripts/http-access2-2.0.6.gem +0 -0
- data/scripts/rubyforge +0 -217
- data/scripts/rubyforge-orig +0 -217
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -10
data/scripts/txt2html
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'rubygems'
|
3
4
|
require 'redcloth'
|
4
5
|
require 'syntax/convertors/html'
|
5
6
|
require 'erb'
|
6
|
-
require '
|
7
|
+
require File.dirname(__FILE__) + '/../lib/composite_primary_keys/version.rb'
|
7
8
|
|
8
9
|
version = CompositePrimaryKeys::VERSION::STRING
|
9
10
|
download = 'http://rubyforge.org/projects/compositekeys'
|
@@ -34,7 +35,8 @@ end
|
|
34
35
|
|
35
36
|
if ARGV.length >= 1
|
36
37
|
src, template = ARGV
|
37
|
-
template ||= 'template.rhtml'
|
38
|
+
template ||= File.dirname(__FILE__) + '/../website/template.rhtml'
|
39
|
+
|
38
40
|
else
|
39
41
|
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
40
42
|
exit!
|
data/scripts/txt2js
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'rubygems'
|
3
4
|
require 'redcloth'
|
4
5
|
require 'syntax/convertors/html'
|
5
6
|
require 'erb'
|
6
7
|
require 'active_support'
|
7
|
-
require '
|
8
|
+
require File.dirname(__FILE__) + '/../lib/composite_primary_keys/version.rb'
|
8
9
|
|
9
10
|
version = CompositePrimaryKeys::VERSION::STRING
|
10
11
|
download = 'http://rubyforge.org/projects/compositekeys'
|
@@ -35,7 +36,7 @@ end
|
|
35
36
|
|
36
37
|
if ARGV.length >= 1
|
37
38
|
src, template = ARGV
|
38
|
-
template ||= 'template.js'
|
39
|
+
template ||= File.dirname(__FILE__) + '/../website/template.js'
|
39
40
|
else
|
40
41
|
puts("Usage: #{File.split($0).last} source.txt [template.js] > output.html")
|
41
42
|
exit!
|
data/test/abstract_unit.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
2
|
|
3
|
+
require 'rubygems'
|
3
4
|
require 'test/unit'
|
4
5
|
require 'hash_tricks'
|
5
6
|
require 'active_record'
|
6
7
|
require 'active_record/fixtures'
|
7
8
|
require 'active_support/binding_of_caller'
|
8
9
|
require 'active_support/breakpoint'
|
9
|
-
|
10
|
+
begin
|
11
|
+
require 'connection'
|
12
|
+
rescue MissingSourceFile => e
|
13
|
+
adapter = 'postgresql' #'sqlite'
|
14
|
+
require "#{File.dirname(__FILE__)}/connections/native_#{adapter}/connection"
|
15
|
+
end
|
10
16
|
require 'composite_primary_keys'
|
11
17
|
|
12
18
|
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)
|
@@ -56,7 +62,8 @@ protected
|
|
56
62
|
classes.keys.each do |@key_test|
|
57
63
|
@klass_info = classes[@key_test]
|
58
64
|
@klass, @primary_keys = @klass_info[:class], @klass_info[:primary_keys]
|
59
|
-
|
65
|
+
order = @klass.primary_key.is_a?(String) ? @klass.primary_key : @klass.primary_key.join(',')
|
66
|
+
@first = @klass.find(:first, :order => order)
|
60
67
|
yield
|
61
68
|
end
|
62
69
|
end
|
@@ -5,9 +5,12 @@ ActiveRecord::Base.logger = Logger.new("debug.log")
|
|
5
5
|
|
6
6
|
db1 = 'composite_primary_keys_unittest'
|
7
7
|
|
8
|
-
|
8
|
+
connection_options = {
|
9
9
|
:adapter => "mysql",
|
10
10
|
:username => "root",
|
11
11
|
:encoding => "utf8",
|
12
12
|
:database => db1
|
13
|
-
|
13
|
+
}
|
14
|
+
|
15
|
+
ActiveRecord::Base.configurations = { db1 => connection_options }
|
16
|
+
ActiveRecord::Base.establish_connection(connection_options)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
print "Using native Postgresql\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
5
|
+
|
6
|
+
db1 = 'composite_primary_keys_unittest'
|
7
|
+
|
8
|
+
connection_options = {
|
9
|
+
:adapter => "postgresql",
|
10
|
+
:encoding => "utf8",
|
11
|
+
:database => db1
|
12
|
+
}
|
13
|
+
|
14
|
+
ActiveRecord::Base.configurations = { db1 => connection_options }
|
15
|
+
ActiveRecord::Base.establish_connection(connection_options)
|
@@ -70,3 +70,23 @@ CREATE TABLE `readings` (
|
|
70
70
|
PRIMARY KEY (`id`)
|
71
71
|
) TYPE=InnoDB;
|
72
72
|
|
73
|
+
CREATE TABLE groups (
|
74
|
+
id int(11) NOT NULL auto_increment,
|
75
|
+
name varchar(50) NOT NULL,
|
76
|
+
PRIMARY KEY (id)
|
77
|
+
) TYPE=InnoDB;
|
78
|
+
|
79
|
+
CREATE TABLE memberships (
|
80
|
+
user_id int(11) NOT NULL,
|
81
|
+
group_id int(11) NOT NULL,
|
82
|
+
PRIMARY KEY (user_id,group_id)
|
83
|
+
) TYPE=InnoDB;
|
84
|
+
|
85
|
+
CREATE TABLE membership_statuses (
|
86
|
+
id int(11) NOT NULL auto_increment,
|
87
|
+
user_id int(11) NOT NULL,
|
88
|
+
group_id int(11) NOT NULL,
|
89
|
+
status varchar(50) NOT NULL,
|
90
|
+
PRIMARY KEY (id)
|
91
|
+
) TYPE=InnoDB;
|
92
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
CREATE SEQUENCE public.reference_types_seq START 100;
|
2
|
+
CREATE TABLE reference_types (
|
3
|
+
reference_type_id int DEFAULT nextval('public.reference_types_seq'),
|
4
|
+
type_label varchar(50) default NULL,
|
5
|
+
abbreviation varchar(50) default NULL,
|
6
|
+
description varchar(50) default NULL,
|
7
|
+
PRIMARY KEY (reference_type_id)
|
8
|
+
);
|
9
|
+
|
10
|
+
CREATE TABLE reference_codes (
|
11
|
+
reference_type_id int NOT NULL,
|
12
|
+
reference_code int NOT NULL,
|
13
|
+
code_label varchar(50) default NULL,
|
14
|
+
abbreviation varchar(50) default NULL,
|
15
|
+
description varchar(50) default NULL,
|
16
|
+
PRIMARY KEY (reference_type_id,reference_code)
|
17
|
+
);
|
18
|
+
|
19
|
+
CREATE SEQUENCE public.products_seq START 100;
|
20
|
+
CREATE TABLE products (
|
21
|
+
id int NOT NULL DEFAULT nextval('public.products_seq'),
|
22
|
+
name varchar(50) default NULL,
|
23
|
+
PRIMARY KEY (id)
|
24
|
+
);
|
25
|
+
|
26
|
+
CREATE TABLE tariffs (
|
27
|
+
tariff_id int NOT NULL,
|
28
|
+
start_date date NOT NULL,
|
29
|
+
amount int default NULL,
|
30
|
+
PRIMARY KEY (tariff_id,start_date)
|
31
|
+
);
|
32
|
+
|
33
|
+
CREATE TABLE product_tariffs (
|
34
|
+
product_id int NOT NULL,
|
35
|
+
tariff_id int NOT NULL,
|
36
|
+
tariff_start_date date NOT NULL,
|
37
|
+
PRIMARY KEY (product_id,tariff_id,tariff_start_date)
|
38
|
+
);
|
39
|
+
|
40
|
+
CREATE TABLE suburbs (
|
41
|
+
city_id int NOT NULL,
|
42
|
+
suburb_id int NOT NULL,
|
43
|
+
name varchar(50) NOT NULL,
|
44
|
+
PRIMARY KEY (city_id,suburb_id)
|
45
|
+
);
|
46
|
+
|
47
|
+
CREATE SEQUENCE public.streets_seq START 100;
|
48
|
+
CREATE TABLE streets (
|
49
|
+
id int NOT NULL DEFAULT nextval('public.streets_seq'),
|
50
|
+
city_id int NOT NULL,
|
51
|
+
suburb_id int NOT NULL,
|
52
|
+
name varchar(50) NOT NULL,
|
53
|
+
PRIMARY KEY (id)
|
54
|
+
);
|
55
|
+
|
56
|
+
CREATE SEQUENCE public.users_seq START 100;
|
57
|
+
CREATE TABLE users (
|
58
|
+
id int NOT NULL DEFAULT nextval('public.users_seq'),
|
59
|
+
name varchar(50) NOT NULL,
|
60
|
+
PRIMARY KEY (id)
|
61
|
+
);
|
62
|
+
|
63
|
+
CREATE SEQUENCE public.articles_seq START 100;
|
64
|
+
CREATE TABLE articles (
|
65
|
+
id int NOT NULL DEFAULT nextval('public.articles_seq'),
|
66
|
+
name varchar(50) NOT NULL,
|
67
|
+
PRIMARY KEY (id)
|
68
|
+
);
|
69
|
+
|
70
|
+
CREATE SEQUENCE public.readings_seq START 100;
|
71
|
+
CREATE TABLE readings (
|
72
|
+
id int NOT NULL DEFAULT nextval('public.readings_seq'),
|
73
|
+
user_id int NOT NULL,
|
74
|
+
article_id int NOT NULL,
|
75
|
+
rating int NOT NULL,
|
76
|
+
PRIMARY KEY (id)
|
77
|
+
);
|
78
|
+
|
79
|
+
CREATE SEQUENCE public.groups_seq START 100;
|
80
|
+
CREATE TABLE groups (
|
81
|
+
id int NOT NULL DEFAULT nextval('public.groups_seq'),
|
82
|
+
name varchar(50) NOT NULL,
|
83
|
+
PRIMARY KEY (id)
|
84
|
+
);
|
85
|
+
|
86
|
+
CREATE TABLE memberships (
|
87
|
+
user_id int NOT NULL,
|
88
|
+
group_id int NOT NULL,
|
89
|
+
PRIMARY KEY (user_id,group_id)
|
90
|
+
);
|
91
|
+
|
92
|
+
CREATE SEQUENCE public.membership_statuses_seq START 100;
|
93
|
+
CREATE TABLE membership_statuses (
|
94
|
+
id int NOT NULL DEFAULT nextval('public.membership_statuses_seq'),
|
95
|
+
user_id int NOT NULL,
|
96
|
+
group_id int NOT NULL,
|
97
|
+
status varchar(50) NOT NULL,
|
98
|
+
PRIMARY KEY (id)
|
99
|
+
);
|
100
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
CREATE TABLE `reference_types` (
|
2
|
+
`reference_type_id` INTEGER PRIMARY KEY,
|
3
|
+
`type_label` varchar(50) default NULL,
|
4
|
+
`abbreviation` varchar(50) default NULL,
|
5
|
+
`description` varchar(50) default NULL
|
6
|
+
);
|
7
|
+
|
8
|
+
CREATE TABLE `reference_codes` (
|
9
|
+
`reference_type_id` int(11) NOT NULL,
|
10
|
+
`reference_code` int(11) NOT NULL,
|
11
|
+
`code_label` varchar(50) default NULL,
|
12
|
+
`abbreviation` varchar(50) default NULL,
|
13
|
+
`description` varchar(50) default NULL,
|
14
|
+
PRIMARY KEY (`reference_type_id`,`reference_code`)
|
15
|
+
);
|
16
|
+
|
17
|
+
CREATE TABLE `products` (
|
18
|
+
`id` int(11) NOT NULL PRIMARY KEY,
|
19
|
+
`name` varchar(50) default NULL
|
20
|
+
);
|
21
|
+
|
22
|
+
CREATE TABLE `tariffs` (
|
23
|
+
`tariff_id` int(11) NOT NULL,
|
24
|
+
`start_date` date NOT NULL,
|
25
|
+
`amount` integer(11) default NULL,
|
26
|
+
PRIMARY KEY (`tariff_id`,`start_date`)
|
27
|
+
);
|
28
|
+
|
29
|
+
CREATE TABLE `product_tariffs` (
|
30
|
+
`product_id` int(11) NOT NULL,
|
31
|
+
`tariff_id` int(11) NOT NULL,
|
32
|
+
`tariff_start_date` date NOT NULL,
|
33
|
+
PRIMARY KEY (`product_id`,`tariff_id`,`tariff_start_date`)
|
34
|
+
);
|
35
|
+
|
36
|
+
CREATE TABLE `suburbs` (
|
37
|
+
`city_id` int(11) NOT NULL,
|
38
|
+
`suburb_id` int(11) NOT NULL,
|
39
|
+
`name` varchar(50) NOT NULL,
|
40
|
+
PRIMARY KEY (`city_id`,`suburb_id`)
|
41
|
+
);
|
42
|
+
|
43
|
+
CREATE TABLE `streets` (
|
44
|
+
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
45
|
+
`city_id` int(11) NOT NULL,
|
46
|
+
`suburb_id` int(11) NOT NULL,
|
47
|
+
`name` varchar(50) NOT NULL
|
48
|
+
);
|
49
|
+
|
50
|
+
CREATE TABLE `users` (
|
51
|
+
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
52
|
+
`name` varchar(50) NOT NULL
|
53
|
+
);
|
54
|
+
|
55
|
+
CREATE TABLE `articles` (
|
56
|
+
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
57
|
+
`name` varchar(50) NOT NULL
|
58
|
+
);
|
59
|
+
|
60
|
+
CREATE TABLE `readings` (
|
61
|
+
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
62
|
+
`user_id` int(11) NOT NULL,
|
63
|
+
`article_id` int(11) NOT NULL,
|
64
|
+
`rating` int(11) NOT NULL
|
65
|
+
);
|
66
|
+
|
67
|
+
CREATE TABLE groups (
|
68
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
69
|
+
name varchar(50) NOT NULL
|
70
|
+
);
|
71
|
+
|
72
|
+
CREATE TABLE memberships (
|
73
|
+
user_id int NOT NULL,
|
74
|
+
group_id int NOT NULL,
|
75
|
+
PRIMARY KEY (user_id,group_id)
|
76
|
+
);
|
77
|
+
|
78
|
+
CREATE TABLE membership_statuses (
|
79
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
80
|
+
user_id int NOT NULL,
|
81
|
+
group_id int NOT NULL,
|
82
|
+
status varchar(50) NOT NULL
|
83
|
+
);
|
84
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class Membership < ActiveRecord::Base
|
2
|
+
# set_primary_keys *keys - turns on composite key functionality
|
3
|
+
set_primary_keys :user_id, :group_id
|
4
|
+
belongs_to :user
|
5
|
+
belongs_to :group
|
6
|
+
has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id]
|
7
|
+
end
|
@@ -5,15 +5,14 @@ require 'fixtures/product_tariff'
|
|
5
5
|
require 'fixtures/suburb'
|
6
6
|
require 'fixtures/street'
|
7
7
|
|
8
|
-
class
|
9
|
-
fixtures :products, :tariffs, :product_tariffs, :suburbs, :streets
|
8
|
+
class TestAssociations < Test::Unit::TestCase
|
10
9
|
|
11
10
|
def setup
|
12
|
-
|
13
|
-
@first_product =
|
14
|
-
@flat =
|
15
|
-
@free =
|
16
|
-
@first_flat =
|
11
|
+
create_fixtures :products, :tariffs, :product_tariffs, :suburbs, :streets
|
12
|
+
@first_product = Product.find(1)
|
13
|
+
@flat = Tariff.find(1, Date.today.to_s(:db))
|
14
|
+
@free = Tariff.find(2, Date.today.to_s(:db))
|
15
|
+
@first_flat = ProductTariff.find(1, 1, Date.today.to_s(:db))
|
17
16
|
end
|
18
17
|
|
19
18
|
def test_setup
|
@@ -61,24 +60,35 @@ class AssociationTest < Test::Unit::TestCase
|
|
61
60
|
assert @products = Product.find(:all, :include => :product_tariffs)
|
62
61
|
assert_equal 2, @products.length
|
63
62
|
assert_not_nil @products.first.instance_variable_get('@product_tariffs'), '@product_tariffs not set; should be array'
|
64
|
-
assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
63
|
+
assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
64
|
+
"Incorrect number of product_tariffs returned"
|
65
65
|
end
|
66
66
|
|
67
67
|
def test_find_includes_tariffs
|
68
68
|
assert @tariffs = Tariff.find(:all, :include => :product_tariffs)
|
69
69
|
assert_equal 3, @tariffs.length
|
70
70
|
assert_not_nil @tariffs.first.instance_variable_get('@product_tariffs'), '@product_tariffs not set; should be array'
|
71
|
-
assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
71
|
+
assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
72
|
+
"Incorrect number of product_tariffs returnedturned"
|
72
73
|
end
|
73
74
|
|
74
75
|
def XXX_test_find_includes_extended
|
75
76
|
# TODO - what's the correct syntax?
|
76
77
|
assert @products = Product.find(:all, :include => {:product_tariffs => :tariffs})
|
77
|
-
assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
78
|
+
assert_equal 3, @products.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
79
|
+
"Incorrect number of product_tariffs returned"
|
78
80
|
|
79
81
|
assert @tariffs = Tariff.find(:all, :include => {:product_tariffs => :products})
|
80
|
-
assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
81
|
-
|
82
|
+
assert_equal 3, @tariffs.inject(0) {|sum, tariff| sum + tariff.instance_variable_get('@product_tariffs').length},
|
83
|
+
"Incorrect number of product_tariffs returned"
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_join_where_clause
|
87
|
+
@product = Product.find(:first, :include => :product_tariffs)
|
88
|
+
where_clause = @product.product_tariffs.composite_where_clause(
|
89
|
+
['foo','bar'], [1,2]
|
90
|
+
)
|
91
|
+
assert_equal('(foo=1 AND bar=2)', where_clause)
|
82
92
|
end
|
83
93
|
|
84
94
|
end
|
@@ -5,8 +5,7 @@ require 'fixtures/product'
|
|
5
5
|
require 'fixtures/tariff'
|
6
6
|
require 'fixtures/product_tariff'
|
7
7
|
|
8
|
-
class
|
9
|
-
fixtures :reference_types, :reference_codes, :products, :tariffs, :product_tariffs
|
8
|
+
class TestAttributes < Test::Unit::TestCase
|
10
9
|
|
11
10
|
CLASSES = {
|
12
11
|
:single => {
|
@@ -20,7 +19,7 @@ class AttributesTest < Test::Unit::TestCase
|
|
20
19
|
}
|
21
20
|
|
22
21
|
def setup
|
23
|
-
|
22
|
+
create_fixtures :reference_types, :reference_codes, :products, :tariffs, :product_tariffs
|
24
23
|
self.class.classes = CLASSES
|
25
24
|
end
|
26
25
|
|
@@ -49,8 +48,8 @@ class AttributesTest < Test::Unit::TestCase
|
|
49
48
|
end
|
50
49
|
|
51
50
|
def test_brackets_foreign_key_assignment
|
52
|
-
@flat =
|
53
|
-
@second_free =
|
51
|
+
@flat = Tariff.find(1, Date.today.to_s(:db))
|
52
|
+
@second_free = ProductTariff.find(2,2,Date.today.to_s(:db))
|
54
53
|
@second_free_fk = [:tariff_id, :tariff_start_date]
|
55
54
|
@second_free[key = @second_free_fk] = @flat.id
|
56
55
|
compare_indexes('@flat', @flat.class.primary_key, '@second_free', @second_free_fk)
|